summaryrefslogtreecommitdiffstats
path: root/vcl/source/edit
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 05:54:39 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 05:54:39 +0000
commit267c6f2ac71f92999e969232431ba04678e7437e (patch)
tree358c9467650e1d0a1d7227a21dac2e3d08b622b2 /vcl/source/edit
parentInitial commit. (diff)
downloadlibreoffice-267c6f2ac71f92999e969232431ba04678e7437e.tar.xz
libreoffice-267c6f2ac71f92999e969232431ba04678e7437e.zip
Adding upstream version 4:24.2.0.upstream/4%24.2.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'vcl/source/edit')
-rw-r--r--vcl/source/edit/textdat2.hxx281
-rw-r--r--vcl/source/edit/textdata.cxx345
-rw-r--r--vcl/source/edit/textdoc.cxx536
-rw-r--r--vcl/source/edit/textdoc.hxx126
-rw-r--r--vcl/source/edit/texteng.cxx2894
-rw-r--r--vcl/source/edit/textund2.hxx105
-rw-r--r--vcl/source/edit/textundo.cxx331
-rw-r--r--vcl/source/edit/textundo.hxx77
-rw-r--r--vcl/source/edit/textview.cxx2238
-rw-r--r--vcl/source/edit/txtattr.cxx93
-rw-r--r--vcl/source/edit/vclmedit.cxx1509
-rw-r--r--vcl/source/edit/xtextedt.cxx227
12 files changed, 8762 insertions, 0 deletions
diff --git a/vcl/source/edit/textdat2.hxx b/vcl/source/edit/textdat2.hxx
new file mode 100644
index 0000000000..e0174848f7
--- /dev/null
+++ b/vcl/source/edit/textdat2.hxx
@@ -0,0 +1,281 @@
+/* -*- 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 .
+ */
+
+#pragma once
+
+#include <vcl/seleng.hxx>
+#include <vcl/cursor.hxx>
+#include <vcl/idle.hxx>
+#include <vcl/textdata.hxx>
+
+#include <cstddef>
+#include <limits>
+#include <vector>
+
+class TextNode;
+class TextView;
+
+#define PORTIONKIND_TEXT 0
+#define PORTIONKIND_TAB 1
+
+#define DELMODE_SIMPLE 0
+#define DELMODE_RESTOFWORD 1
+#define DELMODE_RESTOFCONTENT 2
+
+#define DEL_LEFT 1
+#define DEL_RIGHT 2
+#define TRAVEL_X_DONTKNOW 0xFFFF
+#define MAXCHARSINPARA 0x3FFF-CHARPOSGROW
+
+#define LINE_SEP 0x0A
+
+class TETextPortion
+{
+private:
+ tools::Long nWidth;
+ sal_Int32 nLen;
+ sal_uInt8 nKind;
+ bool bRightToLeft;
+
+public:
+ TETextPortion( sal_Int32 nL )
+ : nWidth {-1}
+ , nLen {nL}
+ , nKind {PORTIONKIND_TEXT}
+ , bRightToLeft {false}
+ {}
+
+ sal_Int32& GetLen() { return nLen; }
+ sal_Int32 GetLen() const { return nLen; }
+ tools::Long& GetWidth() { return nWidth; }
+ sal_uInt8& GetKind() { return nKind; }
+ void SetRightToLeft(bool b) { bRightToLeft = b; }
+ bool IsRightToLeft() const { return bRightToLeft; }
+};
+
+class TETextPortionList
+{
+private:
+ std::vector<TETextPortion> maPortions;
+
+public:
+ static constexpr auto npos = std::numeric_limits<std::size_t>::max();
+
+ TETextPortionList();
+ ~TETextPortionList();
+
+ TETextPortion& operator[]( std::size_t nPos );
+ std::vector<TETextPortion>::iterator begin();
+ std::vector<TETextPortion>::const_iterator begin() const;
+ std::vector<TETextPortion>::iterator end();
+ std::vector<TETextPortion>::const_iterator end() const;
+ bool empty() const;
+ std::size_t size() const;
+ std::vector<TETextPortion>::iterator erase( const std::vector<TETextPortion>::iterator& aIter );
+ std::vector<TETextPortion>::iterator insert( const std::vector<TETextPortion>::iterator& aIter,
+ const TETextPortion& rTP );
+ void push_back( const TETextPortion & aTP );
+
+ void Reset();
+ std::size_t FindPortion( sal_Int32 nCharPos, sal_Int32& rPortionStart, bool bPreferStartingPortion = false );
+ void DeleteFromPortion( std::size_t nDelFrom );
+};
+
+struct TEWritingDirectionInfo
+{
+ bool bLeftToRight;
+ sal_Int32 nStartPos;
+ sal_Int32 nEndPos;
+ TEWritingDirectionInfo( bool LeftToRight, sal_Int32 Start, sal_Int32 End )
+ : bLeftToRight {LeftToRight}
+ , nStartPos {Start}
+ , nEndPos {End}
+ {}
+};
+
+class TextLine
+{
+private:
+ sal_Int32 mnStart;
+ sal_Int32 mnEnd;
+ std::size_t mnStartPortion;
+ std::size_t mnEndPortion;
+
+ short mnStartX;
+
+ bool mbInvalid; // for clever formatting/output
+
+public:
+ TextLine()
+ : mnStart {0}
+ , mnEnd {0}
+ , mnStartPortion {0}
+ , mnEndPortion {0}
+ , mnStartX {0}
+ , mbInvalid {true}
+ {}
+
+ bool IsIn( sal_Int32 nIndex, bool bInclEnd ) const
+ { return nIndex >= mnStart && ( bInclEnd ? nIndex <= mnEnd : nIndex < mnEnd ); }
+
+ void SetStart( sal_Int32 n ) { mnStart = n; }
+ sal_Int32 GetStart() const { return mnStart; }
+
+ void SetEnd( sal_Int32 n ) { mnEnd = n; }
+ sal_Int32 GetEnd() const { return mnEnd; }
+
+ void SetStartPortion( std::size_t n ) { mnStartPortion = n; }
+ std::size_t GetStartPortion() const { return mnStartPortion; }
+
+ void SetEndPortion( std::size_t n ) { mnEndPortion = n; }
+ std::size_t GetEndPortion() const { return mnEndPortion; }
+
+ sal_Int32 GetLen() const { return mnEnd - mnStart; }
+
+ bool IsInvalid() const { return mbInvalid; }
+ bool IsValid() const { return !mbInvalid; }
+ void SetInvalid() { mbInvalid = true; }
+ void SetValid() { mbInvalid = false; }
+
+ short GetStartX() const { return mnStartX; }
+ void SetStartX( short n ) { mnStartX = n; }
+
+ inline bool operator == ( const TextLine& rLine ) const;
+};
+
+inline bool TextLine::operator == ( const TextLine& rLine ) const
+{
+ return mnStart == rLine.mnStart &&
+ mnEnd == rLine.mnEnd &&
+ mnStartPortion == rLine.mnStartPortion &&
+ mnEndPortion == rLine.mnEndPortion;
+}
+
+class TEParaPortion
+{
+private:
+ TextNode* mpNode;
+
+ std::vector<TextLine> maLines;
+ TETextPortionList maTextPortions;
+ std::vector<TEWritingDirectionInfo> maWritingDirectionInfos;
+
+ sal_Int32 mnInvalidPosStart;
+ sal_Int32 mnInvalidDiff;
+
+ bool mbInvalid;
+ bool mbSimple; // only type linearly
+
+public:
+ TEParaPortion( TextNode* pNode );
+ ~TEParaPortion();
+
+ TEParaPortion( const TEParaPortion& ) = delete;
+ void operator=( const TEParaPortion& ) = delete;
+
+ bool IsInvalid() const { return mbInvalid; }
+ bool IsSimpleInvalid() const { return mbSimple; }
+ void SetNotSimpleInvalid() { mbSimple = false; }
+ void SetValid() { mbInvalid = false; mbSimple = true;}
+
+ void MarkInvalid( sal_Int32 nStart, sal_Int32 nDiff );
+ void MarkSelectionInvalid( sal_Int32 nStart );
+
+ sal_Int32 GetInvalidPosStart() const { return mnInvalidPosStart; }
+ sal_Int32 GetInvalidDiff() const { return mnInvalidDiff; }
+
+ TextNode* GetNode() const { return mpNode; }
+ std::vector<TextLine>& GetLines() { return maLines; }
+ TETextPortionList& GetTextPortions() { return maTextPortions; }
+ std::vector<TEWritingDirectionInfo>& GetWritingDirectionInfos() { return maWritingDirectionInfos; }
+
+ std::vector<TextLine>::size_type GetLineNumber( sal_Int32 nIndex, bool bInclEnd );
+ void CorrectValuesBehindLastFormattedLine( sal_uInt16 nLastFormattedLine );
+};
+
+class TEParaPortions
+{
+private:
+ std::vector<std::unique_ptr<TEParaPortion>> mvData;
+
+public:
+ TEParaPortions() : mvData() {}
+ ~TEParaPortions();
+
+ sal_uInt32 Count() const { return static_cast<sal_uInt32>(mvData.size()); }
+ TEParaPortion* GetObject( sal_uInt32 nIndex ) { return mvData[nIndex].get(); }
+ void Insert( TEParaPortion* pObject, sal_uInt32 nPos ) { mvData.emplace( mvData.begin()+nPos, pObject ); }
+ void Remove( sal_uInt32 nPos ) { mvData.erase( mvData.begin()+nPos ); }
+};
+
+class TextSelFunctionSet: public FunctionSet
+{
+private:
+ TextView* mpView;
+
+public:
+ TextSelFunctionSet( TextView* pView );
+
+ virtual void BeginDrag() override;
+
+ virtual void CreateAnchor() override;
+
+ virtual void SetCursorAtPoint( const Point& rPointPixel, bool bDontSelectAtCursor = false ) override;
+
+ virtual bool IsSelectionAtPoint( const Point& rPointPixel ) override;
+ virtual void DeselectAll() override;
+
+ virtual void DeselectAtPoint( const Point& ) override;
+ virtual void DestroyAnchor() override;
+};
+
+class IdleFormatter : public Idle
+{
+private:
+ TextView* mpView;
+ sal_uInt16 mnRestarts;
+
+public:
+ IdleFormatter();
+ virtual ~IdleFormatter() override;
+
+ void DoIdleFormat( TextView* pV, sal_uInt16 nMaxRestarts );
+ void ForceTimeout();
+ TextView* GetView() { return mpView; }
+};
+
+struct TextDDInfo
+{
+ vcl::Cursor maCursor;
+ TextPaM maDropPos;
+
+ bool mbStarterOfDD;
+ bool mbVisCursor;
+
+ TextDDInfo()
+ : maCursor()
+ , maDropPos()
+ , mbStarterOfDD {false}
+ , mbVisCursor {false}
+ {
+ maCursor.SetStyle( CURSOR_SHADOW );
+ }
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/edit/textdata.cxx b/vcl/source/edit/textdata.cxx
new file mode 100644
index 0000000000..bf29c1443d
--- /dev/null
+++ b/vcl/source/edit/textdata.cxx
@@ -0,0 +1,345 @@
+/* -*- 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 <sal/config.h>
+#include <sal/log.hxx>
+#include <osl/diagnose.h>
+
+#include <cstddef>
+
+#include <utility>
+#include <vcl/textdata.hxx>
+#include "textdat2.hxx"
+
+
+TextSelection::TextSelection()
+{
+}
+
+TextSelection::TextSelection( const TextPaM& rPaM ) :
+ maStartPaM( rPaM ), maEndPaM( rPaM )
+{
+}
+
+TextSelection::TextSelection( const TextPaM& rStart, const TextPaM& rEnd ) :
+ maStartPaM( rStart ), maEndPaM( rEnd )
+{
+}
+
+void TextSelection::Justify()
+{
+ if ( maEndPaM < maStartPaM )
+ {
+ TextPaM aTemp( maStartPaM );
+ maStartPaM = maEndPaM;
+ maEndPaM = aTemp;
+ }
+}
+
+TETextPortionList::TETextPortionList()
+{
+}
+
+TETextPortionList::~TETextPortionList()
+{
+ Reset();
+}
+
+TETextPortion& TETextPortionList::operator[]( std::size_t nPos )
+{
+ return maPortions[ nPos ];
+}
+
+std::vector<TETextPortion>::iterator TETextPortionList::begin()
+{
+ return maPortions.begin();
+}
+
+std::vector<TETextPortion>::const_iterator TETextPortionList::begin() const
+{
+ return maPortions.begin();
+}
+
+std::vector<TETextPortion>::iterator TETextPortionList::end()
+{
+ return maPortions.end();
+}
+
+std::vector<TETextPortion>::const_iterator TETextPortionList::end() const
+{
+ return maPortions.end();
+}
+
+bool TETextPortionList::empty() const
+{
+ return maPortions.empty();
+}
+
+std::size_t TETextPortionList::size() const
+{
+ return maPortions.size();
+}
+
+std::vector<TETextPortion>::iterator TETextPortionList::erase( const std::vector<TETextPortion>::iterator& aIter )
+{
+ return maPortions.erase( aIter );
+}
+
+std::vector<TETextPortion>::iterator TETextPortionList::insert( const std::vector<TETextPortion>::iterator& aIter,
+ const TETextPortion& rTP )
+{
+ return maPortions.insert( aIter, rTP );
+}
+
+void TETextPortionList::push_back( const TETextPortion& rTP )
+{
+ maPortions.push_back( rTP );
+}
+
+void TETextPortionList::Reset()
+{
+ maPortions.clear();
+}
+
+void TETextPortionList::DeleteFromPortion( std::size_t nDelFrom )
+{
+ SAL_WARN_IF( ( nDelFrom >= maPortions.size() ) && ( (nDelFrom != 0) || (!maPortions.empty()) ), "vcl", "DeleteFromPortion: Out of range" );
+ maPortions.erase( maPortions.begin() + nDelFrom, maPortions.end() );
+}
+
+std::size_t TETextPortionList::FindPortion( sal_Int32 nCharPos, sal_Int32& nPortionStart, bool bPreferStartingPortion )
+{
+ // find left portion at nCharPos at portion border
+ sal_Int32 nTmpPos = 0;
+ for ( std::size_t nPortion = 0; nPortion < maPortions.size(); nPortion++ )
+ {
+ TETextPortion& rPortion = maPortions[ nPortion ];
+ nTmpPos += rPortion.GetLen();
+ if ( nTmpPos >= nCharPos )
+ {
+ // take this one if we don't prefer the starting portion, or if it's the last one
+ if ( ( nTmpPos != nCharPos ) || !bPreferStartingPortion || ( nPortion == maPortions.size() - 1 ) )
+ {
+ nPortionStart = nTmpPos - rPortion.GetLen();
+ return nPortion;
+ }
+ }
+ }
+ OSL_FAIL( "FindPortion: Not found!" );
+ return ( maPortions.size() - 1 );
+}
+
+TEParaPortion::TEParaPortion( TextNode* pN )
+ : mpNode {pN}
+ , mnInvalidPosStart {0}
+ , mnInvalidDiff {0}
+ , mbInvalid {true}
+ , mbSimple {false}
+{
+}
+
+TEParaPortion::~TEParaPortion()
+{
+}
+
+void TEParaPortion::MarkInvalid( sal_Int32 nStart, sal_Int32 nDiff )
+{
+ if ( !mbInvalid )
+ {
+ mnInvalidPosStart = ( nDiff >= 0 ) ? nStart : ( nStart + nDiff );
+ mnInvalidDiff = nDiff;
+ }
+ else
+ {
+ // simple consecutive typing
+ if ( ( nDiff > 0 ) && ( mnInvalidDiff > 0 ) &&
+ ( ( mnInvalidPosStart+mnInvalidDiff ) == nStart ) )
+ {
+ mnInvalidDiff = mnInvalidDiff + nDiff;
+ }
+ // simple consecutive deleting
+ else if ( ( nDiff < 0 ) && ( mnInvalidDiff < 0 ) && ( mnInvalidPosStart == nStart ) )
+ {
+ mnInvalidPosStart = mnInvalidPosStart + nDiff;
+ mnInvalidDiff = mnInvalidDiff + nDiff;
+ }
+ else
+ {
+ SAL_WARN_IF( ( nDiff < 0 ) && ( (nStart+nDiff) < 0 ), "vcl", "MarkInvalid: Diff out of Range" );
+ mnInvalidPosStart = std::min( mnInvalidPosStart, nDiff < 0 ? nStart+nDiff : nDiff );
+ mnInvalidDiff = 0;
+ mbSimple = false;
+ }
+ }
+
+ maWritingDirectionInfos.clear();
+
+ mbInvalid = true;
+}
+
+void TEParaPortion::MarkSelectionInvalid( sal_Int32 nStart )
+{
+ if ( !mbInvalid )
+ {
+ mnInvalidPosStart = nStart;
+ }
+ else
+ {
+ mnInvalidPosStart = std::min( mnInvalidPosStart, nStart );
+ }
+
+ maWritingDirectionInfos.clear();
+
+ mnInvalidDiff = 0;
+ mbInvalid = true;
+ mbSimple = false;
+}
+
+std::vector<TextLine>::size_type TEParaPortion::GetLineNumber( sal_Int32 nChar, bool bInclEnd )
+{
+ for ( std::vector<TextLine>::size_type nLine = 0; nLine < maLines.size(); nLine++ )
+ {
+ TextLine& rLine = maLines[ nLine ];
+ if ( ( bInclEnd && ( rLine.GetEnd() >= nChar ) ) ||
+ ( rLine.GetEnd() > nChar ) )
+ {
+ return nLine;
+ }
+ }
+
+ // Then it should be at the end of the last line
+ OSL_ENSURE(nChar == maLines.back().GetEnd(), "wrong Index");
+ OSL_ENSURE(!bInclEnd, "Line not found: FindLine");
+ return ( maLines.size() - 1 );
+}
+
+void TEParaPortion::CorrectValuesBehindLastFormattedLine( sal_uInt16 nLastFormattedLine )
+{
+ sal_uInt16 nLines = maLines.size();
+ SAL_WARN_IF( !nLines, "vcl", "CorrectPortionNumbersFromLine: Empty portion?" );
+ if ( nLastFormattedLine >= ( nLines - 1 ) )
+ return;
+
+ const TextLine& rLastFormatted = maLines[ nLastFormattedLine ];
+ const TextLine& rUnformatted = maLines[ nLastFormattedLine+1 ];
+ std::ptrdiff_t nPortionDiff = rUnformatted.GetStartPortion() - rLastFormatted.GetEndPortion();
+ sal_Int32 nTextDiff = rUnformatted.GetStart() - rLastFormatted.GetEnd();
+ nTextDiff++; // LastFormatted.GetEnd() was inclusive => subtracted one too much!
+
+ // The first unformatted one has to start exactly one portion past the last
+ // formatted one.
+ // If a portion got split in the changed row, nLastEnd could be > nNextStart!
+ std::ptrdiff_t nPDiff = -( nPortionDiff-1 );
+ const sal_Int32 nTDiff = -( nTextDiff-1 );
+ if ( !(nPDiff || nTDiff) )
+ return;
+
+ for ( sal_uInt16 nL = nLastFormattedLine+1; nL < nLines; nL++ )
+ {
+ TextLine& rLine = maLines[ nL ];
+
+ rLine.SetStartPortion(rLine.GetStartPortion() + nPDiff);
+ rLine.SetEndPortion(rLine.GetEndPortion() + nPDiff);
+
+ rLine.SetStart(rLine.GetStart() + nTDiff);
+ rLine.SetEnd(rLine.GetEnd() + nTDiff);
+
+ rLine.SetValid();
+ }
+}
+
+TEParaPortions::~TEParaPortions()
+{
+}
+
+IdleFormatter::IdleFormatter()
+ : Idle("vcl::TextEngine mpIdleFormatter")
+ , mpView(nullptr)
+ , mnRestarts(0)
+{
+ SetPriority(TaskPriority::HIGH_IDLE);
+}
+
+IdleFormatter::~IdleFormatter()
+{
+ mpView = nullptr;
+}
+
+void IdleFormatter::DoIdleFormat( TextView* pV, sal_uInt16 nMaxRestarts )
+{
+ mpView = pV;
+
+ if ( IsActive() )
+ mnRestarts++;
+
+ if ( mnRestarts > nMaxRestarts )
+ {
+ mnRestarts = 0;
+ Invoke();
+ }
+ else
+ {
+ Start();
+ }
+}
+
+void IdleFormatter::ForceTimeout()
+{
+ if ( IsActive() )
+ {
+ Stop();
+ mnRestarts = 0;
+ Invoke();
+ }
+}
+
+TextHint::TextHint( SfxHintId Id ) : SfxHint( Id ), mnValue(0)
+{
+}
+
+TextHint::TextHint( SfxHintId Id, sal_Int32 nValue ) : SfxHint( Id ), mnValue(nValue)
+{
+}
+
+TEIMEInfos::TEIMEInfos( const TextPaM& rPos, OUString _aOldTextAfterStartPos )
+ : aOldTextAfterStartPos(std::move(_aOldTextAfterStartPos))
+ , aPos(rPos)
+ , nLen(0)
+ , bWasCursorOverwrite(false)
+{
+}
+
+TEIMEInfos::~TEIMEInfos()
+{
+}
+
+void TEIMEInfos::CopyAttribs(const ExtTextInputAttr* pA, sal_Int32 nL)
+{
+ nLen = nL;
+ pAttribs.reset( new ExtTextInputAttr[ nL ] );
+ memcpy( pAttribs.get(), pA, nL*sizeof(ExtTextInputAttr) );
+}
+
+void TEIMEInfos::DestroyAttribs()
+{
+ pAttribs.reset();
+ nLen = 0;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/edit/textdoc.cxx b/vcl/source/edit/textdoc.cxx
new file mode 100644
index 0000000000..a6d7bd8c24
--- /dev/null
+++ b/vcl/source/edit/textdoc.cxx
@@ -0,0 +1,536 @@
+/* -*- 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 "textdoc.hxx"
+#include <osl/diagnose.h>
+#include <sal/log.hxx>
+#include <rtl/ustrbuf.hxx>
+#include <utility>
+
+// compare function called by QuickSort
+static bool CompareStart( const std::unique_ptr<TextCharAttrib>& pFirst, const std::unique_ptr<TextCharAttrib>& pSecond )
+{
+ return pFirst->GetStart() < pSecond->GetStart();
+}
+
+TextCharAttrib::TextCharAttrib( const TextAttrib& rAttr, sal_Int32 nStart, sal_Int32 nEnd )
+ : mpAttr(rAttr.Clone())
+ , mnStart(nStart)
+ , mnEnd(nEnd)
+{
+}
+
+TextCharAttrib::TextCharAttrib( const TextCharAttrib& rTextCharAttrib )
+ : mpAttr(rTextCharAttrib.mpAttr->Clone())
+ , mnStart(rTextCharAttrib.mnStart)
+ , mnEnd(rTextCharAttrib.mnEnd)
+{
+}
+
+TextCharAttribList::TextCharAttribList()
+ : mbHasEmptyAttribs(false)
+{
+}
+
+TextCharAttribList::~TextCharAttribList()
+{
+ // PTRARR_DEL
+}
+
+void TextCharAttribList::Clear()
+{
+ maAttribs.clear();
+}
+
+void TextCharAttribList::InsertAttrib( std::unique_ptr<TextCharAttrib> pAttrib )
+{
+ if ( pAttrib->IsEmpty() )
+ mbHasEmptyAttribs = true;
+
+ const sal_Int32 nStart = pAttrib->GetStart(); // maybe better for Comp.Opt.
+ bool bInserted = false;
+ auto it = std::find_if(maAttribs.begin(), maAttribs.end(),
+ [nStart](std::unique_ptr<TextCharAttrib>& rAttrib) { return rAttrib->GetStart() > nStart; });
+ if (it != maAttribs.end())
+ {
+ maAttribs.insert( it, std::move(pAttrib) );
+ bInserted = true;
+ }
+ if ( !bInserted )
+ maAttribs.push_back( std::move(pAttrib) );
+}
+
+void TextCharAttribList::ResortAttribs()
+{
+ std::sort( maAttribs.begin(), maAttribs.end(), CompareStart );
+}
+
+TextCharAttrib* TextCharAttribList::FindAttrib( sal_uInt16 nWhich, sal_Int32 nPos )
+{
+ for (std::vector<std::unique_ptr<TextCharAttrib> >::reverse_iterator it = maAttribs.rbegin(); it != maAttribs.rend(); ++it)
+ {
+ if ( (*it)->GetEnd() < nPos )
+ return nullptr;
+
+ if ( ( (*it)->Which() == nWhich ) && (*it)->IsIn(nPos) )
+ return it->get();
+ }
+ return nullptr;
+}
+
+bool TextCharAttribList::HasBoundingAttrib( sal_Int32 nBound )
+{
+ for (std::vector<std::unique_ptr<TextCharAttrib> >::reverse_iterator it = maAttribs.rbegin(); it != maAttribs.rend(); ++it)
+ {
+ if ( (*it)->GetEnd() < nBound )
+ return false;
+
+ if ( ( (*it)->GetStart() == nBound ) || ( (*it)->GetEnd() == nBound ) )
+ return true;
+ }
+ return false;
+}
+
+TextCharAttrib* TextCharAttribList::FindEmptyAttrib( sal_uInt16 nWhich, sal_Int32 nPos )
+{
+ if ( !mbHasEmptyAttribs )
+ return nullptr;
+
+ for (auto const& attrib : maAttribs)
+ {
+ if ( attrib->GetStart() > nPos )
+ return nullptr;
+
+ if ( ( attrib->GetStart() == nPos ) && ( attrib->GetEnd() == nPos ) && ( attrib->Which() == nWhich ) )
+ return attrib.get();
+ }
+ return nullptr;
+}
+
+void TextCharAttribList::DeleteEmptyAttribs()
+{
+ std::erase_if(
+ maAttribs,
+ [] (const std::unique_ptr<TextCharAttrib>& rAttrib) { return rAttrib->IsEmpty(); } );
+ mbHasEmptyAttribs = false;
+}
+
+TextNode::TextNode( OUString aText ) :
+ maText(std::move( aText ))
+{
+}
+
+void TextNode::ExpandAttribs( sal_Int32 nIndex, sal_Int32 nNew )
+{
+ if ( !nNew )
+ return;
+
+ bool bResort = false;
+ sal_uInt16 nAttribs = maCharAttribs.Count();
+ for ( sal_uInt16 nAttr = 0; nAttr < nAttribs; nAttr++ )
+ {
+ TextCharAttrib& rAttrib = maCharAttribs.GetAttrib( nAttr );
+ if ( rAttrib.GetEnd() >= nIndex )
+ {
+ // move all attributes that are behind the cursor
+ if ( rAttrib.GetStart() > nIndex )
+ {
+ rAttrib.MoveForward( nNew );
+ }
+ // 0: expand empty attribute, if at cursor
+ else if ( rAttrib.IsEmpty() )
+ {
+ // Do not check the index; empty one may only be here.
+ // If checking later anyway, special case:
+ // Start == 0; AbsLen == 1, nNew = 1 => Expand due to new paragraph!
+ // Start <= nIndex, End >= nIndex => Start=End=nIndex!
+ rAttrib.Expand( nNew );
+ }
+ // 1: attribute starts before and reaches up to index
+ else if ( rAttrib.GetEnd() == nIndex ) // start must be before
+ {
+ // Only expand if no feature and not in Exclude list!
+ // Otherwise e.g. an UL would go until the new ULDB, thus expand both.
+ if ( !maCharAttribs.FindEmptyAttrib( rAttrib.Which(), nIndex ) )
+ {
+ rAttrib.Expand( nNew );
+ }
+ else
+ bResort = true;
+ }
+ // 2: attribute starts before and reaches past the index
+ else if ( ( rAttrib.GetStart() < nIndex ) && ( rAttrib.GetEnd() > nIndex ) )
+ {
+ rAttrib.Expand( nNew );
+ }
+ // 3: attribute starts at Index
+ else if ( rAttrib.GetStart() == nIndex )
+ {
+ if ( nIndex == 0 )
+ {
+ rAttrib.Expand( nNew );
+ }
+ else
+ rAttrib.MoveForward( nNew );
+ }
+ }
+
+ SAL_WARN_IF( rAttrib.GetStart() > rAttrib.GetEnd(), "vcl", "Expand: attribute twisted!" );
+ SAL_WARN_IF( ( rAttrib.GetEnd() > maText.getLength() ), "vcl", "Expand: attribute greater than paragraph!" );
+ SAL_WARN_IF( rAttrib.IsEmpty(), "vcl", "Empty attribute after ExpandAttribs?" );
+ }
+
+ if ( bResort )
+ maCharAttribs.ResortAttribs();
+}
+
+void TextNode::CollapseAttribs( sal_Int32 nIndex, sal_Int32 nDeleted )
+{
+ if ( !nDeleted )
+ return;
+
+ bool bResort = false;
+ const sal_Int32 nEndChanges = nIndex+nDeleted;
+
+ for ( sal_uInt16 nAttr = 0; nAttr < maCharAttribs.Count(); nAttr++ )
+ {
+ TextCharAttrib& rAttrib = maCharAttribs.GetAttrib( nAttr );
+ bool bDelAttr = false;
+ if ( rAttrib.GetEnd() >= nIndex )
+ {
+ // move all attributes that are behind the cursor
+ if ( rAttrib.GetStart() >= nEndChanges )
+ {
+ rAttrib.MoveBackward( nDeleted );
+ }
+ // 1. delete inner attributes
+ else if ( ( rAttrib.GetStart() >= nIndex ) && ( rAttrib.GetEnd() <= nEndChanges ) )
+ {
+ // special case: attribute covers the region exactly
+ // => keep as an empty attribute
+ if ( ( rAttrib.GetStart() == nIndex ) && ( rAttrib.GetEnd() == nEndChanges ) )
+ rAttrib.SetEnd(nIndex); // empty
+ else
+ bDelAttr = true;
+ }
+ // 2. attribute starts before, ends inside or after
+ else if ( ( rAttrib.GetStart() <= nIndex ) && ( rAttrib.GetEnd() > nIndex ) )
+ {
+ if ( rAttrib.GetEnd() <= nEndChanges ) // ends inside
+ rAttrib.SetEnd(nIndex);
+ else
+ rAttrib.Collaps( nDeleted ); // ends after
+ }
+ // 3. attribute starts inside, ends after
+ else if ( ( rAttrib.GetStart() >= nIndex ) && ( rAttrib.GetEnd() > nEndChanges ) )
+ {
+ // features are not allowed to expand!
+ rAttrib.SetStart(nEndChanges);
+ rAttrib.MoveBackward( nDeleted );
+ }
+ }
+
+ SAL_WARN_IF( rAttrib.GetStart() > rAttrib.GetEnd(), "vcl", "Collaps: attribute twisted!" );
+ SAL_WARN_IF( ( rAttrib.GetEnd() > maText.getLength()) && !bDelAttr, "vcl", "Collaps: attribute greater than paragraph!" );
+ if ( bDelAttr /* || rAttrib.IsEmpty() */ )
+ {
+ bResort = true;
+ maCharAttribs.RemoveAttrib( nAttr );
+ nAttr--;
+ }
+ else if ( rAttrib.IsEmpty() )
+ maCharAttribs.HasEmptyAttribs() = true;
+ }
+
+ if ( bResort )
+ maCharAttribs.ResortAttribs();
+}
+
+void TextNode::InsertText( sal_Int32 nPos, std::u16string_view rText )
+{
+ maText = maText.replaceAt( nPos, 0, rText );
+ ExpandAttribs( nPos, rText.size() );
+}
+
+void TextNode::InsertText( sal_Int32 nPos, sal_Unicode c )
+{
+ maText = maText.replaceAt( nPos, 0, rtl::OUStringChar(c) );
+ ExpandAttribs( nPos, 1 );
+}
+
+void TextNode::RemoveText( sal_Int32 nPos, sal_Int32 nChars )
+{
+ maText = maText.replaceAt( nPos, nChars, u"" );
+ CollapseAttribs( nPos, nChars );
+}
+
+std::unique_ptr<TextNode> TextNode::Split( sal_Int32 nPos )
+{
+ OUString aNewText;
+ if ( nPos < maText.getLength() )
+ {
+ aNewText = maText.copy( nPos );
+ maText = maText.copy(0, nPos);
+ }
+ std::unique_ptr<TextNode> pNew(new TextNode( aNewText ));
+
+ for ( sal_uInt16 nAttr = 0; nAttr < maCharAttribs.Count(); nAttr++ )
+ {
+ TextCharAttrib& rAttrib = maCharAttribs.GetAttrib( nAttr );
+ if ( rAttrib.GetEnd() < nPos )
+ {
+ // no change
+ ;
+ }
+ else if ( rAttrib.GetEnd() == nPos )
+ {
+ // must be copied as an empty attribute
+ // !FindAttrib only sensible if traversing backwards through the list!
+ if ( !pNew->maCharAttribs.FindAttrib( rAttrib.Which(), 0 ) )
+ {
+ std::unique_ptr<TextCharAttrib> pNewAttrib(new TextCharAttrib( rAttrib ));
+ pNewAttrib->SetStart(0);
+ pNewAttrib->SetEnd(0);
+ pNew->maCharAttribs.InsertAttrib( std::move(pNewAttrib) );
+ }
+ }
+ else if ( rAttrib.IsInside( nPos ) || ( !nPos && !rAttrib.GetStart() ) )
+ {
+ // If cutting at the very beginning, the attribute has to be
+ // copied and changed
+ std::unique_ptr<TextCharAttrib> pNewAttrib(new TextCharAttrib( rAttrib ));
+ pNewAttrib->SetStart(0);
+ pNewAttrib->SetEnd(rAttrib.GetEnd()-nPos);
+ pNew->maCharAttribs.InsertAttrib( std::move(pNewAttrib) );
+ // trim
+ rAttrib.SetEnd(nPos);
+ }
+ else
+ {
+ SAL_WARN_IF( rAttrib.GetStart() < nPos, "vcl", "Start < nPos!" );
+ SAL_WARN_IF( rAttrib.GetEnd() < nPos, "vcl", "End < nPos!" );
+ // move all into the new node (this)
+ pNew->maCharAttribs.InsertAttrib(maCharAttribs.RemoveAttrib(nAttr));
+ rAttrib.SetStart( rAttrib.GetStart() - nPos );
+ rAttrib.SetEnd( rAttrib.GetEnd() - nPos );
+ nAttr--;
+ }
+ }
+ return pNew;
+}
+
+void TextNode::Append( const TextNode& rNode )
+{
+ sal_Int32 nOldLen = maText.getLength();
+
+ maText += rNode.GetText();
+
+ const sal_uInt16 nAttribs = rNode.GetCharAttribs().Count();
+ for ( sal_uInt16 nAttr = 0; nAttr < nAttribs; nAttr++ )
+ {
+ const TextCharAttrib& rAttrib = rNode.GetCharAttrib( nAttr );
+ bool bMelted = false;
+ if ( rAttrib.GetStart() == 0 )
+ {
+ // potentially merge attributes
+ sal_uInt16 nTmpAttribs = maCharAttribs.Count();
+ for ( sal_uInt16 nTmpAttr = 0; nTmpAttr < nTmpAttribs; nTmpAttr++ )
+ {
+ TextCharAttrib& rTmpAttrib = maCharAttribs.GetAttrib( nTmpAttr );
+
+ if ( rTmpAttrib.GetEnd() == nOldLen )
+ {
+ if ( ( rTmpAttrib.Which() == rAttrib.Which() ) &&
+ ( rTmpAttrib.GetAttr() == rAttrib.GetAttr() ) )
+ {
+ rTmpAttrib.SetEnd( rTmpAttrib.GetEnd() + rAttrib.GetLen() );
+ bMelted = true;
+ break; // there can be only one of this type at this position
+ }
+ }
+ }
+ }
+
+ if ( !bMelted )
+ {
+ std::unique_ptr<TextCharAttrib> pNewAttrib(new TextCharAttrib( rAttrib ));
+ pNewAttrib->SetStart( pNewAttrib->GetStart() + nOldLen );
+ pNewAttrib->SetEnd( pNewAttrib->GetEnd() + nOldLen );
+ maCharAttribs.InsertAttrib( std::move(pNewAttrib) );
+ }
+ }
+}
+
+TextDoc::TextDoc()
+ : mnLeftMargin(0)
+{
+};
+
+TextDoc::~TextDoc()
+{
+ DestroyTextNodes();
+}
+
+void TextDoc::Clear()
+{
+ DestroyTextNodes();
+}
+
+void TextDoc::DestroyTextNodes()
+{
+ maTextNodes.clear();
+}
+
+OUString TextDoc::GetText( const sal_Unicode* pSep ) const
+{
+ sal_uInt32 nNodes = static_cast<sal_uInt32>(maTextNodes.size());
+
+ OUStringBuffer aASCIIText;
+ const sal_uInt32 nLastNode = nNodes-1;
+ for ( sal_uInt32 nNode = 0; nNode < nNodes; ++nNode )
+ {
+ TextNode* pNode = maTextNodes[ nNode ].get();
+ aASCIIText.append(pNode->GetText());
+ if ( pSep && ( nNode != nLastNode ) )
+ aASCIIText.append(pSep);
+ }
+
+ return aASCIIText.makeStringAndClear();
+}
+
+OUString TextDoc::GetText( sal_uInt32 nPara ) const
+{
+ TextNode* pNode = ( nPara < maTextNodes.size() ) ? maTextNodes[ nPara ].get() : nullptr;
+ if ( pNode )
+ return pNode->GetText();
+
+ return OUString();
+}
+
+sal_Int32 TextDoc::GetTextLen( const sal_Unicode* pSep, const TextSelection* pSel ) const
+{
+ sal_Int32 nLen = 0;
+ sal_uInt32 nNodes = static_cast<sal_uInt32>(maTextNodes.size());
+ if ( nNodes )
+ {
+ sal_uInt32 nStartNode = 0;
+ sal_uInt32 nEndNode = nNodes-1;
+ if ( pSel )
+ {
+ nStartNode = pSel->GetStart().GetPara();
+ nEndNode = pSel->GetEnd().GetPara();
+ }
+
+ for ( sal_uInt32 nNode = nStartNode; nNode <= nEndNode; ++nNode )
+ {
+ TextNode* pNode = maTextNodes[ nNode ].get();
+
+ sal_Int32 nS = 0;
+ sal_Int32 nE = pNode->GetText().getLength();
+ if ( pSel && ( nNode == pSel->GetStart().GetPara() ) )
+ nS = pSel->GetStart().GetIndex();
+ if ( pSel && ( nNode == pSel->GetEnd().GetPara() ) )
+ nE = pSel->GetEnd().GetIndex();
+
+ nLen += ( nE - nS );
+ }
+
+ if ( pSep )
+ nLen += (nEndNode-nStartNode) * rtl_ustr_getLength(pSep);
+ }
+
+ return nLen;
+}
+
+TextPaM TextDoc::InsertText( const TextPaM& rPaM, sal_Unicode c )
+{
+ SAL_WARN_IF( c == 0x0A, "vcl", "TextDoc::InsertText: Line separator in paragraph not allowed!" );
+ SAL_WARN_IF( c == 0x0D, "vcl", "TextDoc::InsertText: Line separator in paragraph not allowed!" );
+
+ TextNode* pNode = maTextNodes[ rPaM.GetPara() ].get();
+ pNode->InsertText( rPaM.GetIndex(), c );
+
+ TextPaM aPaM( rPaM.GetPara(), rPaM.GetIndex()+1 );
+ return aPaM;
+}
+
+TextPaM TextDoc::InsertText( const TextPaM& rPaM, std::u16string_view rStr )
+{
+ SAL_WARN_IF( rStr.find( 0x0A ) != std::u16string_view::npos, "vcl", "TextDoc::InsertText: Line separator in paragraph not allowed!" );
+ SAL_WARN_IF( rStr.find( 0x0D ) != std::u16string_view::npos, "vcl", "TextDoc::InsertText: Line separator in paragraph not allowed!" );
+
+ TextNode* pNode = maTextNodes[ rPaM.GetPara() ].get();
+ pNode->InsertText( rPaM.GetIndex(), rStr );
+
+ TextPaM aPaM( rPaM.GetPara(), rPaM.GetIndex()+rStr.size() );
+ return aPaM;
+}
+
+TextPaM TextDoc::InsertParaBreak( const TextPaM& rPaM )
+{
+ TextNode* pNode = maTextNodes[ rPaM.GetPara() ].get();
+ std::unique_ptr<TextNode> pNew = pNode->Split( rPaM.GetIndex() );
+
+ SAL_WARN_IF( maTextNodes.size()>=SAL_MAX_UINT32, "vcl", "InsertParaBreak: more than 4Gi paragraphs!" );
+ maTextNodes.insert( maTextNodes.begin() + rPaM.GetPara() + 1, std::move(pNew) );
+
+ TextPaM aPaM( rPaM.GetPara()+1, 0 );
+ return aPaM;
+}
+
+TextPaM TextDoc::ConnectParagraphs( TextNode* pLeft, const TextNode* pRight )
+{
+ sal_Int32 nPrevLen = pLeft->GetText().getLength();
+ pLeft->Append( *pRight );
+
+ // the paragraph on the right vanishes
+ maTextNodes.erase( std::find_if( maTextNodes.begin(), maTextNodes.end(),
+ [&] (std::unique_ptr<TextNode> const & p) { return p.get() == pRight; } ) );
+
+ sal_Int32 nLeft = ::std::find_if( maTextNodes.begin(), maTextNodes.end(),
+ [&] (std::unique_ptr<TextNode> const & p) { return p.get() == pLeft; } )
+ - maTextNodes.begin();
+ TextPaM aPaM( nLeft, nPrevLen );
+ return aPaM;
+}
+
+void TextDoc::RemoveChars( const TextPaM& rPaM, sal_Int32 nChars )
+{
+ TextNode* pNode = maTextNodes[ rPaM.GetPara() ].get();
+ pNode->RemoveText( rPaM.GetIndex(), nChars );
+}
+
+bool TextDoc::IsValidPaM( const TextPaM& rPaM )
+{
+ if ( rPaM.GetPara() >= maTextNodes.size() )
+ {
+ OSL_FAIL( "PaM: Para out of range" );
+ return false;
+ }
+ TextNode * pNode = maTextNodes[ rPaM.GetPara() ].get();
+ if ( rPaM.GetIndex() > pNode->GetText().getLength() )
+ {
+ OSL_FAIL( "PaM: Index out of range" );
+ return false;
+ }
+ return true;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/edit/textdoc.hxx b/vcl/source/edit/textdoc.hxx
new file mode 100644
index 0000000000..3dd0dce8d2
--- /dev/null
+++ b/vcl/source/edit/textdoc.hxx
@@ -0,0 +1,126 @@
+/* -*- 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 .
+ */
+
+#pragma once
+
+#include <rtl/ustring.hxx>
+#include <vcl/textdata.hxx>
+#include <vcl/txtattr.hxx>
+#include <vector>
+#include <memory>
+
+class TextCharAttribList
+{
+private:
+ TextCharAttribList(const TextCharAttribList&) = delete;
+ TextCharAttribList& operator=(const TextCharAttribList&) = delete;
+
+ std::vector<std::unique_ptr<TextCharAttrib> > maAttribs;
+ bool mbHasEmptyAttribs;
+
+public:
+ TextCharAttribList();
+ ~TextCharAttribList();
+
+ void Clear();
+ sal_uInt16 Count() const { return maAttribs.size(); }
+
+ const TextCharAttrib& GetAttrib( sal_uInt16 n ) const { return *maAttribs[n]; }
+ TextCharAttrib& GetAttrib( sal_uInt16 n ) { return *maAttribs[n]; }
+ std::unique_ptr<TextCharAttrib> RemoveAttrib( sal_uInt16 n )
+ {
+ std::unique_ptr<TextCharAttrib> pReleased = std::move(maAttribs[n]);
+ maAttribs.erase( maAttribs.begin() + n );
+ return pReleased;
+ }
+
+ void InsertAttrib( std::unique_ptr<TextCharAttrib> pAttrib );
+
+ void DeleteEmptyAttribs();
+ void ResortAttribs();
+
+ bool& HasEmptyAttribs() { return mbHasEmptyAttribs; }
+
+ TextCharAttrib* FindAttrib( sal_uInt16 nWhich, sal_Int32 nPos );
+ TextCharAttrib* FindEmptyAttrib( sal_uInt16 nWhich, sal_Int32 nPos );
+ bool HasBoundingAttrib( sal_Int32 nBound );
+};
+
+class TextNode
+{
+ OUString maText;
+ TextCharAttribList maCharAttribs;
+
+ void ExpandAttribs( sal_Int32 nIndex, sal_Int32 nNewChars );
+ void CollapseAttribs( sal_Int32 nIndex, sal_Int32 nDelChars );
+
+public:
+ TextNode( OUString aText );
+
+ TextNode( const TextNode& ) = delete;
+ void operator=( const TextNode& ) = delete;
+
+ const OUString& GetText() const { return maText; }
+
+ const TextCharAttrib& GetCharAttrib(sal_uInt16 nPos) const { return maCharAttribs.GetAttrib(nPos); }
+ const TextCharAttribList& GetCharAttribs() const { return maCharAttribs; }
+ TextCharAttribList& GetCharAttribs() { return maCharAttribs; }
+
+ void InsertText( sal_Int32 nPos, std::u16string_view rText );
+ void InsertText( sal_Int32 nPos, sal_Unicode c );
+ void RemoveText( sal_Int32 nPos, sal_Int32 nChars );
+
+ std::unique_ptr<TextNode> Split( sal_Int32 nPos );
+ void Append( const TextNode& rNode );
+};
+
+class TextDoc
+{
+ std::vector<std::unique_ptr<TextNode>> maTextNodes;
+ sal_uInt16 mnLeftMargin;
+
+ void DestroyTextNodes();
+
+public:
+ TextDoc();
+ ~TextDoc();
+
+ void Clear();
+
+ std::vector<std::unique_ptr<TextNode>>& GetNodes() { return maTextNodes; }
+ const std::vector<std::unique_ptr<TextNode>>& GetNodes() const { return maTextNodes; }
+
+ void RemoveChars( const TextPaM& rPaM, sal_Int32 nChars );
+ TextPaM InsertText( const TextPaM& rPaM, sal_Unicode c );
+ TextPaM InsertText( const TextPaM& rPaM, std::u16string_view rStr );
+
+ TextPaM InsertParaBreak( const TextPaM& rPaM );
+ TextPaM ConnectParagraphs( TextNode* pLeft, const TextNode* pRight );
+
+ sal_Int32 GetTextLen( const sal_Unicode* pSep, const TextSelection* pSel = nullptr ) const;
+ OUString GetText( const sal_Unicode* pSep ) const;
+ OUString GetText( sal_uInt32 nPara ) const;
+
+ void SetLeftMargin( sal_uInt16 n ) { mnLeftMargin = n; }
+ sal_uInt16 GetLeftMargin() const { return mnLeftMargin; }
+
+ bool IsValidPaM( const TextPaM& rPaM );
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/edit/texteng.cxx b/vcl/source/edit/texteng.cxx
new file mode 100644
index 0000000000..6bf7eddc15
--- /dev/null
+++ b/vcl/source/edit/texteng.cxx
@@ -0,0 +1,2894 @@
+/* -*- 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 <tools/stream.hxx>
+
+#include <vcl/texteng.hxx>
+#include <vcl/textview.hxx>
+#include <vcl/commandevent.hxx>
+#include <vcl/inputctx.hxx>
+#include "textdoc.hxx"
+#include "textdat2.hxx"
+#include "textundo.hxx"
+#include "textund2.hxx"
+#include <svl/ctloptions.hxx>
+#include <vcl/window.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/toolkit/edit.hxx>
+#include <vcl/virdev.hxx>
+#include <sal/log.hxx>
+#include <osl/diagnose.h>
+
+#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/i18n/InputSequenceChecker.hpp>
+#include <com/sun/star/i18n/InputSequenceCheckMode.hpp>
+#include <com/sun/star/i18n/ScriptType.hpp>
+
+#include <comphelper/processfactory.hxx>
+
+#include <unotools/localedatawrapper.hxx>
+#include <vcl/unohelp.hxx>
+
+#include <vcl/svapp.hxx>
+
+#include <unicode/ubidi.h>
+
+#include <algorithm>
+#include <cstddef>
+#include <cstdlib>
+#include <memory>
+#include <o3tl/sorted_vector.hxx>
+#include <string_view>
+#include <vector>
+
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::uno;
+
+TextEngine::TextEngine()
+ : mpActiveView {nullptr}
+ , maTextColor {COL_BLACK}
+ , mnMaxTextLen {0}
+ , mnMaxTextWidth {0}
+ , mnCharHeight {0}
+ , mnCurTextWidth {-1}
+ , mnCurTextHeight {0}
+ , mnDefTab {0}
+ , meAlign {TxtAlign::Left}
+ , mbIsFormatting {false}
+ , mbFormatted {false}
+ , mbUpdate {true}
+ , mbModified {false}
+ , mbUndoEnabled {false}
+ , mbIsInUndo {false}
+ , mbDowning {false}
+ , mbRightToLeft {false}
+ , mbHasMultiLineParas {false}
+{
+ mpViews.reset( new TextViews );
+
+ mpIdleFormatter.reset( new IdleFormatter );
+ mpIdleFormatter->SetInvokeHandler( LINK( this, TextEngine, IdleFormatHdl ) );
+
+ mpRefDev = VclPtr<VirtualDevice>::Create();
+
+ ImpInitLayoutMode( mpRefDev );
+
+ ImpInitDoc();
+
+ vcl::Font aFont(mpRefDev->GetFont().GetFamilyName(), Size(0, 0));
+ aFont.SetTransparent( false );
+ Color aFillColor( aFont.GetFillColor() );
+ aFillColor.SetAlpha( 255 );
+ aFont.SetFillColor( aFillColor );
+ SetFont( aFont );
+}
+
+TextEngine::~TextEngine()
+{
+ mbDowning = true;
+
+ mpIdleFormatter.reset();
+ mpDoc.reset();
+ mpTEParaPortions.reset();
+ mpViews.reset(); // only the list, not the Views
+ mpRefDev.disposeAndClear();
+ mpUndoManager.reset();
+ mpIMEInfos.reset();
+ mpLocaleDataWrapper.reset();
+}
+
+void TextEngine::InsertView( TextView* pTextView )
+{
+ mpViews->push_back( pTextView );
+ pTextView->SetSelection( TextSelection() );
+
+ if ( !GetActiveView() )
+ SetActiveView( pTextView );
+}
+
+void TextEngine::RemoveView( TextView* pTextView )
+{
+ TextViews::iterator it = std::find( mpViews->begin(), mpViews->end(), pTextView );
+ if( it != mpViews->end() )
+ {
+ pTextView->HideCursor();
+ mpViews->erase( it );
+ if ( pTextView == GetActiveView() )
+ SetActiveView( nullptr );
+ }
+}
+
+sal_uInt16 TextEngine::GetViewCount() const
+{
+ return mpViews->size();
+}
+
+TextView* TextEngine::GetView( sal_uInt16 nView ) const
+{
+ return (*mpViews)[ nView ];
+}
+
+
+void TextEngine::SetActiveView( TextView* pTextView )
+{
+ if ( pTextView != mpActiveView )
+ {
+ if ( mpActiveView )
+ mpActiveView->HideSelection();
+
+ mpActiveView = pTextView;
+
+ if ( mpActiveView )
+ mpActiveView->ShowSelection();
+ }
+}
+
+void TextEngine::SetFont( const vcl::Font& rFont )
+{
+ if ( rFont == maFont )
+ return;
+
+ maFont = rFont;
+ // #i40221# As the font's color now defaults to transparent (since i35764)
+ // we have to choose a useful textcolor in this case.
+ // Otherwise maTextColor and maFont.GetColor() are both transparent...
+ if( rFont.GetColor() == COL_TRANSPARENT )
+ maTextColor = COL_BLACK;
+ else
+ maTextColor = rFont.GetColor();
+
+ // Do not allow transparent fonts because of selection
+ // (otherwise delete the background in ImplPaint later differently)
+ maFont.SetTransparent( false );
+ // Tell VCL not to use the font color, use text color from OutputDevice
+ maFont.SetColor( COL_TRANSPARENT );
+ Color aFillColor( maFont.GetFillColor() );
+ aFillColor.SetAlpha( 255 );
+ maFont.SetFillColor( aFillColor );
+
+ maFont.SetAlignment( ALIGN_TOP );
+ mpRefDev->SetFont( maFont );
+ mnDefTab = mpRefDev->GetTextWidth(" ");
+ if ( !mnDefTab )
+ mnDefTab = mpRefDev->GetTextWidth("XXXX");
+ if ( !mnDefTab )
+ mnDefTab = 1;
+ mnCharHeight = mpRefDev->GetTextHeight();
+
+ FormatFullDoc();
+ UpdateViews();
+
+ for ( auto nView = mpViews->size(); nView; )
+ {
+ TextView* pView = (*mpViews)[ --nView ];
+ pView->GetWindow()->SetInputContext( InputContext( GetFont(), !pView->IsReadOnly() ? InputContextFlags::Text|InputContextFlags::ExtText : InputContextFlags::NONE ) );
+ }
+
+}
+
+void TextEngine::SetMaxTextLen( sal_Int32 nLen )
+{
+ mnMaxTextLen = nLen>=0 ? nLen : EDIT_NOLIMIT;
+}
+
+void TextEngine::SetMaxTextWidth( tools::Long nMaxWidth )
+{
+ if ( nMaxWidth>=0 && nMaxWidth != mnMaxTextWidth )
+ {
+ mnMaxTextWidth = nMaxWidth;
+ FormatFullDoc();
+ UpdateViews();
+ }
+}
+
+const sal_Unicode static_aLFText[] = { '\n', 0 };
+const sal_Unicode static_aCRText[] = { '\r', 0 };
+const sal_Unicode static_aCRLFText[] = { '\r', '\n', 0 };
+
+static const sal_Unicode* static_getLineEndText( LineEnd aLineEnd )
+{
+ const sal_Unicode* pRet = nullptr;
+
+ switch( aLineEnd )
+ {
+ case LINEEND_LF:
+ pRet = static_aLFText;
+ break;
+ case LINEEND_CR:
+ pRet = static_aCRText;
+ break;
+ case LINEEND_CRLF:
+ pRet = static_aCRLFText;
+ break;
+ }
+ return pRet;
+}
+
+void TextEngine::ReplaceText(const TextSelection& rSel, const OUString& rText)
+{
+ ImpInsertText( rSel, rText );
+}
+
+OUString TextEngine::GetText( LineEnd aSeparator ) const
+{
+ return mpDoc->GetText( static_getLineEndText( aSeparator ) );
+}
+
+OUString TextEngine::GetTextLines( LineEnd aSeparator ) const
+{
+ OUStringBuffer aText;
+ const sal_uInt32 nParas = mpTEParaPortions->Count();
+ const sal_Unicode* pSep = static_getLineEndText( aSeparator );
+ for ( sal_uInt32 nP = 0; nP < nParas; ++nP )
+ {
+ TEParaPortion* pTEParaPortion = mpTEParaPortions->GetObject( nP );
+
+ const size_t nLines = pTEParaPortion->GetLines().size();
+ for ( size_t nL = 0; nL < nLines; ++nL )
+ {
+ TextLine& rLine = pTEParaPortion->GetLines()[nL];
+ aText.append( pTEParaPortion->GetNode()->GetText().subView(rLine.GetStart(), rLine.GetEnd() - rLine.GetStart()) );
+ if ( pSep && ( ( (nP+1) < nParas ) || ( (nL+1) < nLines ) ) )
+ aText.append(pSep);
+ }
+ }
+ return aText.makeStringAndClear();
+}
+
+OUString TextEngine::GetText( sal_uInt32 nPara ) const
+{
+ return mpDoc->GetText( nPara );
+}
+
+sal_Int32 TextEngine::GetTextLen() const
+{
+ return mpDoc->GetTextLen( static_getLineEndText( LINEEND_LF ) );
+}
+
+sal_Int32 TextEngine::GetTextLen( const TextSelection& rSel ) const
+{
+ TextSelection aSel( rSel );
+ aSel.Justify();
+ ValidateSelection( aSel );
+ return mpDoc->GetTextLen( static_getLineEndText( LINEEND_LF ), &aSel );
+}
+
+sal_Int32 TextEngine::GetTextLen( const sal_uInt32 nPara ) const
+{
+ return mpDoc->GetNodes()[ nPara ]->GetText().getLength();
+}
+
+void TextEngine::SetUpdateMode( bool bUpdate )
+{
+ if ( bUpdate != mbUpdate )
+ {
+ mbUpdate = bUpdate;
+ if ( mbUpdate )
+ {
+ FormatAndUpdate( GetActiveView() );
+ if ( GetActiveView() )
+ GetActiveView()->ShowCursor();
+ }
+ }
+}
+
+bool TextEngine::DoesKeyChangeText( const KeyEvent& rKeyEvent )
+{
+ bool bDoesChange = false;
+
+ KeyFuncType eFunc = rKeyEvent.GetKeyCode().GetFunction();
+ if ( eFunc != KeyFuncType::DONTKNOW )
+ {
+ switch ( eFunc )
+ {
+ case KeyFuncType::UNDO:
+ case KeyFuncType::REDO:
+ case KeyFuncType::CUT:
+ case KeyFuncType::PASTE:
+ bDoesChange = true;
+ break;
+ default:
+ // might get handled below
+ eFunc = KeyFuncType::DONTKNOW;
+ }
+ }
+ if ( eFunc == KeyFuncType::DONTKNOW )
+ {
+ switch ( rKeyEvent.GetKeyCode().GetCode() )
+ {
+ case KEY_DELETE:
+ case KEY_BACKSPACE:
+ if ( !rKeyEvent.GetKeyCode().IsMod2() )
+ bDoesChange = true;
+ break;
+ case KEY_RETURN:
+ case KEY_TAB:
+ if ( !rKeyEvent.GetKeyCode().IsMod1() && !rKeyEvent.GetKeyCode().IsMod2() )
+ bDoesChange = true;
+ break;
+ default:
+ bDoesChange = TextEngine::IsSimpleCharInput( rKeyEvent );
+ }
+ }
+ return bDoesChange;
+}
+
+bool TextEngine::IsSimpleCharInput( const KeyEvent& rKeyEvent )
+{
+ return rKeyEvent.GetCharCode() >= 32 && rKeyEvent.GetCharCode() != 127 &&
+ KEY_MOD1 != (rKeyEvent.GetKeyCode().GetModifier() & ~KEY_SHIFT) && // (ssa) #i45714#:
+ KEY_MOD2 != (rKeyEvent.GetKeyCode().GetModifier() & ~KEY_SHIFT); // check for Ctrl and Alt separately
+}
+
+void TextEngine::ImpInitDoc()
+{
+ if ( mpDoc )
+ mpDoc->Clear();
+ else
+ mpDoc.reset( new TextDoc );
+
+ mpTEParaPortions.reset(new TEParaPortions);
+
+ std::unique_ptr<TextNode> pNode(new TextNode( OUString() ));
+ mpDoc->GetNodes().insert( mpDoc->GetNodes().begin(), std::move(pNode) );
+
+ TEParaPortion* pIniPortion = new TEParaPortion( mpDoc->GetNodes().begin()->get() );
+ mpTEParaPortions->Insert( pIniPortion, 0 );
+
+ mbFormatted = false;
+
+ ImpParagraphRemoved( TEXT_PARA_ALL );
+ ImpParagraphInserted( 0 );
+}
+
+OUString TextEngine::GetText( const TextSelection& rSel, LineEnd aSeparator ) const
+{
+ if ( !rSel.HasRange() )
+ return OUString();
+
+ TextSelection aSel( rSel );
+ aSel.Justify();
+
+ OUStringBuffer aText;
+ const sal_uInt32 nStartPara = aSel.GetStart().GetPara();
+ const sal_uInt32 nEndPara = aSel.GetEnd().GetPara();
+ const sal_Unicode* pSep = static_getLineEndText( aSeparator );
+ for ( sal_uInt32 nNode = aSel.GetStart().GetPara(); nNode <= nEndPara; ++nNode )
+ {
+ TextNode* pNode = mpDoc->GetNodes()[ nNode ].get();
+
+ sal_Int32 nStartPos = 0;
+ sal_Int32 nEndPos = pNode->GetText().getLength();
+ if ( nNode == nStartPara )
+ nStartPos = aSel.GetStart().GetIndex();
+ if ( nNode == nEndPara ) // may also be == nStart!
+ nEndPos = aSel.GetEnd().GetIndex();
+
+ aText.append(pNode->GetText().subView(nStartPos, nEndPos-nStartPos));
+ if ( nNode < nEndPara )
+ aText.append(pSep);
+ }
+ return aText.makeStringAndClear();
+}
+
+void TextEngine::ImpRemoveText()
+{
+ ImpInitDoc();
+
+ const TextSelection aEmptySel;
+ for (TextView* pView : *mpViews)
+ {
+ pView->ImpSetSelection( aEmptySel );
+ }
+ ResetUndo();
+}
+
+void TextEngine::SetText( const OUString& rText )
+{
+ ImpRemoveText();
+
+ const bool bUndoCurrentlyEnabled = IsUndoEnabled();
+ // the manually inserted text cannot be reversed by the user
+ EnableUndo( false );
+
+ const TextSelection aEmptySel;
+
+ TextPaM aPaM;
+ if ( !rText.isEmpty() )
+ aPaM = ImpInsertText( aEmptySel, rText );
+
+ for (TextView* pView : *mpViews)
+ {
+ pView->ImpSetSelection( aEmptySel );
+
+ // if no text, then no Format&Update => the text remains
+ if ( rText.isEmpty() && GetUpdateMode() )
+ pView->Invalidate();
+ }
+
+ if( rText.isEmpty() ) // otherwise needs invalidation later; !bFormatted is sufficient
+ mnCurTextHeight = 0;
+
+ FormatAndUpdate();
+
+ EnableUndo( bUndoCurrentlyEnabled );
+ SAL_WARN_IF( HasUndoManager() && GetUndoManager().GetUndoActionCount(), "vcl", "SetText: Undo!" );
+}
+
+void TextEngine::CursorMoved( sal_uInt32 nNode )
+{
+ // delete empty attribute; but only if paragraph is not empty!
+ TextNode* pNode = mpDoc->GetNodes()[ nNode ].get();
+ if ( pNode && pNode->GetCharAttribs().HasEmptyAttribs() && !pNode->GetText().isEmpty() )
+ pNode->GetCharAttribs().DeleteEmptyAttribs();
+}
+
+void TextEngine::ImpRemoveChars( const TextPaM& rPaM, sal_Int32 nChars )
+{
+ SAL_WARN_IF( !nChars, "vcl", "ImpRemoveChars: 0 Chars?!" );
+ if ( IsUndoEnabled() && !IsInUndo() )
+ {
+ // attributes have to be saved for UNDO before RemoveChars!
+ TextNode* pNode = mpDoc->GetNodes()[ rPaM.GetPara() ].get();
+ OUString aStr( pNode->GetText().copy( rPaM.GetIndex(), nChars ) );
+
+ // check if attributes are being deleted or changed
+ const sal_Int32 nStart = rPaM.GetIndex();
+ const sal_Int32 nEnd = nStart + nChars;
+ for ( sal_uInt16 nAttr = pNode->GetCharAttribs().Count(); nAttr; )
+ {
+ TextCharAttrib& rAttr = pNode->GetCharAttribs().GetAttrib( --nAttr );
+ if ( ( rAttr.GetEnd() >= nStart ) && ( rAttr.GetStart() < nEnd ) )
+ {
+ break; // for
+ }
+ }
+ InsertUndo( std::make_unique<TextUndoRemoveChars>( this, rPaM, aStr ) );
+ }
+
+ mpDoc->RemoveChars( rPaM, nChars );
+ ImpCharsRemoved( rPaM.GetPara(), rPaM.GetIndex(), nChars );
+}
+
+TextPaM TextEngine::ImpConnectParagraphs( sal_uInt32 nLeft, sal_uInt32 nRight )
+{
+ SAL_WARN_IF( nLeft == nRight, "vcl", "ImpConnectParagraphs: connect the very same paragraph ?" );
+
+ TextNode* pLeft = mpDoc->GetNodes()[ nLeft ].get();
+ TextNode* pRight = mpDoc->GetNodes()[ nRight ].get();
+
+ if ( IsUndoEnabled() && !IsInUndo() )
+ InsertUndo( std::make_unique<TextUndoConnectParas>( this, nLeft, pLeft->GetText().getLength() ) );
+
+ // first lookup Portions, as pRight is gone after ConnectParagraphs
+ TEParaPortion* pLeftPortion = mpTEParaPortions->GetObject( nLeft );
+ TEParaPortion* pRightPortion = mpTEParaPortions->GetObject( nRight );
+ SAL_WARN_IF( !pLeft || !pLeftPortion, "vcl", "ImpConnectParagraphs(1): Hidden Portion" );
+ SAL_WARN_IF( !pRight || !pRightPortion, "vcl", "ImpConnectParagraphs(2): Hidden Portion" );
+
+ TextPaM aPaM = mpDoc->ConnectParagraphs( pLeft, pRight );
+ ImpParagraphRemoved( nRight );
+
+ pLeftPortion->MarkSelectionInvalid( aPaM.GetIndex() );
+
+ mpTEParaPortions->Remove( nRight );
+ // the right Node is deleted by EditDoc::ConnectParagraphs()
+
+ return aPaM;
+}
+
+TextPaM TextEngine::ImpDeleteText( const TextSelection& rSel )
+{
+ if ( !rSel.HasRange() )
+ return rSel.GetStart();
+
+ TextSelection aSel( rSel );
+ aSel.Justify();
+ TextPaM aStartPaM( aSel.GetStart() );
+ TextPaM aEndPaM( aSel.GetEnd() );
+
+ CursorMoved( aStartPaM.GetPara() ); // so that newly-adjusted attributes vanish
+ CursorMoved( aEndPaM.GetPara() ); // so that newly-adjusted attributes vanish
+
+ SAL_WARN_IF( !mpDoc->IsValidPaM( aStartPaM ), "vcl", "ImpDeleteText(1): bad Index" );
+ SAL_WARN_IF( !mpDoc->IsValidPaM( aEndPaM ), "vcl", "ImpDeleteText(2): bad Index" );
+
+ const sal_uInt32 nStartNode = aStartPaM.GetPara();
+ sal_uInt32 nEndNode = aEndPaM.GetPara();
+
+ // remove all Nodes inbetween
+ for ( sal_uInt32 z = nStartNode+1; z < nEndNode; ++z )
+ {
+ // always nStartNode+1, because of Remove()!
+ ImpRemoveParagraph( nStartNode+1 );
+ }
+
+ if ( nStartNode != nEndNode )
+ {
+ // the remainder of StartNodes...
+ TextNode* pLeft = mpDoc->GetNodes()[ nStartNode ].get();
+ sal_Int32 nChars = pLeft->GetText().getLength() - aStartPaM.GetIndex();
+ if ( nChars )
+ {
+ ImpRemoveChars( aStartPaM, nChars );
+ TEParaPortion* pPortion = mpTEParaPortions->GetObject( nStartNode );
+ SAL_WARN_IF( !pPortion, "vcl", "ImpDeleteText(3): bad Index" );
+ pPortion->MarkSelectionInvalid( aStartPaM.GetIndex() );
+ }
+
+ // the beginning of EndNodes...
+ nEndNode = nStartNode+1; // the other paragraphs were deleted
+ nChars = aEndPaM.GetIndex();
+ if ( nChars )
+ {
+ aEndPaM.GetPara() = nEndNode;
+ aEndPaM.GetIndex() = 0;
+ ImpRemoveChars( aEndPaM, nChars );
+ TEParaPortion* pPortion = mpTEParaPortions->GetObject( nEndNode );
+ SAL_WARN_IF( !pPortion, "vcl", "ImpDeleteText(4): bad Index" );
+ pPortion->MarkSelectionInvalid( 0 );
+ }
+
+ // connect...
+ aStartPaM = ImpConnectParagraphs( nStartNode, nEndNode );
+ }
+ else
+ {
+ const sal_Int32 nChars = aEndPaM.GetIndex() - aStartPaM.GetIndex();
+ ImpRemoveChars( aStartPaM, nChars );
+ TEParaPortion* pPortion = mpTEParaPortions->GetObject( nStartNode );
+ SAL_WARN_IF( !pPortion, "vcl", "ImpDeleteText(5): bad Index" );
+ pPortion->MarkInvalid( aEndPaM.GetIndex(), aStartPaM.GetIndex() - aEndPaM.GetIndex() );
+ }
+
+// UpdateSelections();
+ TextModified();
+ return aStartPaM;
+}
+
+void TextEngine::ImpRemoveParagraph( sal_uInt32 nPara )
+{
+ std::unique_ptr<TextNode> pNode = std::move(mpDoc->GetNodes()[ nPara ]);
+
+ // the Node is handled by Undo and is deleted if appropriate
+ mpDoc->GetNodes().erase( mpDoc->GetNodes().begin() + nPara );
+ if ( IsUndoEnabled() && !IsInUndo() )
+ InsertUndo( std::make_unique<TextUndoDelPara>( this, pNode.release(), nPara ) );
+
+ mpTEParaPortions->Remove( nPara );
+
+ ImpParagraphRemoved( nPara );
+}
+
+uno::Reference < i18n::XExtendedInputSequenceChecker > const & TextEngine::GetInputSequenceChecker()
+{
+ if ( !mxISC.is() )
+ {
+ mxISC = i18n::InputSequenceChecker::create( ::comphelper::getProcessComponentContext() );
+ }
+ return mxISC;
+}
+
+bool TextEngine::IsInputSequenceCheckingRequired( sal_Unicode c, const TextSelection& rCurSel ) const
+{
+ // get the index that really is first
+ const sal_Int32 nFirstPos = std::min(rCurSel.GetStart().GetIndex(), rCurSel.GetEnd().GetIndex());
+
+ bool bIsSequenceChecking =
+ SvtCTLOptions::IsCTLFontEnabled() &&
+ SvtCTLOptions::IsCTLSequenceChecking() &&
+ nFirstPos != 0; /* first char needs not to be checked */
+
+ if (bIsSequenceChecking)
+ {
+ uno::Reference< i18n::XBreakIterator > xBI = const_cast<TextEngine *>(this)->GetBreakIterator();
+ bIsSequenceChecking = xBI.is() && i18n::ScriptType::COMPLEX == xBI->getScriptType( OUString( c ), 0 );
+ }
+
+ return bIsSequenceChecking;
+}
+
+TextPaM TextEngine::ImpInsertText( const TextSelection& rCurSel, sal_Unicode c, bool bOverwrite )
+{
+ return ImpInsertText( c, rCurSel, bOverwrite );
+}
+
+TextPaM TextEngine::ImpInsertText( sal_Unicode c, const TextSelection& rCurSel, bool bOverwrite, bool bIsUserInput )
+{
+ SAL_WARN_IF( c == '\n', "vcl", "InsertText: NewLine!" );
+ SAL_WARN_IF( c == '\r', "vcl", "InsertText: NewLine!" );
+
+ TextPaM aPaM( rCurSel.GetStart() );
+ TextNode* pNode = mpDoc->GetNodes()[ aPaM.GetPara() ].get();
+
+ bool bDoOverwrite = bOverwrite && ( aPaM.GetIndex() < pNode->GetText().getLength() );
+
+ bool bUndoAction = rCurSel.HasRange() || bDoOverwrite;
+
+ if ( bUndoAction )
+ UndoActionStart();
+
+ if ( rCurSel.HasRange() )
+ {
+ aPaM = ImpDeleteText( rCurSel );
+ }
+ else if ( bDoOverwrite )
+ {
+ // if selection, then don't overwrite a character
+ TextSelection aTmpSel( aPaM );
+ ++aTmpSel.GetEnd().GetIndex();
+ ImpDeleteText( aTmpSel );
+ }
+
+ if (bIsUserInput && IsInputSequenceCheckingRequired( c, rCurSel ))
+ {
+ uno::Reference < i18n::XExtendedInputSequenceChecker > xISC = GetInputSequenceChecker();
+
+ if (xISC.is())
+ {
+ sal_Int32 nTmpPos = aPaM.GetIndex();
+ sal_Int16 nCheckMode = SvtCTLOptions::IsCTLSequenceCheckingRestricted() ?
+ i18n::InputSequenceCheckMode::STRICT : i18n::InputSequenceCheckMode::BASIC;
+
+ // the text that needs to be checked is only the one
+ // before the current cursor position
+ OUString aOldText( mpDoc->GetText( aPaM.GetPara() ).copy(0, nTmpPos) );
+ if (SvtCTLOptions::IsCTLSequenceCheckingTypeAndReplace())
+ {
+ OUString aNewText( aOldText );
+ xISC->correctInputSequence( aNewText, nTmpPos - 1, c, nCheckMode );
+
+ // find position of first character that has changed
+ const sal_Int32 nOldLen = aOldText.getLength();
+ const sal_Int32 nNewLen = aNewText.getLength();
+ const sal_Unicode *pOldTxt = aOldText.getStr();
+ const sal_Unicode *pNewTxt = aNewText.getStr();
+ sal_Int32 nChgPos = 0;
+ while ( nChgPos < nOldLen && nChgPos < nNewLen &&
+ pOldTxt[nChgPos] == pNewTxt[nChgPos] )
+ ++nChgPos;
+
+ OUString aChgText( aNewText.copy( nChgPos ) );
+
+ // select text from first pos to be changed to current pos
+ TextSelection aSel( TextPaM( aPaM.GetPara(), nChgPos ), aPaM );
+
+ if (!aChgText.isEmpty())
+ // ImpInsertText implicitly handles undo...
+ return ImpInsertText( aSel, aChgText );
+ else
+ return aPaM;
+ }
+ else
+ {
+ // should the character be ignored (i.e. not get inserted) ?
+ if (!xISC->checkInputSequence( aOldText, nTmpPos - 1, c, nCheckMode ))
+ return aPaM; // nothing to be done -> no need for undo
+ }
+ }
+
+ // at this point now we will insert the character 'normally' some lines below...
+ }
+
+ if ( IsUndoEnabled() && !IsInUndo() )
+ {
+ std::unique_ptr<TextUndoInsertChars> pNewUndo(new TextUndoInsertChars( this, aPaM, OUString(c) ));
+ bool bTryMerge = !bDoOverwrite && ( c != ' ' );
+ InsertUndo( std::move(pNewUndo), bTryMerge );
+ }
+
+ TEParaPortion* pPortion = mpTEParaPortions->GetObject( aPaM.GetPara() );
+ pPortion->MarkInvalid( aPaM.GetIndex(), 1 );
+ if ( c == '\t' )
+ pPortion->SetNotSimpleInvalid();
+ aPaM = mpDoc->InsertText( aPaM, c );
+ ImpCharsInserted( aPaM.GetPara(), aPaM.GetIndex()-1, 1 );
+
+ TextModified();
+
+ if ( bUndoAction )
+ UndoActionEnd();
+
+ return aPaM;
+}
+
+TextPaM TextEngine::ImpInsertText( const TextSelection& rCurSel, const OUString& rStr )
+{
+ UndoActionStart();
+
+ TextPaM aPaM;
+
+ if ( rCurSel.HasRange() )
+ aPaM = ImpDeleteText( rCurSel );
+ else
+ aPaM = rCurSel.GetEnd();
+
+ OUString aText(convertLineEnd(rStr, LINEEND_LF));
+
+ sal_Int32 nStart = 0;
+ while ( nStart < aText.getLength() )
+ {
+ sal_Int32 nEnd = aText.indexOf( LINE_SEP, nStart );
+ if (nEnd == -1)
+ nEnd = aText.getLength(); // do not dereference!
+
+ // Start == End => empty line
+ if ( nEnd > nStart )
+ {
+ OUString aLine(aText.copy(nStart, nEnd-nStart));
+ if ( IsUndoEnabled() && !IsInUndo() )
+ InsertUndo( std::make_unique<TextUndoInsertChars>( this, aPaM, aLine ) );
+
+ TEParaPortion* pPortion = mpTEParaPortions->GetObject( aPaM.GetPara() );
+ pPortion->MarkInvalid( aPaM.GetIndex(), aLine.getLength() );
+ if (aLine.indexOf( '\t' ) != -1)
+ pPortion->SetNotSimpleInvalid();
+
+ aPaM = mpDoc->InsertText( aPaM, aLine );
+ ImpCharsInserted( aPaM.GetPara(), aPaM.GetIndex()-aLine.getLength(), aLine.getLength() );
+
+ }
+ if ( nEnd < aText.getLength() )
+ aPaM = ImpInsertParaBreak( aPaM );
+
+ if ( nEnd == aText.getLength() ) // #108611# prevent overflow in "nStart = nEnd+1" calculation
+ break;
+
+ nStart = nEnd+1;
+ }
+
+ UndoActionEnd();
+
+ TextModified();
+ return aPaM;
+}
+
+TextPaM TextEngine::ImpInsertParaBreak( const TextSelection& rCurSel )
+{
+ TextPaM aPaM;
+ if ( rCurSel.HasRange() )
+ aPaM = ImpDeleteText( rCurSel );
+ else
+ aPaM = rCurSel.GetEnd();
+
+ return ImpInsertParaBreak( aPaM );
+}
+
+TextPaM TextEngine::ImpInsertParaBreak( const TextPaM& rPaM )
+{
+ if ( IsUndoEnabled() && !IsInUndo() )
+ InsertUndo( std::make_unique<TextUndoSplitPara>( this, rPaM.GetPara(), rPaM.GetIndex() ) );
+
+ TextNode* pNode = mpDoc->GetNodes()[ rPaM.GetPara() ].get();
+ bool bFirstParaContentChanged = rPaM.GetIndex() < pNode->GetText().getLength();
+
+ TextPaM aPaM( mpDoc->InsertParaBreak( rPaM ) );
+
+ TEParaPortion* pPortion = mpTEParaPortions->GetObject( rPaM.GetPara() );
+ SAL_WARN_IF( !pPortion, "vcl", "ImpInsertParaBreak: Hidden Portion" );
+ pPortion->MarkInvalid( rPaM.GetIndex(), 0 );
+
+ TextNode* pNewNode = mpDoc->GetNodes()[ aPaM.GetPara() ].get();
+ TEParaPortion* pNewPortion = new TEParaPortion( pNewNode );
+ mpTEParaPortions->Insert( pNewPortion, aPaM.GetPara() );
+ ImpParagraphInserted( aPaM.GetPara() );
+
+ CursorMoved( rPaM.GetPara() ); // if empty attribute created
+ TextModified();
+
+ if ( bFirstParaContentChanged )
+ Broadcast( TextHint( SfxHintId::TextParaContentChanged, rPaM.GetPara() ) );
+
+ return aPaM;
+}
+
+tools::Rectangle TextEngine::PaMtoEditCursor( const TextPaM& rPaM, bool bSpecial )
+{
+ SAL_WARN_IF( !GetUpdateMode(), "vcl", "PaMtoEditCursor: GetUpdateMode()" );
+
+ tools::Rectangle aEditCursor;
+ tools::Long nY = 0;
+
+ if ( !mbHasMultiLineParas )
+ {
+ nY = rPaM.GetPara() * mnCharHeight;
+ }
+ else
+ {
+ for ( sal_uInt32 nPortion = 0; nPortion < rPaM.GetPara(); ++nPortion )
+ {
+ TEParaPortion* pPortion = mpTEParaPortions->GetObject(nPortion);
+ nY += pPortion->GetLines().size() * mnCharHeight;
+ }
+ }
+
+ aEditCursor = GetEditCursor( rPaM, bSpecial );
+ aEditCursor.AdjustTop(nY );
+ aEditCursor.AdjustBottom(nY );
+ return aEditCursor;
+}
+
+tools::Rectangle TextEngine::GetEditCursor( const TextPaM& rPaM, bool bSpecial, bool bPreferPortionStart )
+{
+ if ( !IsFormatted() && !IsFormatting() )
+ FormatAndUpdate();
+
+ TEParaPortion* pPortion = mpTEParaPortions->GetObject( rPaM.GetPara() );
+ //TextNode* pNode = mpDoc->GetNodes().GetObject( rPaM.GetPara() );
+
+ /*
+ bSpecial: If behind the last character of a made up line, stay at the
+ end of the line, not at the start of the next line.
+ Purpose: - really END = > behind the last character
+ - to selection...
+
+ */
+
+ tools::Long nY = 0;
+ sal_Int32 nCurIndex = 0;
+ TextLine* pLine = nullptr;
+ for (TextLine & rTmpLine : pPortion->GetLines())
+ {
+ if ( ( rTmpLine.GetStart() == rPaM.GetIndex() ) || ( rTmpLine.IsIn( rPaM.GetIndex(), bSpecial ) ) )
+ {
+ pLine = &rTmpLine;
+ break;
+ }
+
+ nCurIndex = nCurIndex + rTmpLine.GetLen();
+ nY += mnCharHeight;
+ }
+ if ( !pLine )
+ {
+ // Cursor at end of paragraph
+ SAL_WARN_IF( rPaM.GetIndex() != nCurIndex, "vcl", "GetEditCursor: Bad Index!" );
+
+ pLine = & ( pPortion->GetLines().back() );
+ nY -= mnCharHeight;
+ }
+
+ tools::Rectangle aEditCursor;
+
+ aEditCursor.SetTop( nY );
+ nY += mnCharHeight;
+ aEditCursor.SetBottom( nY-1 );
+
+ // search within the line
+ tools::Long nX = ImpGetXPos( rPaM.GetPara(), pLine, rPaM.GetIndex(), bPreferPortionStart );
+ aEditCursor.SetLeft(nX);
+ aEditCursor.SetRight(nX);
+ return aEditCursor;
+}
+
+tools::Long TextEngine::ImpGetXPos( sal_uInt32 nPara, TextLine* pLine, sal_Int32 nIndex, bool bPreferPortionStart )
+{
+ SAL_WARN_IF( ( nIndex < pLine->GetStart() ) || ( nIndex > pLine->GetEnd() ) , "vcl", "ImpGetXPos: Bad parameters!" );
+
+ bool bDoPreferPortionStart = bPreferPortionStart;
+ // Assure that the portion belongs to this line
+ if ( nIndex == pLine->GetStart() )
+ bDoPreferPortionStart = true;
+ else if ( nIndex == pLine->GetEnd() )
+ bDoPreferPortionStart = false;
+
+ TEParaPortion* pParaPortion = mpTEParaPortions->GetObject( nPara );
+
+ sal_Int32 nTextPortionStart = 0;
+ std::size_t nTextPortion = pParaPortion->GetTextPortions().FindPortion( nIndex, nTextPortionStart, bDoPreferPortionStart );
+
+ SAL_WARN_IF( ( nTextPortion < pLine->GetStartPortion() ) || ( nTextPortion > pLine->GetEndPortion() ), "vcl", "GetXPos: Portion not in current line!" );
+
+ TETextPortion& rPortion = pParaPortion->GetTextPortions()[ nTextPortion ];
+
+ tools::Long nX = ImpGetPortionXOffset( nPara, pLine, nTextPortion );
+
+ tools::Long nPortionTextWidth = rPortion.GetWidth();
+
+ if ( nTextPortionStart != nIndex )
+ {
+ // Search within portion...
+ if ( nIndex == ( nTextPortionStart + rPortion.GetLen() ) )
+ {
+ // End of Portion
+ if ( ( rPortion.GetKind() == PORTIONKIND_TAB ) ||
+ ( !IsRightToLeft() && !rPortion.IsRightToLeft() ) ||
+ ( IsRightToLeft() && rPortion.IsRightToLeft() ) )
+ {
+ nX += nPortionTextWidth;
+ if ( ( rPortion.GetKind() == PORTIONKIND_TAB ) && ( (nTextPortion+1) < pParaPortion->GetTextPortions().size() ) )
+ {
+ TETextPortion& rNextPortion = pParaPortion->GetTextPortions()[ nTextPortion+1 ];
+ if (rNextPortion.GetKind() != PORTIONKIND_TAB && IsRightToLeft() != rNextPortion.IsRightToLeft())
+ {
+ // End of the tab portion, use start of next for cursor pos
+ SAL_WARN_IF( bPreferPortionStart, "vcl", "ImpGetXPos: How can we get here!" );
+ nX = ImpGetXPos( nPara, pLine, nIndex, true );
+ }
+
+ }
+ }
+ }
+ else if ( rPortion.GetKind() == PORTIONKIND_TEXT )
+ {
+ SAL_WARN_IF( nIndex == pLine->GetStart(), "vcl", "ImpGetXPos: Strange behavior" );
+
+ tools::Long nPosInPortion = CalcTextWidth( nPara, nTextPortionStart, nIndex-nTextPortionStart );
+
+ if (IsRightToLeft() == rPortion.IsRightToLeft())
+ {
+ nX += nPosInPortion;
+ }
+ else
+ {
+ nX += nPortionTextWidth - nPosInPortion;
+ }
+ }
+ }
+ else // if ( nIndex == pLine->GetStart() )
+ {
+ if (rPortion.GetKind() != PORTIONKIND_TAB && IsRightToLeft() != rPortion.IsRightToLeft())
+ {
+ nX += nPortionTextWidth;
+ }
+ }
+
+ return nX;
+}
+
+const TextAttrib* TextEngine::FindAttrib( const TextPaM& rPaM, sal_uInt16 nWhich ) const
+{
+ const TextAttrib* pAttr = nullptr;
+ const TextCharAttrib* pCharAttr = FindCharAttrib( rPaM, nWhich );
+ if ( pCharAttr )
+ pAttr = &pCharAttr->GetAttr();
+ return pAttr;
+}
+
+const TextCharAttrib* TextEngine::FindCharAttrib( const TextPaM& rPaM, sal_uInt16 nWhich ) const
+{
+ const TextCharAttrib* pAttr = nullptr;
+ TextNode* pNode = mpDoc->GetNodes()[ rPaM.GetPara() ].get();
+ if (pNode && (rPaM.GetIndex() <= pNode->GetText().getLength()))
+ pAttr = pNode->GetCharAttribs().FindAttrib( nWhich, rPaM.GetIndex() );
+ return pAttr;
+}
+
+TextPaM TextEngine::GetPaM( const Point& rDocPos )
+{
+ SAL_WARN_IF( !GetUpdateMode(), "vcl", "GetPaM: GetUpdateMode()" );
+
+ tools::Long nY = 0;
+ for ( sal_uInt32 nPortion = 0; nPortion < mpTEParaPortions->Count(); ++nPortion )
+ {
+ TEParaPortion* pPortion = mpTEParaPortions->GetObject( nPortion );
+ tools::Long nTmpHeight = pPortion->GetLines().size() * mnCharHeight;
+ nY += nTmpHeight;
+ if ( nY > rDocPos.Y() )
+ {
+ nY -= nTmpHeight;
+ Point aPosInPara( rDocPos );
+ aPosInPara.AdjustY( -nY );
+
+ TextPaM aPaM( nPortion, 0 );
+ aPaM.GetIndex() = ImpFindIndex( nPortion, aPosInPara );
+ return aPaM;
+ }
+ }
+
+ // not found - go to last visible
+ const sal_uInt32 nLastNode = static_cast<sal_uInt32>(mpDoc->GetNodes().size() - 1);
+ TextNode* pLast = mpDoc->GetNodes()[ nLastNode ].get();
+ return TextPaM( nLastNode, pLast->GetText().getLength() );
+}
+
+sal_Int32 TextEngine::ImpFindIndex( sal_uInt32 nPortion, const Point& rPosInPara )
+{
+ SAL_WARN_IF( !IsFormatted(), "vcl", "GetPaM: Not formatted" );
+ TEParaPortion* pPortion = mpTEParaPortions->GetObject( nPortion );
+
+ sal_Int32 nCurIndex = 0;
+
+ tools::Long nY = 0;
+ TextLine* pLine = nullptr;
+ std::vector<TextLine>::size_type nLine;
+ for ( nLine = 0; nLine < pPortion->GetLines().size(); nLine++ )
+ {
+ TextLine& rmpLine = pPortion->GetLines()[ nLine ];
+ nY += mnCharHeight;
+ if ( nY > rPosInPara.Y() ) // that's it
+ {
+ pLine = &rmpLine;
+ break; // correct Y-Position not needed
+ }
+ }
+
+ assert(pLine && "ImpFindIndex: pLine ?");
+
+ nCurIndex = GetCharPos( nPortion, nLine, rPosInPara.X() );
+
+ if ( nCurIndex && ( nCurIndex == pLine->GetEnd() ) &&
+ ( pLine != &( pPortion->GetLines().back() ) ) )
+ {
+ uno::Reference < i18n::XBreakIterator > xBI = GetBreakIterator();
+ sal_Int32 nCount = 1;
+ nCurIndex = xBI->previousCharacters( pPortion->GetNode()->GetText(), nCurIndex, GetLocale(), i18n::CharacterIteratorMode::SKIPCELL, nCount, nCount );
+ }
+ return nCurIndex;
+}
+
+sal_Int32 TextEngine::GetCharPos( sal_uInt32 nPortion, std::vector<TextLine>::size_type nLine, tools::Long nXPos )
+{
+
+ TEParaPortion* pPortion = mpTEParaPortions->GetObject( nPortion );
+ TextLine& rLine = pPortion->GetLines()[ nLine ];
+
+ sal_Int32 nCurIndex = rLine.GetStart();
+
+ tools::Long nTmpX = rLine.GetStartX();
+ if ( nXPos <= nTmpX )
+ return nCurIndex;
+
+ for ( std::size_t i = rLine.GetStartPortion(); i <= rLine.GetEndPortion(); i++ )
+ {
+ TETextPortion& rTextPortion = pPortion->GetTextPortions()[ i ];
+ nTmpX += rTextPortion.GetWidth();
+
+ if ( nTmpX > nXPos )
+ {
+ if( rTextPortion.GetLen() > 1 )
+ {
+ nTmpX -= rTextPortion.GetWidth(); // position before Portion
+ // TODO: Optimize: no GetTextBreak if fixed-width Font
+ vcl::Font aFont;
+ SeekCursor( nPortion, nCurIndex+1, aFont, nullptr );
+ mpRefDev->SetFont( aFont);
+ tools::Long nPosInPortion = nXPos-nTmpX;
+ if ( IsRightToLeft() != rTextPortion.IsRightToLeft() )
+ nPosInPortion = rTextPortion.GetWidth() - nPosInPortion;
+ nCurIndex = mpRefDev->GetTextBreak( pPortion->GetNode()->GetText(), nPosInPortion, nCurIndex );
+ // MT: GetTextBreak should assure that we are not within a CTL cell...
+ }
+ return nCurIndex;
+ }
+ nCurIndex += rTextPortion.GetLen();
+ }
+ return nCurIndex;
+}
+
+tools::Long TextEngine::GetTextHeight() const
+{
+ SAL_WARN_IF( !GetUpdateMode(), "vcl", "GetTextHeight: GetUpdateMode()" );
+
+ if ( !IsFormatted() && !IsFormatting() )
+ const_cast<TextEngine*>(this)->FormatAndUpdate();
+
+ return mnCurTextHeight;
+}
+
+tools::Long TextEngine::GetTextHeight( sal_uInt32 nParagraph ) const
+{
+ SAL_WARN_IF( !GetUpdateMode(), "vcl", "GetTextHeight: GetUpdateMode()" );
+
+ if ( !IsFormatted() && !IsFormatting() )
+ const_cast<TextEngine*>(this)->FormatAndUpdate();
+
+ return CalcParaHeight( nParagraph );
+}
+
+tools::Long TextEngine::CalcTextWidth( sal_uInt32 nPara )
+{
+ tools::Long nParaWidth = 0;
+ TEParaPortion* pPortion = mpTEParaPortions->GetObject( nPara );
+ for ( auto nLine = pPortion->GetLines().size(); nLine; )
+ {
+ tools::Long nLineWidth = 0;
+ TextLine& rLine = pPortion->GetLines()[ --nLine ];
+ for ( std::size_t nTP = rLine.GetStartPortion(); nTP <= rLine.GetEndPortion(); nTP++ )
+ {
+ TETextPortion& rTextPortion = pPortion->GetTextPortions()[ nTP ];
+ nLineWidth += rTextPortion.GetWidth();
+ }
+ if ( nLineWidth > nParaWidth )
+ nParaWidth = nLineWidth;
+ }
+ return nParaWidth;
+}
+
+tools::Long TextEngine::CalcTextWidth()
+{
+ if ( !IsFormatted() && !IsFormatting() )
+ FormatAndUpdate();
+
+ if ( mnCurTextWidth < 0 )
+ {
+ mnCurTextWidth = 0;
+ for ( sal_uInt32 nPara = mpTEParaPortions->Count(); nPara; )
+ {
+ const tools::Long nParaWidth = CalcTextWidth( --nPara );
+ if ( nParaWidth > mnCurTextWidth )
+ mnCurTextWidth = nParaWidth;
+ }
+ }
+ return mnCurTextWidth+1;// wider by 1, as CreateLines breaks at >=
+}
+
+tools::Long TextEngine::CalcTextHeight() const
+{
+ SAL_WARN_IF( !GetUpdateMode(), "vcl", "CalcTextHeight: GetUpdateMode()" );
+
+ tools::Long nY = 0;
+ for ( auto nPortion = mpTEParaPortions->Count(); nPortion; )
+ nY += CalcParaHeight( --nPortion );
+ return nY;
+}
+
+tools::Long TextEngine::CalcTextWidth( sal_uInt32 nPara, sal_Int32 nPortionStart, sal_Int32 nLen )
+{
+#ifdef DBG_UTIL
+ // within the text there must not be a Portion change (attribute/tab)!
+ sal_Int32 nTabPos = mpDoc->GetNodes()[ nPara ]->GetText().indexOf( '\t', nPortionStart );
+ SAL_WARN_IF( nTabPos != -1 && nTabPos < (nPortionStart+nLen), "vcl", "CalcTextWidth: Tab!" );
+#endif
+
+ vcl::Font aFont;
+ SeekCursor( nPara, nPortionStart+1, aFont, nullptr );
+ mpRefDev->SetFont( aFont );
+ TextNode* pNode = mpDoc->GetNodes()[ nPara ].get();
+ tools::Long nWidth = mpRefDev->GetTextWidth( pNode->GetText(), nPortionStart, nLen );
+ return nWidth;
+}
+
+void TextEngine::GetTextPortionRange(const TextPaM& rPaM, sal_Int32& nStart, sal_Int32& nEnd)
+{
+ nStart = 0;
+ nEnd = 0;
+ TEParaPortion* pParaPortion = mpTEParaPortions->GetObject( rPaM.GetPara() );
+ for ( std::size_t i = 0; i < pParaPortion->GetTextPortions().size(); ++i )
+ {
+ TETextPortion& rTextPortion = pParaPortion->GetTextPortions()[ i ];
+ if (nStart + rTextPortion.GetLen() > rPaM.GetIndex())
+ {
+ nEnd = nStart + rTextPortion.GetLen();
+ return;
+ }
+ else
+ {
+ nStart += rTextPortion.GetLen();
+ }
+ }
+}
+
+sal_uInt16 TextEngine::GetLineCount( sal_uInt32 nParagraph ) const
+{
+ SAL_WARN_IF( nParagraph >= mpTEParaPortions->Count(), "vcl", "GetLineCount: Out of range" );
+
+ TEParaPortion* pPPortion = mpTEParaPortions->GetObject( nParagraph );
+ if ( pPPortion )
+ return pPPortion->GetLines().size();
+
+ return 0;
+}
+
+sal_Int32 TextEngine::GetLineLen( sal_uInt32 nParagraph, sal_uInt16 nLine ) const
+{
+ SAL_WARN_IF( nParagraph >= mpTEParaPortions->Count(), "vcl", "GetLineCount: Out of range" );
+
+ TEParaPortion* pPPortion = mpTEParaPortions->GetObject( nParagraph );
+ if ( pPPortion && ( nLine < pPPortion->GetLines().size() ) )
+ {
+ return pPPortion->GetLines()[ nLine ].GetLen();
+ }
+
+ return 0;
+}
+
+tools::Long TextEngine::CalcParaHeight( sal_uInt32 nParagraph ) const
+{
+ tools::Long nHeight = 0;
+
+ TEParaPortion* pPPortion = mpTEParaPortions->GetObject( nParagraph );
+ SAL_WARN_IF( !pPPortion, "vcl", "GetParaHeight: paragraph not found" );
+ if ( pPPortion )
+ nHeight = pPPortion->GetLines().size() * mnCharHeight;
+
+ return nHeight;
+}
+
+Range TextEngine::GetInvalidYOffsets( sal_uInt32 nPortion )
+{
+ TEParaPortion* pTEParaPortion = mpTEParaPortions->GetObject( nPortion );
+ sal_uInt16 nLines = pTEParaPortion->GetLines().size();
+ sal_uInt16 nLastInvalid, nFirstInvalid = 0;
+ sal_uInt16 nLine;
+ for ( nLine = 0; nLine < nLines; nLine++ )
+ {
+ TextLine& rL = pTEParaPortion->GetLines()[ nLine ];
+ if ( rL.IsInvalid() )
+ {
+ nFirstInvalid = nLine;
+ break;
+ }
+ }
+
+ for ( nLastInvalid = nFirstInvalid; nLastInvalid < nLines; nLastInvalid++ )
+ {
+ TextLine& rL = pTEParaPortion->GetLines()[ nLine ];
+ if ( rL.IsValid() )
+ break;
+ }
+
+ if ( nLastInvalid >= nLines )
+ nLastInvalid = nLines-1;
+
+ return Range( nFirstInvalid*mnCharHeight, ((nLastInvalid+1)*mnCharHeight)-1 );
+}
+
+sal_uInt32 TextEngine::GetParagraphCount() const
+{
+ return static_cast<sal_uInt32>(mpDoc->GetNodes().size());
+}
+
+void TextEngine::EnableUndo( bool bEnable )
+{
+ // delete list when switching mode
+ if ( bEnable != IsUndoEnabled() )
+ ResetUndo();
+
+ mbUndoEnabled = bEnable;
+}
+
+SfxUndoManager& TextEngine::GetUndoManager()
+{
+ if ( !mpUndoManager )
+ mpUndoManager.reset( new TextUndoManager( this ) );
+ return *mpUndoManager;
+}
+
+void TextEngine::UndoActionStart( sal_uInt16 nId )
+{
+ if ( IsUndoEnabled() && !IsInUndo() )
+ {
+ GetUndoManager().EnterListAction( OUString(), OUString(), nId, ViewShellId(-1) );
+ }
+}
+
+void TextEngine::UndoActionEnd()
+{
+ if ( IsUndoEnabled() && !IsInUndo() )
+ GetUndoManager().LeaveListAction();
+}
+
+void TextEngine::InsertUndo( std::unique_ptr<TextUndo> pUndo, bool bTryMerge )
+{
+ SAL_WARN_IF( IsInUndo(), "vcl", "InsertUndo: in Undo mode!" );
+ GetUndoManager().AddUndoAction( std::move(pUndo), bTryMerge );
+}
+
+void TextEngine::ResetUndo()
+{
+ if ( mpUndoManager )
+ mpUndoManager->Clear();
+}
+
+void TextEngine::InsertContent( std::unique_ptr<TextNode> pNode, sal_uInt32 nPara )
+{
+ SAL_WARN_IF( !pNode, "vcl", "InsertContent: NULL-Pointer!" );
+ SAL_WARN_IF( !IsInUndo(), "vcl", "InsertContent: only in Undo()!" );
+ TEParaPortion* pNew = new TEParaPortion( pNode.get() );
+ mpTEParaPortions->Insert( pNew, nPara );
+ mpDoc->GetNodes().insert( mpDoc->GetNodes().begin() + nPara, std::move(pNode) );
+ ImpParagraphInserted( nPara );
+}
+
+TextPaM TextEngine::SplitContent( sal_uInt32 nNode, sal_Int32 nSepPos )
+{
+#ifdef DBG_UTIL
+ TextNode* pNode = mpDoc->GetNodes()[ nNode ].get();
+ SAL_WARN_IF( !pNode, "vcl", "SplitContent: Invalid Node!" );
+ SAL_WARN_IF( !IsInUndo(), "vcl", "SplitContent: only in Undo()!" );
+ SAL_WARN_IF( nSepPos > pNode->GetText().getLength(), "vcl", "SplitContent: Bad index" );
+#endif
+ TextPaM aPaM( nNode, nSepPos );
+ return ImpInsertParaBreak( aPaM );
+}
+
+TextPaM TextEngine::ConnectContents( sal_uInt32 nLeftNode )
+{
+ SAL_WARN_IF( !IsInUndo(), "vcl", "ConnectContent: only in Undo()!" );
+ return ImpConnectParagraphs( nLeftNode, nLeftNode+1 );
+}
+
+void TextEngine::SeekCursor( sal_uInt32 nPara, sal_Int32 nPos, vcl::Font& rFont, OutputDevice* pOutDev )
+{
+ rFont = maFont;
+ if ( pOutDev )
+ pOutDev->SetTextColor( maTextColor );
+
+ TextNode* pNode = mpDoc->GetNodes()[ nPara ].get();
+ sal_uInt16 nAttribs = pNode->GetCharAttribs().Count();
+ for ( sal_uInt16 nAttr = 0; nAttr < nAttribs; nAttr++ )
+ {
+ TextCharAttrib& rAttrib = pNode->GetCharAttribs().GetAttrib( nAttr );
+ if ( rAttrib.GetStart() > nPos )
+ break;
+
+ // When seeking don't use Attr that start there!
+ // Do not use empty attributes:
+ // - If just being setup and empty => no effect on Font
+ // - Characters that are setup in an empty paragraph become visible right away.
+ if ( ( ( rAttrib.GetStart() < nPos ) && ( rAttrib.GetEnd() >= nPos ) )
+ || pNode->GetText().isEmpty() )
+ {
+ if ( rAttrib.Which() != TEXTATTR_FONTCOLOR )
+ {
+ rAttrib.GetAttr().SetFont(rFont);
+ }
+ else
+ {
+ if ( pOutDev )
+ pOutDev->SetTextColor( static_cast<const TextAttribFontColor&>(rAttrib.GetAttr()).GetColor() );
+ }
+ }
+ }
+
+ if ( !(mpIMEInfos && mpIMEInfos->pAttribs && ( mpIMEInfos->aPos.GetPara() == nPara ) &&
+ ( nPos > mpIMEInfos->aPos.GetIndex() ) && ( nPos <= ( mpIMEInfos->aPos.GetIndex() + mpIMEInfos->nLen ) )) )
+ return;
+
+ ExtTextInputAttr nAttr = mpIMEInfos->pAttribs[ nPos - mpIMEInfos->aPos.GetIndex() - 1 ];
+ if ( nAttr & ExtTextInputAttr::Underline )
+ rFont.SetUnderline( LINESTYLE_SINGLE );
+ else if ( nAttr & ExtTextInputAttr::DoubleUnderline )
+ rFont.SetUnderline( LINESTYLE_DOUBLE );
+ else if ( nAttr & ExtTextInputAttr::BoldUnderline )
+ rFont.SetUnderline( LINESTYLE_BOLD );
+ else if ( nAttr & ExtTextInputAttr::DottedUnderline )
+ rFont.SetUnderline( LINESTYLE_DOTTED );
+ else if ( nAttr & ExtTextInputAttr::DashDotUnderline )
+ rFont.SetUnderline( LINESTYLE_DOTTED );
+ if ( nAttr & ExtTextInputAttr::RedText )
+ rFont.SetColor( COL_RED );
+ else if ( nAttr & ExtTextInputAttr::HalfToneText )
+ rFont.SetColor( COL_LIGHTGRAY );
+ if ( nAttr & ExtTextInputAttr::Highlight )
+ {
+ const StyleSettings& rStyleSettings = Application::GetSettings().GetStyleSettings();
+ rFont.SetColor( rStyleSettings.GetHighlightTextColor() );
+ rFont.SetFillColor( rStyleSettings.GetHighlightColor() );
+ rFont.SetTransparent( false );
+ }
+ else if ( nAttr & ExtTextInputAttr::GrayWaveline )
+ {
+ rFont.SetUnderline( LINESTYLE_WAVE );
+// if( pOut )
+// pOut->SetTextLineColor( COL_LIGHTGRAY );
+ }
+}
+
+void TextEngine::FormatAndUpdate( TextView* pCurView )
+{
+ if ( mbDowning )
+ return;
+
+ if ( IsInUndo() )
+ IdleFormatAndUpdate( pCurView );
+ else
+ {
+ FormatDoc();
+ UpdateViews( pCurView );
+ }
+}
+
+void TextEngine::IdleFormatAndUpdate( TextView* pCurView, sal_uInt16 nMaxTimerRestarts )
+{
+ mpIdleFormatter->DoIdleFormat( pCurView, nMaxTimerRestarts );
+}
+
+void TextEngine::TextModified()
+{
+ mbFormatted = false;
+ mbModified = true;
+}
+
+void TextEngine::UpdateViews( TextView* pCurView )
+{
+ if ( !GetUpdateMode() || IsFormatting() || maInvalidRect.IsEmpty() )
+ return;
+
+ SAL_WARN_IF( !IsFormatted(), "vcl", "UpdateViews: Doc not formatted!" );
+
+ for (TextView* pView : *mpViews)
+ {
+ pView->HideCursor();
+
+ tools::Rectangle aClipRect( maInvalidRect );
+ const Size aOutSz = pView->GetWindow()->GetOutputSizePixel();
+ const tools::Rectangle aVisArea( pView->GetStartDocPos(), aOutSz );
+ aClipRect.Intersection( aVisArea );
+ if ( !aClipRect.IsEmpty() )
+ {
+ // translate into window coordinates
+ Point aNewPos = pView->GetWindowPos( aClipRect.TopLeft() );
+ if ( IsRightToLeft() )
+ aNewPos.AdjustX( -(aOutSz.Width() - 1) );
+ aClipRect.SetPos( aNewPos );
+
+ pView->GetWindow()->Invalidate( aClipRect );
+ }
+ }
+
+ if ( pCurView )
+ {
+ pCurView->ShowCursor( pCurView->IsAutoScroll() );
+ }
+
+ maInvalidRect = tools::Rectangle();
+}
+
+IMPL_LINK_NOARG(TextEngine, IdleFormatHdl, Timer *, void)
+{
+ FormatAndUpdate( mpIdleFormatter->GetView() );
+}
+
+void TextEngine::CheckIdleFormatter()
+{
+ mpIdleFormatter->ForceTimeout();
+}
+
+void TextEngine::FormatFullDoc()
+{
+ for ( sal_uInt32 nPortion = 0; nPortion < mpTEParaPortions->Count(); ++nPortion )
+ {
+ TEParaPortion* pTEParaPortion = mpTEParaPortions->GetObject( nPortion );
+ pTEParaPortion->MarkSelectionInvalid( 0 );
+ }
+ mbFormatted = false;
+ FormatDoc();
+}
+
+void TextEngine::FormatDoc()
+{
+ if ( IsFormatted() || !GetUpdateMode() || IsFormatting() )
+ return;
+
+ mbIsFormatting = true;
+ mbHasMultiLineParas = false;
+
+ tools::Long nY = 0;
+ bool bGrow = false;
+
+ maInvalidRect = tools::Rectangle(); // clear
+ for ( sal_uInt32 nPara = 0; nPara < mpTEParaPortions->Count(); ++nPara )
+ {
+ TEParaPortion* pTEParaPortion = mpTEParaPortions->GetObject( nPara );
+ if ( pTEParaPortion->IsInvalid() )
+ {
+ const tools::Long nOldParaWidth = mnCurTextWidth >= 0 ? CalcTextWidth( nPara ) : -1;
+
+ Broadcast( TextHint( SfxHintId::TextFormatPara, nPara ) );
+
+ if ( CreateLines( nPara ) )
+ bGrow = true;
+
+ // set InvalidRect only once
+ if ( maInvalidRect.IsEmpty() )
+ {
+ // otherwise remains Empty() for Paperwidth 0 (AutoPageSize)
+ const tools::Long nWidth = mnMaxTextWidth
+ ? mnMaxTextWidth
+ : std::numeric_limits<tools::Long>::max();
+ const Range aInvRange( GetInvalidYOffsets( nPara ) );
+ maInvalidRect = tools::Rectangle( Point( 0, nY+aInvRange.Min() ),
+ Size( nWidth, aInvRange.Len() ) );
+ }
+ else
+ {
+ maInvalidRect.SetBottom( nY + CalcParaHeight( nPara ) );
+ }
+
+ if ( mnCurTextWidth >= 0 )
+ {
+ const tools::Long nNewParaWidth = CalcTextWidth( nPara );
+ if ( nNewParaWidth >= mnCurTextWidth )
+ mnCurTextWidth = nNewParaWidth;
+ else if ( nOldParaWidth >= mnCurTextWidth )
+ mnCurTextWidth = -1;
+ }
+ }
+ else if ( bGrow )
+ {
+ maInvalidRect.SetBottom( nY + CalcParaHeight( nPara ) );
+ }
+ nY += CalcParaHeight( nPara );
+ if ( !mbHasMultiLineParas && pTEParaPortion->GetLines().size() > 1 )
+ mbHasMultiLineParas = true;
+ }
+
+ if ( !maInvalidRect.IsEmpty() )
+ {
+ const tools::Long nNewHeight = CalcTextHeight();
+ const tools::Long nDiff = nNewHeight - mnCurTextHeight;
+ if ( nNewHeight < mnCurTextHeight )
+ {
+ maInvalidRect.SetBottom( std::max( nNewHeight, mnCurTextHeight ) );
+ if ( maInvalidRect.IsEmpty() )
+ {
+ maInvalidRect.SetTop( 0 );
+ // Left and Right are not evaluated, but set because of IsEmpty
+ maInvalidRect.SetLeft( 0 );
+ maInvalidRect.SetRight( mnMaxTextWidth );
+ }
+ }
+
+ mnCurTextHeight = nNewHeight;
+ if ( nDiff )
+ {
+ mbFormatted = true;
+ Broadcast( TextHint( SfxHintId::TextHeightChanged ) );
+ }
+ }
+
+ mbIsFormatting = false;
+ mbFormatted = true;
+
+ Broadcast( TextHint( SfxHintId::TextFormatted ) );
+}
+
+void TextEngine::CreateAndInsertEmptyLine( sal_uInt32 nPara )
+{
+ TextNode* pNode = mpDoc->GetNodes()[ nPara ].get();
+ TEParaPortion* pTEParaPortion = mpTEParaPortions->GetObject( nPara );
+
+ TextLine aTmpLine;
+ aTmpLine.SetStart( pNode->GetText().getLength() );
+ aTmpLine.SetEnd( aTmpLine.GetStart() );
+
+ if ( ImpGetAlign() == TxtAlign::Center )
+ aTmpLine.SetStartX( static_cast<short>(mnMaxTextWidth / 2) );
+ else if ( ImpGetAlign() == TxtAlign::Right )
+ aTmpLine.SetStartX( static_cast<short>(mnMaxTextWidth) );
+ else
+ aTmpLine.SetStartX( mpDoc->GetLeftMargin() );
+
+ bool bLineBreak = !pNode->GetText().isEmpty();
+
+ TETextPortion aDummyPortion( 0 );
+ aDummyPortion.GetWidth() = 0;
+ pTEParaPortion->GetTextPortions().push_back( aDummyPortion );
+
+ if ( bLineBreak )
+ {
+ // -2: The new one is already inserted.
+ const std::size_t nPos = pTEParaPortion->GetTextPortions().size() - 1;
+ aTmpLine.SetStartPortion( nPos );
+ aTmpLine.SetEndPortion( nPos );
+ }
+ pTEParaPortion->GetLines().push_back( aTmpLine );
+}
+
+void TextEngine::ImpBreakLine( sal_uInt32 nPara, TextLine* pLine, sal_Int32 nPortionStart, tools::Long nRemainingWidth )
+{
+ TextNode* pNode = mpDoc->GetNodes()[ nPara ].get();
+
+ // Font still should be adjusted
+ sal_Int32 nMaxBreakPos = mpRefDev->GetTextBreak( pNode->GetText(), nRemainingWidth, nPortionStart );
+
+ SAL_WARN_IF( nMaxBreakPos >= pNode->GetText().getLength(), "vcl", "ImpBreakLine: Break?!" );
+
+ if ( nMaxBreakPos == -1 ) // GetTextBreak() != GetTextSize()
+ nMaxBreakPos = pNode->GetText().getLength() - 1;
+
+ uno::Reference < i18n::XBreakIterator > xBI = GetBreakIterator();
+ i18n::LineBreakHyphenationOptions aHyphOptions( nullptr, uno::Sequence< beans::PropertyValue >(), 1 );
+
+ i18n::LineBreakUserOptions aUserOptions;
+ aUserOptions.forbiddenBeginCharacters = ImpGetLocaleDataWrapper()->getForbiddenCharacters().beginLine;
+ aUserOptions.forbiddenEndCharacters = ImpGetLocaleDataWrapper()->getForbiddenCharacters().endLine;
+ aUserOptions.applyForbiddenRules = true;
+ aUserOptions.allowPunctuationOutsideMargin = false;
+ aUserOptions.allowHyphenateEnglish = false;
+
+ static const css::lang::Locale aDefLocale;
+ i18n::LineBreakResults aLBR = xBI->getLineBreak( pNode->GetText(), nMaxBreakPos, aDefLocale, pLine->GetStart(), aHyphOptions, aUserOptions );
+ sal_Int32 nBreakPos = aLBR.breakIndex;
+ if ( nBreakPos <= pLine->GetStart() )
+ {
+ nBreakPos = nMaxBreakPos;
+ if ( nBreakPos <= pLine->GetStart() )
+ nBreakPos = pLine->GetStart() + 1; // infinite loop otherwise!
+ }
+
+ // the damaged Portion is the End Portion
+ pLine->SetEnd( nBreakPos );
+ const std::size_t nEndPortion = SplitTextPortion( nPara, nBreakPos );
+
+ if ( nBreakPos >= pLine->GetStart() &&
+ nBreakPos < pNode->GetText().getLength() &&
+ pNode->GetText()[ nBreakPos ] == ' ' )
+ {
+ // generally suppress blanks at the end of line
+ TEParaPortion* pTEParaPortion = mpTEParaPortions->GetObject( nPara );
+ TETextPortion& rTP = pTEParaPortion->GetTextPortions()[ nEndPortion ];
+ SAL_WARN_IF( nBreakPos <= pLine->GetStart(), "vcl", "ImpBreakLine: SplitTextPortion at beginning of line?" );
+ rTP.GetWidth() = CalcTextWidth( nPara, nBreakPos-rTP.GetLen(), rTP.GetLen()-1 );
+ }
+ pLine->SetEndPortion( nEndPortion );
+}
+
+std::size_t TextEngine::SplitTextPortion( sal_uInt32 nPara, sal_Int32 nPos )
+{
+
+ // the Portion at nPos is being split, unless there is already a switch at nPos
+ if ( nPos == 0 )
+ return 0;
+
+ std::size_t nSplitPortion;
+ sal_Int32 nTmpPos = 0;
+ TETextPortion* pTextPortion = nullptr;
+ TEParaPortion* pTEParaPortion = mpTEParaPortions->GetObject( nPara );
+ const std::size_t nPortions = pTEParaPortion->GetTextPortions().size();
+ for ( nSplitPortion = 0; nSplitPortion < nPortions; nSplitPortion++ )
+ {
+ TETextPortion& rTP = pTEParaPortion->GetTextPortions()[nSplitPortion];
+ nTmpPos += rTP.GetLen();
+ if ( nTmpPos >= nPos )
+ {
+ if ( nTmpPos == nPos ) // nothing needs splitting
+ return nSplitPortion;
+ pTextPortion = &rTP;
+ break;
+ }
+ }
+
+ assert(pTextPortion && "SplitTextPortion: position outside of region!");
+
+ const sal_Int32 nOverlapp = nTmpPos - nPos;
+ pTextPortion->GetLen() -= nOverlapp;
+ pTextPortion->GetWidth() = CalcTextWidth( nPara, nPos-pTextPortion->GetLen(), pTextPortion->GetLen() );
+ TETextPortion aNewPortion( nOverlapp );
+ pTEParaPortion->GetTextPortions().insert( pTEParaPortion->GetTextPortions().begin() + nSplitPortion + 1, aNewPortion );
+
+ return nSplitPortion;
+}
+
+void TextEngine::CreateTextPortions( sal_uInt32 nPara, sal_Int32 nStartPos )
+{
+ TEParaPortion* pTEParaPortion = mpTEParaPortions->GetObject( nPara );
+ TextNode* pNode = pTEParaPortion->GetNode();
+ SAL_WARN_IF( pNode->GetText().isEmpty(), "vcl", "CreateTextPortions: should not be used for empty paragraphs!" );
+
+ o3tl::sorted_vector<sal_Int32> aPositions;
+ o3tl::sorted_vector<sal_Int32>::const_iterator aPositionsIt;
+ aPositions.insert(0);
+
+ const sal_uInt16 nAttribs = pNode->GetCharAttribs().Count();
+ for ( sal_uInt16 nAttr = 0; nAttr < nAttribs; nAttr++ )
+ {
+ TextCharAttrib& rAttrib = pNode->GetCharAttribs().GetAttrib( nAttr );
+
+ aPositions.insert( rAttrib.GetStart() );
+ aPositions.insert( rAttrib.GetEnd() );
+ }
+ aPositions.insert( pNode->GetText().getLength() );
+
+ const std::vector<TEWritingDirectionInfo>& rWritingDirections = pTEParaPortion->GetWritingDirectionInfos();
+ for ( const auto& rWritingDirection : rWritingDirections )
+ aPositions.insert( rWritingDirection.nStartPos );
+
+ if ( mpIMEInfos && mpIMEInfos->pAttribs && ( mpIMEInfos->aPos.GetPara() == nPara ) )
+ {
+ ExtTextInputAttr nLastAttr = ExtTextInputAttr(0xffff);
+ for( sal_Int32 n = 0; n < mpIMEInfos->nLen; n++ )
+ {
+ if ( mpIMEInfos->pAttribs[n] != nLastAttr )
+ {
+ aPositions.insert( mpIMEInfos->aPos.GetIndex() + n );
+ nLastAttr = mpIMEInfos->pAttribs[n];
+ }
+ }
+ }
+
+ sal_Int32 nTabPos = pNode->GetText().indexOf( '\t' );
+ while ( nTabPos != -1 )
+ {
+ aPositions.insert( nTabPos );
+ aPositions.insert( nTabPos + 1 );
+ nTabPos = pNode->GetText().indexOf( '\t', nTabPos+1 );
+ }
+
+ // Delete starting with...
+ // Unfortunately, the number of TextPortions does not have to be
+ // equal to aPositions.Count(), because of linebreaks
+ sal_Int32 nPortionStart = 0;
+ std::size_t nInvPortion = 0;
+ std::size_t nP;
+ for ( nP = 0; nP < pTEParaPortion->GetTextPortions().size(); nP++ )
+ {
+ TETextPortion& rTmpPortion = pTEParaPortion->GetTextPortions()[nP];
+ nPortionStart += rTmpPortion.GetLen();
+ if ( nPortionStart >= nStartPos )
+ {
+ nPortionStart -= rTmpPortion.GetLen();
+ nInvPortion = nP;
+ break;
+ }
+ }
+ OSL_ENSURE(nP < pTEParaPortion->GetTextPortions().size()
+ || pTEParaPortion->GetTextPortions().empty(),
+ "CreateTextPortions: Nothing to delete!");
+ if ( nInvPortion && ( nPortionStart+pTEParaPortion->GetTextPortions()[nInvPortion].GetLen() > nStartPos ) )
+ {
+ // better one before...
+ // But only if it was within the Portion; otherwise it might be
+ // the only one in the previous line!
+ nInvPortion--;
+ nPortionStart -= pTEParaPortion->GetTextPortions()[nInvPortion].GetLen();
+ }
+ pTEParaPortion->GetTextPortions().DeleteFromPortion( nInvPortion );
+
+ // a Portion might have been created by a line break
+ aPositions.insert( nPortionStart );
+
+ aPositionsIt = aPositions.find( nPortionStart );
+ SAL_WARN_IF( aPositionsIt == aPositions.end(), "vcl", "CreateTextPortions: nPortionStart not found" );
+
+ if ( aPositionsIt != aPositions.end() )
+ {
+ o3tl::sorted_vector<sal_Int32>::const_iterator nextIt = aPositionsIt;
+ for ( ++nextIt; nextIt != aPositions.end(); ++aPositionsIt, ++nextIt )
+ {
+ TETextPortion aNew( *nextIt - *aPositionsIt );
+ pTEParaPortion->GetTextPortions().push_back( aNew );
+ }
+ }
+ OSL_ENSURE(pTEParaPortion->GetTextPortions().size(), "CreateTextPortions: No Portions?!");
+}
+
+void TextEngine::RecalcTextPortion( sal_uInt32 nPara, sal_Int32 nStartPos, sal_Int32 nNewChars )
+{
+ TEParaPortion* pTEParaPortion = mpTEParaPortions->GetObject( nPara );
+ OSL_ENSURE(pTEParaPortion->GetTextPortions().size(), "RecalcTextPortion: no Portions!");
+ OSL_ENSURE(nNewChars, "RecalcTextPortion: Diff == 0");
+
+ TextNode* const pNode = pTEParaPortion->GetNode();
+ if ( nNewChars > 0 )
+ {
+ // If an Attribute is starting/ending at nStartPos, or there is a tab
+ // before nStartPos => a new Portion starts.
+ // Otherwise the Portion is extended at nStartPos.
+ // Or if at the very beginning ( StartPos 0 ) followed by a tab...
+ if ( ( pNode->GetCharAttribs().HasBoundingAttrib( nStartPos ) ) ||
+ ( nStartPos && ( pNode->GetText()[ nStartPos - 1 ] == '\t' ) ) ||
+ ( !nStartPos && ( nNewChars < pNode->GetText().getLength() ) && pNode->GetText()[ nNewChars ] == '\t' ) )
+ {
+ std::size_t nNewPortionPos = 0;
+ if ( nStartPos )
+ nNewPortionPos = SplitTextPortion( nPara, nStartPos ) + 1;
+
+ // Here could be an empty Portion if the paragraph was empty,
+ // or a new line was created by a hard line-break.
+ if ( ( nNewPortionPos < pTEParaPortion->GetTextPortions().size() ) &&
+ !pTEParaPortion->GetTextPortions()[nNewPortionPos].GetLen() )
+ {
+ // use the empty Portion
+ pTEParaPortion->GetTextPortions()[nNewPortionPos].GetLen() = nNewChars;
+ }
+ else
+ {
+ TETextPortion aNewPortion( nNewChars );
+ pTEParaPortion->GetTextPortions().insert( pTEParaPortion->GetTextPortions().begin() + nNewPortionPos, aNewPortion );
+ }
+ }
+ else
+ {
+ sal_Int32 nPortionStart {0};
+ const std::size_t nTP = pTEParaPortion->GetTextPortions().FindPortion( nStartPos, nPortionStart );
+ TETextPortion& rTP = pTEParaPortion->GetTextPortions()[ nTP ];
+ rTP.GetLen() += nNewChars;
+ rTP.GetWidth() = -1;
+ }
+ }
+ else
+ {
+ // Shrink or remove Portion
+ // Before calling this function, ensure that no Portions were in the deleted range!
+
+ // There must be no Portion reaching into or starting within,
+ // thus: nStartPos <= nPos <= nStartPos - nNewChars(neg.)
+ std::size_t nPortion = 0;
+ sal_Int32 nPos = 0;
+ const sal_Int32 nEnd = nStartPos-nNewChars;
+ const std::size_t nPortions = pTEParaPortion->GetTextPortions().size();
+ TETextPortion* pTP = nullptr;
+ for ( nPortion = 0; nPortion < nPortions; nPortion++ )
+ {
+ pTP = &pTEParaPortion->GetTextPortions()[ nPortion ];
+ if ( ( nPos+pTP->GetLen() ) > nStartPos )
+ {
+ SAL_WARN_IF( nPos > nStartPos, "vcl", "RecalcTextPortion: Bad Start!" );
+ SAL_WARN_IF( nPos+pTP->GetLen() < nEnd, "vcl", "RecalcTextPortion: Bad End!" );
+ break;
+ }
+ nPos += pTP->GetLen();
+ }
+ SAL_WARN_IF( !pTP, "vcl", "RecalcTextPortion: Portion not found!" );
+ if ( ( nPos == nStartPos ) && ( (nPos+pTP->GetLen()) == nEnd ) )
+ {
+ // remove Portion
+ pTEParaPortion->GetTextPortions().erase( pTEParaPortion->GetTextPortions().begin() + nPortion );
+ }
+ else
+ {
+ SAL_WARN_IF( pTP->GetLen() <= (-nNewChars), "vcl", "RecalcTextPortion: Portion too small to shrink!" );
+ pTP->GetLen() += nNewChars;
+ }
+ OSL_ENSURE( pTEParaPortion->GetTextPortions().size(),
+ "RecalcTextPortion: none are left!" );
+ }
+}
+
+void TextEngine::ImpPaint( OutputDevice* pOutDev, const Point& rStartPos, tools::Rectangle const* pPaintArea, TextSelection const* pSelection )
+{
+ if ( !GetUpdateMode() )
+ return;
+
+ if ( !IsFormatted() )
+ FormatDoc();
+
+ vcl::Window* const pOutWin = pOutDev->GetOwnerWindow();
+ const bool bTransparent = (pOutWin && pOutWin->IsPaintTransparent());
+
+ tools::Long nY = rStartPos.Y();
+
+ TextPaM const* pSelStart = nullptr;
+ TextPaM const* pSelEnd = nullptr;
+ if ( pSelection && pSelection->HasRange() )
+ {
+ const bool bInvers = pSelection->GetEnd() < pSelection->GetStart();
+ pSelStart = !bInvers ? &pSelection->GetStart() : &pSelection->GetEnd();
+ pSelEnd = bInvers ? &pSelection->GetStart() : &pSelection->GetEnd();
+ }
+
+ const StyleSettings& rStyleSettings = pOutDev->GetSettings().GetStyleSettings();
+
+ // for all paragraphs
+ for ( sal_uInt32 nPara = 0; nPara < mpTEParaPortions->Count(); ++nPara )
+ {
+ TEParaPortion* pPortion = mpTEParaPortions->GetObject( nPara );
+ // in case while typing Idle-Formatting, asynchronous Paint
+ if ( pPortion->IsInvalid() )
+ return;
+
+ const tools::Long nParaHeight = CalcParaHeight( nPara );
+ if ( !pPaintArea || ( ( nY + nParaHeight ) > pPaintArea->Top() ) )
+ {
+ // for all lines of the paragraph
+ sal_Int32 nIndex = 0;
+ for ( auto & rLine : pPortion->GetLines() )
+ {
+ Point aTmpPos( rStartPos.X() + rLine.GetStartX(), nY );
+
+ if ( !pPaintArea || ( ( nY + mnCharHeight ) > pPaintArea->Top() ) )
+ {
+ // for all Portions of the line
+ nIndex = rLine.GetStart();
+ for ( std::size_t y = rLine.GetStartPortion(); y <= rLine.GetEndPortion(); y++ )
+ {
+ OSL_ENSURE(pPortion->GetTextPortions().size(),
+ "ImpPaint: Line without Textportion!");
+ TETextPortion& rTextPortion = pPortion->GetTextPortions()[ y ];
+
+ ImpInitLayoutMode( pOutDev /*, pTextPortion->IsRightToLeft() */);
+
+ const tools::Long nTxtWidth = rTextPortion.GetWidth();
+ aTmpPos.setX( rStartPos.X() + ImpGetOutputOffset( nPara, &rLine, nIndex, nIndex ) );
+
+ // only print if starting in the visible region
+ if ( ( aTmpPos.X() + nTxtWidth ) >= 0 )
+ {
+ switch ( rTextPortion.GetKind() )
+ {
+ case PORTIONKIND_TEXT:
+ {
+ vcl::Font aFont;
+ SeekCursor( nPara, nIndex+1, aFont, pOutDev );
+ if( bTransparent )
+ aFont.SetTransparent( true );
+ else if ( pSelection )
+ aFont.SetTransparent( false );
+ pOutDev->SetFont( aFont );
+
+ sal_Int32 nTmpIndex = nIndex;
+ sal_Int32 nEnd = nTmpIndex + rTextPortion.GetLen();
+ Point aPos = aTmpPos;
+
+ bool bDone = false;
+ if ( pSelStart )
+ {
+ // is a part of it in the selection?
+ const TextPaM aTextStart( nPara, nTmpIndex );
+ const TextPaM aTextEnd( nPara, nEnd );
+ if ( ( aTextStart < *pSelEnd ) && ( aTextEnd > *pSelStart ) )
+ {
+ // 1) vcl::Region before Selection
+ if ( aTextStart < *pSelStart )
+ {
+ const sal_Int32 nL = pSelStart->GetIndex() - nTmpIndex;
+ pOutDev->SetFont( aFont);
+ pOutDev->SetTextFillColor();
+ aPos.setX( rStartPos.X() + ImpGetOutputOffset( nPara, &rLine, nTmpIndex, nTmpIndex+nL ) );
+ pOutDev->DrawText( aPos, pPortion->GetNode()->GetText(), nTmpIndex, nL );
+ nTmpIndex = nTmpIndex + nL;
+
+ }
+ // 2) vcl::Region with Selection
+ sal_Int32 nL = nEnd - nTmpIndex;
+ if ( aTextEnd > *pSelEnd )
+ nL = pSelEnd->GetIndex() - nTmpIndex;
+ if ( nL )
+ {
+ const Color aOldTextColor = pOutDev->GetTextColor();
+ pOutDev->SetTextColor( rStyleSettings.GetHighlightTextColor() );
+ pOutDev->SetTextFillColor( rStyleSettings.GetHighlightColor() );
+ aPos.setX( rStartPos.X() + ImpGetOutputOffset( nPara, &rLine, nTmpIndex, nTmpIndex+nL ) );
+ pOutDev->DrawText( aPos, pPortion->GetNode()->GetText(), nTmpIndex, nL );
+ pOutDev->SetTextColor( aOldTextColor );
+ pOutDev->SetTextFillColor();
+ nTmpIndex = nTmpIndex + nL;
+ }
+
+ // 3) vcl::Region after Selection
+ if ( nTmpIndex < nEnd )
+ {
+ nL = nEnd-nTmpIndex;
+ pOutDev->SetTextFillColor();
+ aPos.setX( rStartPos.X() + ImpGetOutputOffset( nPara, &rLine, nTmpIndex, nTmpIndex+nL ) );
+ pOutDev->DrawText( aPos, pPortion->GetNode()->GetText(), nTmpIndex, nEnd-nTmpIndex );
+ }
+ bDone = true;
+ }
+ }
+ if ( !bDone )
+ {
+ pOutDev->SetTextFillColor();
+ aPos.setX( rStartPos.X() + ImpGetOutputOffset( nPara, &rLine, nTmpIndex, nEnd ) );
+ pOutDev->DrawText( aPos, pPortion->GetNode()->GetText(), nTmpIndex, nEnd-nTmpIndex );
+ }
+ }
+ break;
+ case PORTIONKIND_TAB:
+ // for HideSelection() only Range, pSelection = 0.
+ if ( pSelStart ) // also implies pSelEnd
+ {
+ const tools::Rectangle aTabArea( aTmpPos, Point( aTmpPos.X()+nTxtWidth, aTmpPos.Y()+mnCharHeight-1 ) );
+ // is the Tab in the Selection???
+ const TextPaM aTextStart(nPara, nIndex);
+ const TextPaM aTextEnd(nPara, nIndex + 1);
+ if ((aTextStart < *pSelEnd) && (aTextEnd > *pSelStart))
+ {
+ const Color aOldColor = pOutDev->GetFillColor();
+ pOutDev->SetFillColor(
+ rStyleSettings.GetHighlightColor());
+ pOutDev->DrawRect(aTabArea);
+ pOutDev->SetFillColor(aOldColor);
+ }
+ else
+ {
+ pOutDev->Erase( aTabArea );
+ }
+ }
+ break;
+ default:
+ OSL_FAIL( "ImpPaint: Unknown Portion-Type !" );
+ }
+ }
+
+ nIndex += rTextPortion.GetLen();
+ }
+ }
+
+ nY += mnCharHeight;
+
+ if ( pPaintArea && ( nY >= pPaintArea->Bottom() ) )
+ break; // no more visible actions
+ }
+ }
+ else
+ {
+ nY += nParaHeight;
+ }
+
+ if ( pPaintArea && ( nY > pPaintArea->Bottom() ) )
+ break; // no more visible actions
+ }
+}
+
+bool TextEngine::CreateLines( sal_uInt32 nPara )
+{
+ // bool: changing Height of Paragraph Yes/No - true/false
+
+ TextNode* pNode = mpDoc->GetNodes()[ nPara ].get();
+ TEParaPortion* pTEParaPortion = mpTEParaPortions->GetObject( nPara );
+ SAL_WARN_IF( !pTEParaPortion->IsInvalid(), "vcl", "CreateLines: Portion not invalid!" );
+
+ const auto nOldLineCount = pTEParaPortion->GetLines().size();
+
+ // fast special case for empty paragraphs
+ if ( pTEParaPortion->GetNode()->GetText().isEmpty() )
+ {
+ if ( !pTEParaPortion->GetTextPortions().empty() )
+ pTEParaPortion->GetTextPortions().Reset();
+ pTEParaPortion->GetLines().clear();
+ CreateAndInsertEmptyLine( nPara );
+ pTEParaPortion->SetValid();
+ return nOldLineCount != pTEParaPortion->GetLines().size();
+ }
+
+ // initialization
+ if ( pTEParaPortion->GetLines().empty() )
+ {
+ pTEParaPortion->GetLines().emplace_back( );
+ }
+
+ const sal_Int32 nInvalidDiff = pTEParaPortion->GetInvalidDiff();
+ const sal_Int32 nInvalidStart = pTEParaPortion->GetInvalidPosStart();
+ const sal_Int32 nInvalidEnd = nInvalidStart + std::abs( nInvalidDiff );
+ bool bQuickFormat = false;
+
+ if ( pTEParaPortion->GetWritingDirectionInfos().empty() )
+ ImpInitWritingDirections( nPara );
+
+ if ( pTEParaPortion->GetWritingDirectionInfos().size() == 1 && pTEParaPortion->IsSimpleInvalid() )
+ {
+ bQuickFormat = nInvalidDiff != 0;
+ if ( nInvalidDiff < 0 )
+ {
+ // check if deleting across Portion border
+ sal_Int32 nPos = 0;
+ for ( const auto & rTP : pTEParaPortion->GetTextPortions() )
+ {
+ // there must be no Start/End in the deleted region
+ nPos += rTP.GetLen();
+ if ( nPos > nInvalidStart && nPos < nInvalidEnd )
+ {
+ bQuickFormat = false;
+ break;
+ }
+ }
+ }
+ }
+
+ if ( bQuickFormat )
+ RecalcTextPortion( nPara, nInvalidStart, nInvalidDiff );
+ else
+ CreateTextPortions( nPara, nInvalidStart );
+
+ // search for line with InvalidPos; start a line prior
+ // flag lines => do not remove!
+
+ sal_uInt16 nLine = pTEParaPortion->GetLines().size()-1;
+ for ( sal_uInt16 nL = 0; nL <= nLine; nL++ )
+ {
+ TextLine& rLine = pTEParaPortion->GetLines()[ nL ];
+ if ( rLine.GetEnd() > nInvalidStart )
+ {
+ nLine = nL;
+ break;
+ }
+ rLine.SetValid();
+ }
+ // start a line before...
+ // if typing at the end, the line before cannot change
+ if ( nLine && ( !pTEParaPortion->IsSimpleInvalid() || ( nInvalidEnd < pNode->GetText().getLength() ) || ( nInvalidDiff <= 0 ) ) )
+ nLine--;
+
+ TextLine* pLine = &( pTEParaPortion->GetLines()[ nLine ] );
+
+ // format all lines starting here
+ std::size_t nDelFromLine = TETextPortionList::npos;
+
+ sal_Int32 nIndex = pLine->GetStart();
+ TextLine aSaveLine( *pLine );
+
+ while ( nIndex < pNode->GetText().getLength() )
+ {
+ bool bEOL = false;
+ sal_Int32 nPortionStart = 0;
+ sal_Int32 nPortionEnd = 0;
+
+ sal_Int32 nTmpPos = nIndex;
+ std::size_t nTmpPortion = pLine->GetStartPortion();
+ tools::Long nTmpWidth = mpDoc->GetLeftMargin();
+ // do not subtract margin; it is included in TmpWidth
+ tools::Long nXWidth = std::max(
+ mnMaxTextWidth ? mnMaxTextWidth : std::numeric_limits<tools::Long>::max(), nTmpWidth);
+
+ // search for Portion that does not fit anymore into line
+ TETextPortion* pPortion = nullptr;
+ bool bBrokenLine = false;
+
+ while ( ( nTmpWidth <= nXWidth ) && !bEOL && ( nTmpPortion < pTEParaPortion->GetTextPortions().size() ) )
+ {
+ nPortionStart = nTmpPos;
+ pPortion = &pTEParaPortion->GetTextPortions()[ nTmpPortion ];
+ SAL_WARN_IF( !pPortion->GetLen(), "vcl", "CreateLines: Empty Portion!" );
+ if ( pNode->GetText()[ nTmpPos ] == '\t' )
+ {
+ tools::Long nCurPos = nTmpWidth-mpDoc->GetLeftMargin();
+ nTmpWidth = ((nCurPos/mnDefTab)+1)*mnDefTab+mpDoc->GetLeftMargin();
+ pPortion->GetWidth() = nTmpWidth - nCurPos - mpDoc->GetLeftMargin();
+ // infinite loop, if this is the first token of the line and nTmpWidth > aPaperSize.Width !!!
+ if ( ( nTmpWidth >= nXWidth ) && ( nTmpPortion == pLine->GetStartPortion() ) )
+ {
+ // adjust Tab
+ pPortion->GetWidth() = nXWidth-1;
+ nTmpWidth = pPortion->GetWidth();
+ bEOL = true;
+ bBrokenLine = true;
+ }
+ pPortion->GetKind() = PORTIONKIND_TAB;
+ }
+ else
+ {
+
+ pPortion->GetWidth() = CalcTextWidth( nPara, nTmpPos, pPortion->GetLen() );
+ nTmpWidth += pPortion->GetWidth();
+
+ pPortion->SetRightToLeft( ImpGetRightToLeft( nPara, nTmpPos+1 ) );
+ pPortion->GetKind() = PORTIONKIND_TEXT;
+ }
+
+ nTmpPos += pPortion->GetLen();
+ nPortionEnd = nTmpPos;
+ nTmpPortion++;
+ }
+
+ // this was perhaps one Portion too far
+ bool bFixedEnd = false;
+ if ( nTmpWidth > nXWidth )
+ {
+ assert(pPortion);
+
+ nPortionEnd = nTmpPos;
+ nTmpPos -= pPortion->GetLen();
+ nPortionStart = nTmpPos;
+ nTmpPortion--;
+ bEOL = false;
+
+ nTmpWidth -= pPortion->GetWidth();
+ if ( pPortion->GetKind() == PORTIONKIND_TAB )
+ {
+ bEOL = true;
+ bFixedEnd = true;
+ }
+ }
+ else
+ {
+ bEOL = true;
+ pLine->SetEnd( nPortionEnd );
+ OSL_ENSURE(pTEParaPortion->GetTextPortions().size(),
+ "CreateLines: No TextPortions?");
+ pLine->SetEndPortion( pTEParaPortion->GetTextPortions().size() - 1 );
+ }
+
+ if ( bFixedEnd )
+ {
+ pLine->SetEnd( nPortionStart );
+ pLine->SetEndPortion( nTmpPortion-1 );
+ }
+ else if ( bBrokenLine )
+ {
+ pLine->SetEnd( nPortionStart+1 );
+ pLine->SetEndPortion( nTmpPortion-1 );
+ }
+ else if ( !bEOL )
+ {
+ SAL_WARN_IF( (nPortionEnd-nPortionStart) != pPortion->GetLen(), "vcl", "CreateLines: There is a Portion after all?!" );
+ const tools::Long nRemainingWidth = mnMaxTextWidth - nTmpWidth;
+ ImpBreakLine( nPara, pLine, nPortionStart, nRemainingWidth );
+ }
+
+ if ( ( ImpGetAlign() == TxtAlign::Center ) || ( ImpGetAlign() == TxtAlign::Right ) )
+ {
+ // adjust
+ tools::Long nTextWidth = 0;
+ for ( std::size_t nTP = pLine->GetStartPortion(); nTP <= pLine->GetEndPortion(); nTP++ )
+ {
+ TETextPortion& rTextPortion = pTEParaPortion->GetTextPortions()[ nTP ];
+ nTextWidth += rTextPortion.GetWidth();
+ }
+ const tools::Long nSpace = mnMaxTextWidth - nTextWidth;
+ if ( nSpace > 0 )
+ {
+ if ( ImpGetAlign() == TxtAlign::Center )
+ pLine->SetStartX( static_cast<sal_uInt16>(nSpace / 2) );
+ else // TxtAlign::Right
+ pLine->SetStartX( static_cast<sal_uInt16>(nSpace) );
+ }
+ }
+ else
+ {
+ pLine->SetStartX( mpDoc->GetLeftMargin() );
+ }
+
+ // check if the line has to be printed again
+ pLine->SetInvalid();
+
+ if ( pTEParaPortion->IsSimpleInvalid() )
+ {
+ // Change due to simple TextChange...
+ // Do not abort formatting, as Portions might have to be split!
+ // Once it is ok to abort, then validate the following lines!
+ // But mark as valid, thus reduce printing...
+ if ( pLine->GetEnd() < nInvalidStart )
+ {
+ if ( *pLine == aSaveLine )
+ {
+ pLine->SetValid();
+ }
+ }
+ else
+ {
+ const sal_Int32 nStart = pLine->GetStart();
+ const sal_Int32 nEnd = pLine->GetEnd();
+
+ if ( nStart > nInvalidEnd )
+ {
+ if ( ( ( nStart-nInvalidDiff ) == aSaveLine.GetStart() ) &&
+ ( ( nEnd-nInvalidDiff ) == aSaveLine.GetEnd() ) )
+ {
+ pLine->SetValid();
+ if ( bQuickFormat )
+ {
+ pTEParaPortion->CorrectValuesBehindLastFormattedLine( nLine );
+ break;
+ }
+ }
+ }
+ else if ( bQuickFormat && ( nEnd > nInvalidEnd) )
+ {
+ // If the invalid line ends such that the next line starts
+ // at the 'same' position as before (no change in line breaks),
+ // the text width does not have to be recalculated.
+ if ( nEnd == ( aSaveLine.GetEnd() + nInvalidDiff ) )
+ {
+ pTEParaPortion->CorrectValuesBehindLastFormattedLine( nLine );
+ break;
+ }
+ }
+ }
+ }
+
+ nIndex = pLine->GetEnd(); // next line Start = previous line End
+ // because nEnd is past the last char!
+
+ const std::size_t nEndPortion = pLine->GetEndPortion();
+
+ // next line or new line
+ pLine = nullptr;
+ if ( nLine < pTEParaPortion->GetLines().size()-1 )
+ pLine = &( pTEParaPortion->GetLines()[ ++nLine ] );
+ if ( pLine && ( nIndex >= pNode->GetText().getLength() ) )
+ {
+ nDelFromLine = nLine;
+ break;
+ }
+ if ( !pLine )
+ {
+ if ( nIndex < pNode->GetText().getLength() )
+ {
+ ++nLine;
+ pTEParaPortion->GetLines().insert( pTEParaPortion->GetLines().begin() + nLine, TextLine() );
+ pLine = &pTEParaPortion->GetLines()[nLine];
+ }
+ else
+ {
+ break;
+ }
+ }
+ aSaveLine = *pLine;
+ pLine->SetStart( nIndex );
+ pLine->SetEnd( nIndex );
+ pLine->SetStartPortion( nEndPortion+1 );
+ pLine->SetEndPortion( nEndPortion+1 );
+
+ } // while ( Index < Len )
+
+ if (nDelFromLine != TETextPortionList::npos)
+ {
+ pTEParaPortion->GetLines().erase( pTEParaPortion->GetLines().begin() + nDelFromLine,
+ pTEParaPortion->GetLines().end() );
+ }
+
+ SAL_WARN_IF( pTEParaPortion->GetLines().empty(), "vcl", "CreateLines: No Line!" );
+
+ pTEParaPortion->SetValid();
+
+ return nOldLineCount != pTEParaPortion->GetLines().size();
+}
+
+OUString TextEngine::GetWord( const TextPaM& rCursorPos, TextPaM* pStartOfWord, TextPaM* pEndOfWord )
+{
+ OUString aWord;
+ if ( rCursorPos.GetPara() < mpDoc->GetNodes().size() )
+ {
+ TextSelection aSel( rCursorPos );
+ TextNode* pNode = mpDoc->GetNodes()[ rCursorPos.GetPara() ].get();
+ uno::Reference < i18n::XBreakIterator > xBI = GetBreakIterator();
+ i18n::Boundary aBoundary = xBI->getWordBoundary( pNode->GetText(), rCursorPos.GetIndex(), GetLocale(), i18n::WordType::ANYWORD_IGNOREWHITESPACES, true );
+ // tdf#57879 - expand selection to the left to include connector punctuations and search for additional word boundaries
+ if (aBoundary.startPos > 0 && aBoundary.startPos < pNode->GetText().getLength() && u_charType(pNode->GetText()[aBoundary.startPos]) == U_CONNECTOR_PUNCTUATION)
+ {
+ aBoundary.startPos = xBI->getWordBoundary(pNode->GetText(), aBoundary.startPos - 1,
+ GetLocale(), css::i18n::WordType::ANYWORD_IGNOREWHITESPACES, true).startPos;
+ }
+ while (aBoundary.startPos > 0 && u_charType(pNode->GetText()[aBoundary.startPos - 1]) == U_CONNECTOR_PUNCTUATION)
+ {
+ aBoundary.startPos = std::min(aBoundary.startPos,
+ xBI->getWordBoundary( pNode->GetText(), aBoundary.startPos - 2,
+ GetLocale(), css::i18n::WordType::ANYWORD_IGNOREWHITESPACES, true).startPos);
+ }
+ // tdf#57879 - expand selection to the right to include connector punctuations and search for additional word boundaries
+ if (aBoundary.endPos > 0 && aBoundary.endPos < pNode->GetText().getLength() && u_charType(pNode->GetText()[aBoundary.endPos - 1]) == U_CONNECTOR_PUNCTUATION)
+ {
+ aBoundary.endPos = xBI->getWordBoundary(pNode->GetText(), aBoundary.endPos,
+ GetLocale(), css::i18n::WordType::ANYWORD_IGNOREWHITESPACES, true).endPos;
+ }
+ while (aBoundary.endPos < pNode->GetText().getLength() && u_charType(pNode->GetText()[aBoundary.endPos]) == U_CONNECTOR_PUNCTUATION)
+ {
+ aBoundary.endPos = xBI->getWordBoundary(pNode->GetText(), aBoundary.endPos + 1,
+ GetLocale(), css::i18n::WordType::ANYWORD_IGNOREWHITESPACES, true).endPos;
+ }
+ aSel.GetStart().GetIndex() = aBoundary.startPos;
+ aSel.GetEnd().GetIndex() = aBoundary.endPos;
+ aWord = pNode->GetText().copy( aSel.GetStart().GetIndex(), aSel.GetEnd().GetIndex() - aSel.GetStart().GetIndex() );
+ if ( pStartOfWord )
+ *pStartOfWord = aSel.GetStart();
+ if (pEndOfWord)
+ *pEndOfWord = aSel.GetEnd();
+ }
+ return aWord;
+}
+
+bool TextEngine::Read( SvStream& rInput, const TextSelection* pSel )
+{
+ const bool bUpdate = GetUpdateMode();
+ SetUpdateMode( false );
+
+ UndoActionStart();
+ TextSelection aSel;
+ if ( pSel )
+ aSel = *pSel;
+ else
+ {
+ const sal_uInt32 nParas = static_cast<sal_uInt32>(mpDoc->GetNodes().size());
+ TextNode* pNode = mpDoc->GetNodes()[ nParas - 1 ].get();
+ aSel = TextPaM( nParas-1 , pNode->GetText().getLength() );
+ }
+
+ if ( aSel.HasRange() )
+ aSel = ImpDeleteText( aSel );
+
+ OStringBuffer aLine;
+ bool bDone = rInput.ReadLine( aLine );
+ OUString aTmpStr(OStringToOUString(aLine, rInput.GetStreamCharSet()));
+ while ( bDone )
+ {
+ aSel = ImpInsertText( aSel, aTmpStr );
+ bDone = rInput.ReadLine( aLine );
+ aTmpStr = OStringToOUString(aLine, rInput.GetStreamCharSet());
+ if ( bDone )
+ aSel = ImpInsertParaBreak( aSel.GetEnd() );
+ }
+
+ UndoActionEnd();
+
+ const TextSelection aNewSel( aSel.GetEnd(), aSel.GetEnd() );
+
+ // so that FormatAndUpdate does not access the invalid selection
+ if ( GetActiveView() )
+ GetActiveView()->ImpSetSelection( aNewSel );
+
+ SetUpdateMode( bUpdate );
+ FormatAndUpdate( GetActiveView() );
+
+ return rInput.GetError() == ERRCODE_NONE;
+}
+
+void TextEngine::Write( SvStream& rOutput )
+{
+ TextSelection aSel;
+ const sal_uInt32 nParas = static_cast<sal_uInt32>(mpDoc->GetNodes().size());
+ TextNode* pSelNode = mpDoc->GetNodes()[ nParas - 1 ].get();
+ aSel.GetStart() = TextPaM( 0, 0 );
+ aSel.GetEnd() = TextPaM( nParas-1, pSelNode->GetText().getLength() );
+
+ for ( sal_uInt32 nPara = aSel.GetStart().GetPara(); nPara <= aSel.GetEnd().GetPara(); ++nPara )
+ {
+ TextNode* pNode = mpDoc->GetNodes()[ nPara ].get();
+
+ const sal_Int32 nStartPos = nPara == aSel.GetStart().GetPara()
+ ? aSel.GetStart().GetIndex() : 0;
+ const sal_Int32 nEndPos = nPara == aSel.GetEnd().GetPara()
+ ? aSel.GetEnd().GetIndex() : pNode->GetText().getLength();
+
+ const OUString aText = pNode->GetText().copy( nStartPos, nEndPos-nStartPos );
+ rOutput.WriteLine(OUStringToOString(aText, rOutput.GetStreamCharSet()));
+ }
+}
+
+void TextEngine::RemoveAttribs( sal_uInt32 nPara )
+{
+ if ( nPara >= mpDoc->GetNodes().size() )
+ return;
+
+ TextNode* pNode = mpDoc->GetNodes()[ nPara ].get();
+ if ( pNode->GetCharAttribs().Count() )
+ {
+ pNode->GetCharAttribs().Clear();
+
+ TEParaPortion* pTEParaPortion = mpTEParaPortions->GetObject( nPara );
+ pTEParaPortion->MarkSelectionInvalid( 0 );
+
+ mbFormatted = false;
+
+ IdleFormatAndUpdate( nullptr, 0xFFFF );
+ }
+}
+
+void TextEngine::SetAttrib( const TextAttrib& rAttr, sal_uInt32 nPara, sal_Int32 nStart, sal_Int32 nEnd )
+{
+
+ // For now do not check if Attributes overlap!
+ // This function is for TextEditors that want to _quickly_ generate the Syntax-Highlight
+
+ // As TextEngine is currently intended only for TextEditors, there is no Undo for Attributes!
+
+ if ( nPara >= mpDoc->GetNodes().size() )
+ return;
+
+ TextNode* pNode = mpDoc->GetNodes()[ nPara ].get();
+ TEParaPortion* pTEParaPortion = mpTEParaPortions->GetObject( nPara );
+
+ const sal_Int32 nMax = pNode->GetText().getLength();
+ if ( nStart > nMax )
+ nStart = nMax;
+ if ( nEnd > nMax )
+ nEnd = nMax;
+
+ pNode->GetCharAttribs().InsertAttrib( std::make_unique<TextCharAttrib>( rAttr, nStart, nEnd ) );
+ pTEParaPortion->MarkSelectionInvalid( nStart );
+
+ mbFormatted = false;
+ IdleFormatAndUpdate( nullptr, 0xFFFF );
+}
+
+void TextEngine::SetTextAlign( TxtAlign eAlign )
+{
+ if ( eAlign != meAlign )
+ {
+ meAlign = eAlign;
+ FormatFullDoc();
+ UpdateViews();
+ }
+}
+
+void TextEngine::ValidateSelection( TextSelection& rSel ) const
+{
+ ValidatePaM( rSel.GetStart() );
+ ValidatePaM( rSel.GetEnd() );
+}
+
+void TextEngine::ValidatePaM( TextPaM& rPaM ) const
+{
+ const sal_uInt32 nParas = static_cast<sal_uInt32>(mpDoc->GetNodes().size());
+ if ( rPaM.GetPara() >= nParas )
+ {
+ rPaM.GetPara() = nParas ? nParas-1 : 0;
+ rPaM.GetIndex() = TEXT_INDEX_ALL;
+ }
+
+ const sal_Int32 nMaxIndex = GetTextLen( rPaM.GetPara() );
+ if ( rPaM.GetIndex() > nMaxIndex )
+ rPaM.GetIndex() = nMaxIndex;
+}
+
+// adjust State & Selection
+
+void TextEngine::ImpParagraphInserted( sal_uInt32 nPara )
+{
+ // No adjustment needed for the active View;
+ // but for all passive Views the Selection needs adjusting.
+ if ( mpViews->size() > 1 )
+ {
+ for ( auto nView = mpViews->size(); nView; )
+ {
+ TextView* pView = (*mpViews)[ --nView ];
+ if ( pView != GetActiveView() )
+ {
+ for ( int n = 0; n <= 1; n++ )
+ {
+ TextPaM& rPaM = n ? pView->GetSelection().GetStart(): pView->GetSelection().GetEnd();
+ if ( rPaM.GetPara() >= nPara )
+ rPaM.GetPara()++;
+ }
+ }
+ }
+ }
+ Broadcast( TextHint( SfxHintId::TextParaInserted, nPara ) );
+}
+
+void TextEngine::ImpParagraphRemoved( sal_uInt32 nPara )
+{
+ if ( mpViews->size() > 1 )
+ {
+ for ( auto nView = mpViews->size(); nView; )
+ {
+ TextView* pView = (*mpViews)[ --nView ];
+ if ( pView != GetActiveView() )
+ {
+ const sal_uInt32 nParas = static_cast<sal_uInt32>(mpDoc->GetNodes().size());
+ for ( int n = 0; n <= 1; n++ )
+ {
+ TextPaM& rPaM = n ? pView->GetSelection().GetStart(): pView->GetSelection().GetEnd();
+ if ( rPaM.GetPara() > nPara )
+ rPaM.GetPara()--;
+ else if ( rPaM.GetPara() == nPara )
+ {
+ rPaM.GetIndex() = 0;
+ if ( rPaM.GetPara() >= nParas )
+ rPaM.GetPara()--;
+ }
+ }
+ }
+ }
+ }
+ Broadcast( TextHint( SfxHintId::TextParaRemoved, nPara ) );
+}
+
+void TextEngine::ImpCharsRemoved( sal_uInt32 nPara, sal_Int32 nPos, sal_Int32 nChars )
+{
+ if ( mpViews->size() > 1 )
+ {
+ for ( auto nView = mpViews->size(); nView; )
+ {
+ TextView* pView = (*mpViews)[ --nView ];
+ if ( pView != GetActiveView() )
+ {
+ const sal_Int32 nEnd = nPos + nChars;
+ for ( int n = 0; n <= 1; n++ )
+ {
+ TextPaM& rPaM = n ? pView->GetSelection().GetStart(): pView->GetSelection().GetEnd();
+ if ( rPaM.GetPara() == nPara )
+ {
+ if ( rPaM.GetIndex() > nEnd )
+ rPaM.GetIndex() = rPaM.GetIndex() - nChars;
+ else if ( rPaM.GetIndex() > nPos )
+ rPaM.GetIndex() = nPos;
+ }
+ }
+ }
+ }
+ }
+ Broadcast( TextHint( SfxHintId::TextParaContentChanged, nPara ) );
+}
+
+void TextEngine::ImpCharsInserted( sal_uInt32 nPara, sal_Int32 nPos, sal_Int32 nChars )
+{
+ if ( mpViews->size() > 1 )
+ {
+ for ( auto nView = mpViews->size(); nView; )
+ {
+ TextView* pView = (*mpViews)[ --nView ];
+ if ( pView != GetActiveView() )
+ {
+ for ( int n = 0; n <= 1; n++ )
+ {
+ TextPaM& rPaM = n ? pView->GetSelection().GetStart(): pView->GetSelection().GetEnd();
+ if ( rPaM.GetPara() == nPara )
+ {
+ if ( rPaM.GetIndex() >= nPos )
+ rPaM.GetIndex() += nChars;
+ }
+ }
+ }
+ }
+ }
+ Broadcast( TextHint( SfxHintId::TextParaContentChanged, nPara ) );
+}
+
+void TextEngine::Draw( OutputDevice* pDev, const Point& rPos )
+{
+ ImpPaint( pDev, rPos, nullptr );
+}
+
+void TextEngine::SetLeftMargin( sal_uInt16 n )
+{
+ mpDoc->SetLeftMargin( n );
+}
+
+uno::Reference< i18n::XBreakIterator > const & TextEngine::GetBreakIterator()
+{
+ if ( !mxBreakIterator.is() )
+ mxBreakIterator = vcl::unohelper::CreateBreakIterator();
+ SAL_WARN_IF( !mxBreakIterator.is(), "vcl", "BreakIterator: Failed to create!" );
+ return mxBreakIterator;
+}
+
+void TextEngine::SetLocale( const css::lang::Locale& rLocale )
+{
+ maLocale = rLocale;
+ mpLocaleDataWrapper.reset();
+}
+
+css::lang::Locale const & TextEngine::GetLocale()
+{
+ if ( maLocale.Language.isEmpty() )
+ {
+ maLocale = Application::GetSettings().GetUILanguageTag().getLocale(); // TODO: why UI locale?
+ }
+ return maLocale;
+}
+
+LocaleDataWrapper* TextEngine::ImpGetLocaleDataWrapper()
+{
+ if ( !mpLocaleDataWrapper )
+ mpLocaleDataWrapper.reset( new LocaleDataWrapper( LanguageTag( GetLocale()) ) );
+
+ return mpLocaleDataWrapper.get();
+}
+
+void TextEngine::SetRightToLeft( bool bR2L )
+{
+ if ( mbRightToLeft != bR2L )
+ {
+ mbRightToLeft = bR2L;
+ meAlign = bR2L ? TxtAlign::Right : TxtAlign::Left;
+ FormatFullDoc();
+ UpdateViews();
+ }
+}
+
+void TextEngine::ImpInitWritingDirections( sal_uInt32 nPara )
+{
+ TEParaPortion* pParaPortion = mpTEParaPortions->GetObject( nPara );
+ std::vector<TEWritingDirectionInfo>& rInfos = pParaPortion->GetWritingDirectionInfos();
+ rInfos.clear();
+
+ if ( !pParaPortion->GetNode()->GetText().isEmpty() )
+ {
+ const UBiDiLevel nBidiLevel = IsRightToLeft() ? 1 /*RTL*/ : 0 /*LTR*/;
+ OUString aText( pParaPortion->GetNode()->GetText() );
+
+ // Bidi functions from icu 2.0
+
+ UErrorCode nError = U_ZERO_ERROR;
+ UBiDi* pBidi = ubidi_openSized( aText.getLength(), 0, &nError );
+ nError = U_ZERO_ERROR;
+
+ ubidi_setPara( pBidi, reinterpret_cast<const UChar *>(aText.getStr()), aText.getLength(), nBidiLevel, nullptr, &nError );
+ nError = U_ZERO_ERROR;
+
+ tools::Long nCount = ubidi_countRuns( pBidi, &nError );
+
+ int32_t nStart = 0;
+ int32_t nEnd;
+ UBiDiLevel nCurrDir;
+
+ for ( tools::Long nIdx = 0; nIdx < nCount; ++nIdx )
+ {
+ ubidi_getLogicalRun( pBidi, nStart, &nEnd, &nCurrDir );
+ // bit 0 of nCurrDir indicates direction
+ rInfos.emplace_back( /*bLeftToRight*/ nCurrDir % 2 == 0, nStart, nEnd );
+ nStart = nEnd;
+ }
+
+ ubidi_close( pBidi );
+ }
+
+ // No infos mean no CTL and default dir is L2R...
+ if ( rInfos.empty() )
+ rInfos.emplace_back( 0, 0, pParaPortion->GetNode()->GetText().getLength() );
+
+}
+
+bool TextEngine::ImpGetRightToLeft( sal_uInt32 nPara, sal_Int32 nPos )
+{
+ bool bRightToLeft = false;
+
+ TextNode* pNode = mpDoc->GetNodes()[ nPara ].get();
+ if ( pNode && !pNode->GetText().isEmpty() )
+ {
+ TEParaPortion* pParaPortion = mpTEParaPortions->GetObject( nPara );
+ if ( pParaPortion->GetWritingDirectionInfos().empty() )
+ ImpInitWritingDirections( nPara );
+
+ std::vector<TEWritingDirectionInfo>& rDirInfos = pParaPortion->GetWritingDirectionInfos();
+ for ( const auto& rWritingDirectionInfo : rDirInfos )
+ {
+ if ( rWritingDirectionInfo.nStartPos <= nPos && rWritingDirectionInfo.nEndPos >= nPos )
+ {
+ bRightToLeft = !rWritingDirectionInfo.bLeftToRight;
+ break;
+ }
+ }
+ }
+ return bRightToLeft;
+}
+
+tools::Long TextEngine::ImpGetPortionXOffset( sal_uInt32 nPara, TextLine const * pLine, std::size_t nTextPortion )
+{
+ tools::Long nX = pLine->GetStartX();
+
+ TEParaPortion* pParaPortion = mpTEParaPortions->GetObject( nPara );
+
+ for ( std::size_t i = pLine->GetStartPortion(); i < nTextPortion; i++ )
+ {
+ TETextPortion& rPortion = pParaPortion->GetTextPortions()[ i ];
+ nX += rPortion.GetWidth();
+ }
+
+ TETextPortion& rDestPortion = pParaPortion->GetTextPortions()[ nTextPortion ];
+ if ( rDestPortion.GetKind() != PORTIONKIND_TAB )
+ {
+ if ( !IsRightToLeft() && rDestPortion.IsRightToLeft() )
+ {
+ // Portions behind must be added, visual before this portion
+ std::size_t nTmpPortion = nTextPortion+1;
+ while ( nTmpPortion <= pLine->GetEndPortion() )
+ {
+ TETextPortion& rNextTextPortion = pParaPortion->GetTextPortions()[ nTmpPortion ];
+ if ( rNextTextPortion.IsRightToLeft() && ( rNextTextPortion.GetKind() != PORTIONKIND_TAB ) )
+ nX += rNextTextPortion.GetWidth();
+ else
+ break;
+ nTmpPortion++;
+ }
+ // Portions before must be removed, visual behind this portion
+ nTmpPortion = nTextPortion;
+ while ( nTmpPortion > pLine->GetStartPortion() )
+ {
+ --nTmpPortion;
+ TETextPortion& rPrevTextPortion = pParaPortion->GetTextPortions()[ nTmpPortion ];
+ if ( rPrevTextPortion.IsRightToLeft() && ( rPrevTextPortion.GetKind() != PORTIONKIND_TAB ) )
+ nX -= rPrevTextPortion.GetWidth();
+ else
+ break;
+ }
+ }
+ else if ( IsRightToLeft() && !rDestPortion.IsRightToLeft() )
+ {
+ // Portions behind must be removed, visual behind this portion
+ std::size_t nTmpPortion = nTextPortion+1;
+ while ( nTmpPortion <= pLine->GetEndPortion() )
+ {
+ TETextPortion& rNextTextPortion = pParaPortion->GetTextPortions()[ nTmpPortion ];
+ if ( !rNextTextPortion.IsRightToLeft() && ( rNextTextPortion.GetKind() != PORTIONKIND_TAB ) )
+ nX += rNextTextPortion.GetWidth();
+ else
+ break;
+ nTmpPortion++;
+ }
+ // Portions before must be added, visual before this portion
+ nTmpPortion = nTextPortion;
+ while ( nTmpPortion > pLine->GetStartPortion() )
+ {
+ --nTmpPortion;
+ TETextPortion& rPrevTextPortion = pParaPortion->GetTextPortions()[ nTmpPortion ];
+ if ( !rPrevTextPortion.IsRightToLeft() && ( rPrevTextPortion.GetKind() != PORTIONKIND_TAB ) )
+ nX -= rPrevTextPortion.GetWidth();
+ else
+ break;
+ }
+ }
+ }
+
+ return nX;
+}
+
+void TextEngine::ImpInitLayoutMode( OutputDevice* pOutDev )
+{
+ vcl::text::ComplexTextLayoutFlags nLayoutMode = pOutDev->GetLayoutMode();
+
+ nLayoutMode &= ~vcl::text::ComplexTextLayoutFlags(vcl::text::ComplexTextLayoutFlags::BiDiRtl | vcl::text::ComplexTextLayoutFlags::BiDiStrong );
+
+ pOutDev->SetLayoutMode( nLayoutMode );
+}
+
+TxtAlign TextEngine::ImpGetAlign() const
+{
+ TxtAlign eAlign = meAlign;
+ if ( IsRightToLeft() )
+ {
+ if ( eAlign == TxtAlign::Left )
+ eAlign = TxtAlign::Right;
+ else if ( eAlign == TxtAlign::Right )
+ eAlign = TxtAlign::Left;
+ }
+ return eAlign;
+}
+
+tools::Long TextEngine::ImpGetOutputOffset( sal_uInt32 nPara, TextLine* pLine, sal_Int32 nIndex, sal_Int32 nIndex2 )
+{
+ TEParaPortion* pPortion = mpTEParaPortions->GetObject( nPara );
+
+ sal_Int32 nPortionStart {0};
+ const std::size_t nPortion = pPortion->GetTextPortions().FindPortion( nIndex, nPortionStart, true );
+
+ TETextPortion& rTextPortion = pPortion->GetTextPortions()[ nPortion ];
+
+ tools::Long nX;
+
+ if ( ( nIndex == nPortionStart ) && ( nIndex == nIndex2 ) )
+ {
+ // Output of full portion, so we need portion x offset.
+ // Use ImpGetPortionXOffset, because GetXPos may deliver left or right position from portion, depending on R2L, L2R
+ nX = ImpGetPortionXOffset( nPara, pLine, nPortion );
+ if ( IsRightToLeft() )
+ {
+ nX = -nX - rTextPortion.GetWidth();
+ }
+ }
+ else
+ {
+ nX = ImpGetXPos( nPara, pLine, nIndex, nIndex == nPortionStart );
+ if ( nIndex2 != nIndex )
+ {
+ const tools::Long nX2 = ImpGetXPos( nPara, pLine, nIndex2 );
+ if ( ( !IsRightToLeft() && ( nX2 < nX ) ) ||
+ ( IsRightToLeft() && ( nX2 > nX ) ) )
+ {
+ nX = nX2;
+ }
+ }
+ if ( IsRightToLeft() )
+ {
+ nX = -nX;
+ }
+ }
+
+ return nX;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/edit/textund2.hxx b/vcl/source/edit/textund2.hxx
new file mode 100644
index 0000000000..7aea9af6b8
--- /dev/null
+++ b/vcl/source/edit/textund2.hxx
@@ -0,0 +1,105 @@
+/* -*- 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 .
+ */
+#pragma once
+
+#include "textundo.hxx"
+#include <vcl/textdata.hxx>
+
+class TextUndoDelPara : public TextUndo
+{
+private:
+ bool mbDelObject;
+ sal_uInt32 mnPara;
+ TextNode* mpNode; // points at the valid not-destroyed object
+
+public:
+ TextUndoDelPara( TextEngine* pTextEngine, TextNode* pNode, sal_uInt32 nPara );
+ virtual ~TextUndoDelPara() override;
+
+ virtual void Undo() override;
+ virtual void Redo() override;
+
+ virtual OUString GetComment () const override;
+};
+
+class TextUndoConnectParas : public TextUndo
+{
+private:
+ sal_uInt32 mnPara;
+ sal_Int32 mnSepPos;
+
+public:
+ TextUndoConnectParas( TextEngine* pTextEngine, sal_uInt32 nPara, sal_Int32 nSepPos );
+ virtual ~TextUndoConnectParas() override;
+
+ virtual void Undo() override;
+ virtual void Redo() override;
+
+ virtual OUString GetComment () const override;
+};
+
+class TextUndoSplitPara : public TextUndo
+{
+private:
+ sal_uInt32 mnPara;
+ sal_Int32 mnSepPos;
+
+public:
+ TextUndoSplitPara( TextEngine* pTextEngine, sal_uInt32 nPara, sal_Int32 nSepPos );
+ virtual ~TextUndoSplitPara() override;
+
+ virtual void Undo() override;
+ virtual void Redo() override;
+
+ virtual OUString GetComment () const override;
+};
+
+class TextUndoInsertChars : public TextUndo
+{
+private:
+ TextPaM maTextPaM;
+ OUString maText;
+
+public:
+ TextUndoInsertChars( TextEngine* pTextEngine, const TextPaM& rTextPaM, OUString aStr );
+
+ virtual void Undo() override;
+ virtual void Redo() override;
+
+ virtual bool Merge( SfxUndoAction *pNextAction ) override;
+
+ virtual OUString GetComment () const override;
+};
+
+class TextUndoRemoveChars : public TextUndo
+{
+private:
+ TextPaM maTextPaM;
+ OUString maText;
+
+public:
+ TextUndoRemoveChars( TextEngine* pTextEngine, const TextPaM& rTextPaM, OUString aStr );
+
+ virtual void Undo() override;
+ virtual void Redo() override;
+
+ virtual OUString GetComment () const override;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/edit/textundo.cxx b/vcl/source/edit/textundo.cxx
new file mode 100644
index 0000000000..e53e60e5d5
--- /dev/null
+++ b/vcl/source/edit/textundo.cxx
@@ -0,0 +1,331 @@
+/* -*- 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 "textundo.hxx"
+#include "textund2.hxx"
+#include <strings.hrc>
+
+#include <sal/log.hxx>
+#include <utility>
+#include <vcl/texteng.hxx>
+#include <vcl/textview.hxx>
+#include <vcl/textdata.hxx>
+#include "textdoc.hxx"
+#include "textdat2.hxx"
+#include <svdata.hxx>
+
+namespace
+{
+
+// Shorten() -- inserts ellipsis (...) in the middle of a long text
+void Shorten (OUString& rString)
+{
+ auto const nLen = rString.getLength();
+ if (nLen <= 48)
+ return;
+
+ // If possible, we don't break a word, hence first we look for a space.
+ // Space before the ellipsis:
+ auto iFirst = rString.lastIndexOf(' ', 32);
+ if (iFirst == -1 || iFirst < 16)
+ iFirst = 24; // not possible
+ // Space after the ellipsis:
+ auto iLast = rString.indexOf(' ', nLen - 16);
+ if (iLast == -1 || iLast > nLen - 4)
+ iLast = nLen - 8; // not possible
+ // finally:
+ rString =
+ OUString::Concat(rString.subView(0, iFirst + 1)) +
+ "..." +
+ rString.subView(iLast);
+}
+
+} // namespace
+
+TextUndoManager::TextUndoManager( TextEngine* p )
+{
+ mpTextEngine = p;
+}
+
+TextUndoManager::~TextUndoManager()
+{
+}
+
+bool TextUndoManager::Undo()
+{
+ if ( GetUndoActionCount() == 0 )
+ return false;
+
+ UndoRedoStart();
+
+ mpTextEngine->SetIsInUndo( true );
+ bool bDone = SfxUndoManager::Undo();
+ mpTextEngine->SetIsInUndo( false );
+
+ UndoRedoEnd();
+
+ return bDone;
+}
+
+bool TextUndoManager::Redo()
+{
+ if ( GetRedoActionCount() == 0 )
+ return false;
+
+ UndoRedoStart();
+
+ mpTextEngine->SetIsInUndo( true );
+ bool bDone = SfxUndoManager::Redo();
+ mpTextEngine->SetIsInUndo( false );
+
+ UndoRedoEnd();
+
+ return bDone;
+}
+
+void TextUndoManager::UndoRedoStart()
+{
+ SAL_WARN_IF( !GetView(), "vcl", "Undo/Redo: Active View?" );
+}
+
+void TextUndoManager::UndoRedoEnd()
+{
+ if ( GetView() )
+ {
+ TextSelection aNewSel( GetView()->GetSelection() );
+ aNewSel.GetStart() = aNewSel.GetEnd();
+ GetView()->ImpSetSelection( aNewSel );
+ }
+
+ mpTextEngine->FormatAndUpdate( GetView() );
+}
+
+TextUndo::TextUndo( TextEngine* p )
+{
+ mpTextEngine = p;
+}
+
+TextUndo::~TextUndo()
+{
+}
+
+OUString TextUndo::GetComment() const
+{
+ return OUString();
+}
+
+void TextUndo::SetSelection( const TextSelection& rSel )
+{
+ if ( GetView() )
+ GetView()->ImpSetSelection( rSel );
+}
+
+TextUndoDelPara::TextUndoDelPara( TextEngine* pTextEngine, TextNode* pNode, sal_uInt32 nPara )
+ : TextUndo( pTextEngine )
+ , mbDelObject( true)
+ , mnPara( nPara )
+ , mpNode( pNode )
+{
+}
+
+TextUndoDelPara::~TextUndoDelPara()
+{
+ if ( mbDelObject )
+ delete mpNode;
+}
+
+void TextUndoDelPara::Undo()
+{
+ GetTextEngine()->InsertContent( std::unique_ptr<TextNode>(mpNode), mnPara );
+ mbDelObject = false; // belongs again to the engine
+
+ if ( GetView() )
+ {
+ TextSelection aSel( TextPaM( mnPara, 0 ), TextPaM( mnPara, mpNode->GetText().getLength() ) );
+ SetSelection( aSel );
+ }
+}
+
+void TextUndoDelPara::Redo()
+{
+ auto & rDocNodes = GetDoc()->GetNodes();
+ // pNode is not valid anymore in case an Undo joined paragraphs
+ mpNode = rDocNodes[ mnPara ].get();
+
+ GetTEParaPortions()->Remove( mnPara );
+
+ // do not delete Node because of Undo!
+ auto it = ::std::find_if( rDocNodes.begin(), rDocNodes.end(),
+ [&] (std::unique_ptr<TextNode> const & p) { return p.get() == mpNode; } );
+ assert(it != rDocNodes.end());
+ // coverity[leaked_storage : FALSE] - ownership transferred to this with mbDelObject
+ it->release();
+ GetDoc()->GetNodes().erase( it );
+
+ GetTextEngine()->ImpParagraphRemoved( mnPara );
+
+ mbDelObject = true; // belongs again to the Undo
+
+ const sal_uInt32 nParas = static_cast<sal_uInt32>(GetDoc()->GetNodes().size());
+ const sal_uInt32 n = mnPara < nParas ? mnPara : nParas-1;
+ TextNode* pN = GetDoc()->GetNodes()[ n ].get();
+ TextPaM aPaM( n, pN->GetText().getLength() );
+ SetSelection( aPaM );
+}
+
+OUString TextUndoDelPara::GetComment () const
+{
+ return VclResId(STR_TEXTUNDO_DELPARA);
+}
+
+TextUndoConnectParas::TextUndoConnectParas( TextEngine* pTextEngine, sal_uInt32 nPara, sal_Int32 nPos )
+ : TextUndo( pTextEngine )
+ , mnPara( nPara )
+ , mnSepPos( nPos )
+{
+}
+
+TextUndoConnectParas::~TextUndoConnectParas()
+{
+}
+
+void TextUndoConnectParas::Undo()
+{
+ TextPaM aPaM = GetTextEngine()->SplitContent( mnPara, mnSepPos );
+ SetSelection( aPaM );
+}
+
+void TextUndoConnectParas::Redo()
+{
+ TextPaM aPaM = GetTextEngine()->ConnectContents( mnPara );
+ SetSelection( aPaM );
+}
+
+OUString TextUndoConnectParas::GetComment () const
+{
+ return VclResId(STR_TEXTUNDO_CONNECTPARAS);
+}
+
+TextUndoSplitPara::TextUndoSplitPara( TextEngine* pTextEngine, sal_uInt32 nPara, sal_Int32 nPos )
+ : TextUndo( pTextEngine )
+ , mnPara( nPara )
+ , mnSepPos ( nPos )
+{
+}
+
+TextUndoSplitPara::~TextUndoSplitPara()
+{
+}
+
+void TextUndoSplitPara::Undo()
+{
+ TextPaM aPaM = GetTextEngine()->ConnectContents( mnPara );
+ SetSelection( aPaM );
+}
+
+void TextUndoSplitPara::Redo()
+{
+ TextPaM aPaM = GetTextEngine()->SplitContent( mnPara, mnSepPos );
+ SetSelection( aPaM );
+}
+
+OUString TextUndoSplitPara::GetComment () const
+{
+ return VclResId(STR_TEXTUNDO_SPLITPARA);
+}
+
+TextUndoInsertChars::TextUndoInsertChars( TextEngine* pTextEngine, const TextPaM& rTextPaM, OUString aStr )
+ : TextUndo( pTextEngine ),
+ maTextPaM( rTextPaM ), maText(std::move( aStr ))
+{
+}
+
+void TextUndoInsertChars::Undo()
+{
+ TextSelection aSel( maTextPaM, maTextPaM );
+ aSel.GetEnd().GetIndex() += maText.getLength();
+ TextPaM aPaM = GetTextEngine()->ImpDeleteText( aSel );
+ SetSelection( aPaM );
+}
+
+void TextUndoInsertChars::Redo()
+{
+ TextSelection aSel( maTextPaM, maTextPaM );
+ GetTextEngine()->ImpInsertText( aSel, maText );
+ TextPaM aNewPaM( maTextPaM );
+ aNewPaM.GetIndex() += maText.getLength();
+ SetSelection( TextSelection( aSel.GetStart(), aNewPaM ) );
+}
+
+bool TextUndoInsertChars::Merge( SfxUndoAction* pNextAction )
+{
+ TextUndoInsertChars* pNext = dynamic_cast<TextUndoInsertChars*>(pNextAction);
+ if ( !pNext )
+ return false;
+
+ if ( maTextPaM.GetPara() != pNext->maTextPaM.GetPara() )
+ return false;
+
+ if ( ( maTextPaM.GetIndex() + maText.getLength() ) == pNext->maTextPaM.GetIndex() )
+ {
+ maText += pNext->maText;
+ return true;
+ }
+ return false;
+}
+
+OUString TextUndoInsertChars::GetComment () const
+{
+ // multiple lines?
+ OUString sText(maText);
+ Shorten(sText);
+ return VclResId(STR_TEXTUNDO_INSERTCHARS).replaceAll("$1", sText);
+}
+
+TextUndoRemoveChars::TextUndoRemoveChars( TextEngine* pTextEngine, const TextPaM& rTextPaM, OUString aStr )
+ : TextUndo( pTextEngine ),
+ maTextPaM( rTextPaM ), maText(std::move( aStr ))
+{
+}
+
+void TextUndoRemoveChars::Undo()
+{
+ TextSelection aSel( maTextPaM, maTextPaM );
+ GetTextEngine()->ImpInsertText( aSel, maText );
+ aSel.GetEnd().GetIndex() += maText.getLength();
+ SetSelection( aSel );
+}
+
+void TextUndoRemoveChars::Redo()
+{
+ TextSelection aSel( maTextPaM, maTextPaM );
+ aSel.GetEnd().GetIndex() += maText.getLength();
+ TextPaM aPaM = GetTextEngine()->ImpDeleteText( aSel );
+ SetSelection( aPaM );
+}
+
+OUString TextUndoRemoveChars::GetComment () const
+{
+ // multiple lines?
+ OUString sText(maText);
+ Shorten(sText);
+ return VclResId(STR_TEXTUNDO_REMOVECHARS).replaceAll("$1", sText);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/edit/textundo.hxx b/vcl/source/edit/textundo.hxx
new file mode 100644
index 0000000000..571a16c2ce
--- /dev/null
+++ b/vcl/source/edit/textundo.hxx
@@ -0,0 +1,77 @@
+/* -*- 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 .
+ */
+#pragma once
+
+#include <svl/undo.hxx>
+#include <vcl/texteng.hxx>
+
+class TextEngine;
+class TextView;
+class TextSelection;
+class TextDoc;
+class TEParaPortions;
+
+class TextUndoManager : public SfxUndoManager
+{
+ TextEngine* mpTextEngine;
+
+protected:
+
+ void UndoRedoStart();
+ void UndoRedoEnd();
+
+ TextView* GetView() const { return mpTextEngine->GetActiveView(); }
+
+public:
+ explicit TextUndoManager( TextEngine* pTextEngine );
+ virtual ~TextUndoManager() override;
+
+ using SfxUndoManager::Undo;
+ virtual bool Undo() override;
+ using SfxUndoManager::Redo;
+ virtual bool Redo() override;
+
+};
+
+class TextUndo : public SfxUndoAction
+{
+private:
+ TextEngine* mpTextEngine;
+
+protected:
+
+ TextView* GetView() const { return mpTextEngine->GetActiveView(); }
+ void SetSelection( const TextSelection& rSel );
+
+ TextDoc* GetDoc() const { return mpTextEngine->mpDoc.get(); }
+ TEParaPortions* GetTEParaPortions() const { return mpTextEngine->mpTEParaPortions.get(); }
+
+public:
+ explicit TextUndo( TextEngine* pTextEngine );
+ virtual ~TextUndo() override;
+
+ TextEngine* GetTextEngine() const { return mpTextEngine; }
+
+ virtual void Undo() override = 0;
+ virtual void Redo() override = 0;
+
+ virtual OUString GetComment() const override;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/edit/textview.cxx b/vcl/source/edit/textview.cxx
new file mode 100644
index 0000000000..ad1d28d199
--- /dev/null
+++ b/vcl/source/edit/textview.cxx
@@ -0,0 +1,2238 @@
+/* -*- 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 <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/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 <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 );
+}
+
+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 mbCursorEnabled : 1;
+ bool mbClickedInSelection : 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->mbAutoIndent = false;
+ mpImpl->mbCursorEnabled = true;
+ mpImpl->mbClickedInSelection = false;
+// mbInSelection = false;
+
+ mpImpl->mnTravelXPos = TRAVEL_X_DONTKNOW;
+
+ mpImpl->mpSelFuncSet = std::make_unique<TextSelFunctionSet>( this );
+ mpImpl->mpSelEngine = std::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 ) );
+
+ pWindow->GetOutDev()->SetLineColor();
+
+ if ( pWindow->GetDragGestureRecognizer().is() )
+ {
+ mpImpl->mxDnDListener = new vcl::unohelper::DragAndDropWrapper( this );
+
+ 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.SetAlpha(255);
+ 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->maSelection.HasRange())
+ pDrawSelection = &mpImpl->maSelection;
+
+ Point aStartPos = ImpGetOutputStartPos(mpImpl->maStartDocPos);
+ ImpPaint(rRenderContext, aStartPos, &rRect, pDrawSelection);
+}
+
+void TextView::ImpSetSelection( const TextSelection& rSelection )
+{
+ if (rSelection == mpImpl->maSelection)
+ return;
+
+ 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() )
+ return;
+
+ 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(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 ( !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();
+ 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( u"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( u"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( OUStringChar(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(GetSystemPrimarySelection());
+ 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(GetSystemPrimarySelection());
+ 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 ))
+ return;
+
+ 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();
+ // tdf#57879 - expand selection to include connector punctuations
+ TextSelection aNewSel;
+ mpImpl->mpTextEngine->GetWord( mpImpl->maSelection.GetEnd(), &aNewSel.GetStart(), &aNewSel.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 = std::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 ) );
+
+ tools::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( tools::Long ndX, tools::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 );
+
+ tools::Long nDiffX = mpImpl->maStartDocPos.X() - aNewStartPos.X();
+ tools::Long nDiffY = mpImpl->maStartDocPos.Y() - aNewStartPos.Y();
+
+ if ( nDiffX || nDiffY )
+ {
+ bool bVisCursor = mpImpl->mpCursor->IsVisible();
+ mpImpl->mpCursor->Hide();
+ mpImpl->mpWindow->PaintImmediately();
+ mpImpl->maStartDocPos = aNewStartPos;
+
+ if ( mpImpl->mpTextEngine->IsRightToLeft() )
+ nDiffX = -nDiffX;
+ mpImpl->mpWindow->Scroll( nDiffX, nDiffY );
+ mpImpl->mpWindow->PaintImmediately();
+ 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() )
+ 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( 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() const
+{
+ return GetSelected( GetSystemLineEnd() );
+}
+
+OUString TextView::GetSelected( LineEnd aSeparator ) const
+{
+ 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:
+ 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
+ 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::CursorFirstWord( const TextPaM& rPaM )
+{
+ TextPaM aPaM(rPaM);
+ TextNode* pNode = mpImpl->mpTextEngine->mpDoc->GetNodes()[aPaM.GetPara()].get();
+
+ css::uno::Reference<css::i18n::XBreakIterator> xBI = mpImpl->mpTextEngine->GetBreakIterator();
+ aPaM.GetIndex() = xBI->beginOfSentence(pNode->GetText(), 0, mpImpl->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
+ mpImpl->mpTextEngine->GetWord( rPaM, &aPaM );
+ if ( aPaM.GetIndex() >= rPaM.GetIndex() )
+ {
+ TextNode* pNode = mpImpl->mpTextEngine->mpDoc->GetNodes()[ aPaM.GetPara() ].get();
+ css::uno::Reference < css::i18n::XBreakIterator > xBI = mpImpl->mpTextEngine->GetBreakIterator();
+ aPaM.GetIndex() = xBI->previousWord( pNode->GetText(), rPaM.GetIndex(), mpImpl->mpTextEngine->GetLocale(), css::i18n::WordType::ANYWORD_IGNOREWHITESPACES ).startPos;
+ if ( aPaM.GetIndex() > 0 )
+ mpImpl->mpTextEngine->GetWord( aPaM, &aPaM );
+ else
+ aPaM.GetIndex() = 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();
+ aPaM.GetIndex() = xBI->nextWord( pNode->GetText(), aPaM.GetIndex(), mpImpl->mpTextEngine->GetLocale(), css::i18n::WordType::ANYWORD_IGNOREWHITESPACES ).endPos;
+ mpImpl->mpTextEngine->GetWord( aPaM, nullptr, &aPaM );
+ }
+ 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 );
+
+ tools::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 );
+
+ tools::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 );
+ tools::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& 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( 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()
+ )
+ {
+ tools::Long nVisStartY = mpImpl->maStartDocPos.Y();
+ tools::Long nVisEndY = mpImpl->maStartDocPos.Y() + aOutSz.Height();
+ tools::Long nVisStartX = mpImpl->maStartDocPos.X();
+ tools::Long nVisEndX = mpImpl->maStartDocPos.X() + aOutSz.Width();
+ tools::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();
+ tools::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
+ tools::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() )
+ {
+ tools::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 ) const
+{
+ 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( std::u16string_view rNewText ) const
+{
+ bool bOK = true;
+ if ( mpImpl->mpTextEngine->GetMaxTextLen() )
+ {
+ sal_Int32 n = mpImpl->mpTextEngine->GetTextLen() + rNewText.size();
+ 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 )
+ return;
+
+ 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;
+
+ rtl::Reference<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);
+
+ if ( ImplCheckTextLen( aText ) )
+ ImpSetSelection( mpImpl->mpTextEngine->ImpInsertText( mpImpl->mpDDInfo->maDropPos, aText ) );
+
+ if ( aPrevSel.HasRange() &&
+ (( 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 );
+
+ // Don't drop in selection or in read only engine
+ if ( IsReadOnly() || IsInSelection( mpImpl->mpDDInfo->maDropPos ))
+ {
+ 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::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 = mpImpl->mpWindow->GetOutputSizePixel();
+ tools::Long nVisStartY = mpImpl->maStartDocPos.Y();
+ tools::Long nVisEndY = mpImpl->maStartDocPos.Y() + aOutSz.Height();
+
+ // Retrieve the coordinates of the PaM
+ tools::Rectangle aRect = mpImpl->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 );
+}
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/edit/txtattr.cxx b/vcl/source/edit/txtattr.cxx
new file mode 100644
index 0000000000..8e979c1e3f
--- /dev/null
+++ b/vcl/source/edit/txtattr.cxx
@@ -0,0 +1,93 @@
+/* -*- 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 <vcl/txtattr.hxx>
+#include <vcl/font.hxx>
+
+TextAttrib::~TextAttrib()
+{
+}
+
+bool TextAttrib::operator==( const TextAttrib& rAttr ) const
+{
+ return mnWhich == rAttr.mnWhich;
+}
+
+TextAttribFontColor::TextAttribFontColor( const Color& rColor )
+ : TextAttrib( TEXTATTR_FONTCOLOR ), maColor( rColor )
+{
+}
+
+void TextAttribFontColor::SetFont( vcl::Font& rFont ) const
+{
+ rFont.SetColor( maColor );
+}
+
+std::unique_ptr<TextAttrib> TextAttribFontColor::Clone() const
+{
+ return std::unique_ptr<TextAttrib>(new TextAttribFontColor( *this ));
+}
+
+bool TextAttribFontColor::operator==( const TextAttrib& rAttr ) const
+{
+ return ( ( TextAttrib::operator==(rAttr ) ) &&
+ ( maColor == static_cast<const TextAttribFontColor&>(rAttr).maColor ) );
+}
+
+TextAttribFontWeight::TextAttribFontWeight( FontWeight eWeight )
+ : TextAttrib( TEXTATTR_FONTWEIGHT ), meWeight( eWeight )
+{
+}
+
+void TextAttribFontWeight::SetFont( vcl::Font& rFont ) const
+{
+ rFont.SetWeight( meWeight );
+}
+
+std::unique_ptr<TextAttrib> TextAttribFontWeight::Clone() const
+{
+ return std::unique_ptr<TextAttrib>(new TextAttribFontWeight( *this ));
+}
+
+bool TextAttribFontWeight::operator==( const TextAttrib& rAttr ) const
+{
+ return ( ( TextAttrib::operator==(rAttr ) ) &&
+ ( meWeight == static_cast<const TextAttribFontWeight&>(rAttr).meWeight ) );
+}
+
+TextAttribProtect::TextAttribProtect() :
+ TextAttrib( TEXTATTR_PROTECTED )
+{
+}
+
+void TextAttribProtect::SetFont( vcl::Font& ) const
+{
+}
+
+std::unique_ptr<TextAttrib> TextAttribProtect::Clone() const
+{
+ return std::unique_ptr<TextAttrib>(new TextAttribProtect());
+}
+
+bool TextAttribProtect::operator==( const TextAttrib& rAttr ) const
+{
+ return TextAttrib::operator==(rAttr );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/edit/vclmedit.cxx b/vcl/source/edit/vclmedit.cxx
new file mode 100644
index 0000000000..d519735859
--- /dev/null
+++ b/vcl/source/edit/vclmedit.cxx
@@ -0,0 +1,1509 @@
+/* -*- 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 <i18nlangtag/languagetag.hxx>
+#include <vcl/commandevent.hxx>
+#include <vcl/builder.hxx>
+#include <vcl/decoview.hxx>
+#include <vcl/event.hxx>
+#include <vcl/menu.hxx>
+#include <vcl/specialchars.hxx>
+#include <vcl/timer.hxx>
+#include <vcl/vclevent.hxx>
+#include <vcl/xtextedt.hxx>
+#include <vcl/textview.hxx>
+#include <vcl/ptrstyle.hxx>
+
+#include <svl/undo.hxx>
+#include <svl/lstner.hxx>
+#include <vcl/uitest/uiobject.hxx>
+
+#include <vcl/settings.hxx>
+#include <vcl/toolkit/scrbar.hxx>
+#include <vcl/toolkit/vclmedit.hxx>
+#include <vcl/weld.hxx>
+#include <osl/diagnose.h>
+#include <tools/json_writer.hxx>
+#include <strings.hrc>
+#include <svdata.hxx>
+
+class ImpVclMEdit : public SfxListener
+{
+private:
+ VclPtr<VclMultiLineEdit> pVclMultiLineEdit;
+
+ VclPtr<TextWindow> mpTextWindow;
+ VclPtr<ScrollBar> mpHScrollBar;
+ VclPtr<ScrollBar> mpVScrollBar;
+ VclPtr<ScrollBarBox> mpScrollBox;
+
+ tools::Long mnTextWidth;
+ mutable Selection maSelection;
+
+protected:
+ virtual void Notify( SfxBroadcaster& rBC, const SfxHint& rHint ) override;
+ void ImpUpdateScrollBarVis( WinBits nWinStyle );
+ void ImpInitScrollBars();
+ void ImpSetScrollBarRanges();
+ void ImpSetHScrollBarThumbPos();
+ DECL_LINK( ScrollHdl, ScrollBar*, void );
+
+public:
+ ImpVclMEdit( VclMultiLineEdit* pVclMultiLineEdit, WinBits nWinStyle );
+ virtual ~ImpVclMEdit() override;
+
+ void SetModified( bool bMod );
+
+ void SetReadOnly( bool bRdOnly );
+ bool IsReadOnly() const;
+
+ void SetMaxTextLen(sal_Int32 nLen);
+ sal_Int32 GetMaxTextLen() const;
+
+ void SetMaxTextWidth(tools::Long nMaxWidth);
+
+ void InsertText( const OUString& rStr );
+ OUString GetSelected() const;
+ OUString GetSelected( LineEnd aSeparator ) const;
+
+ void SetSelection( const Selection& rSelection );
+ const Selection& GetSelection() const;
+
+ void Cut();
+ void Copy();
+ void Paste();
+
+ void SetText( const OUString& rStr );
+ OUString GetText() const;
+ OUString GetText( LineEnd aSeparator ) const;
+ OUString GetTextLines( LineEnd aSeparator ) const;
+
+ void Resize();
+ void GetFocus();
+
+ bool HandleCommand( const CommandEvent& rCEvt );
+
+ void Enable( bool bEnable );
+
+ Size CalcMinimumSize() const;
+ Size CalcBlockSize( sal_uInt16 nColumns, sal_uInt16 nLines ) const;
+ void GetMaxVisColumnsAndLines( sal_uInt16& rnCols, sal_uInt16& rnLines ) const;
+
+ void SetAlign( WinBits nWinStyle );
+
+ void InitFromStyle( WinBits nWinStyle );
+
+ TextWindow* GetTextWindow() { return mpTextWindow; }
+ ScrollBar& GetHScrollBar() { return *mpHScrollBar; }
+ ScrollBar& GetVScrollBar() { return *mpVScrollBar; }
+};
+
+ImpVclMEdit::ImpVclMEdit( VclMultiLineEdit* pEdt, WinBits nWinStyle )
+ : pVclMultiLineEdit(pEdt)
+ , mpTextWindow(VclPtr<TextWindow>::Create(pEdt))
+ , mpHScrollBar(VclPtr<ScrollBar>::Create(pVclMultiLineEdit, WB_HSCROLL|WB_DRAG))
+ , mpVScrollBar(VclPtr<ScrollBar>::Create(pVclMultiLineEdit, WB_VSCROLL|WB_DRAG))
+ , mpScrollBox(VclPtr<ScrollBarBox>::Create(pVclMultiLineEdit, WB_SIZEABLE))
+ , mnTextWidth(0)
+{
+ mpVScrollBar->SetScrollHdl( LINK( this, ImpVclMEdit, ScrollHdl ) );
+ mpHScrollBar->SetScrollHdl( LINK( this, ImpVclMEdit, ScrollHdl ) );
+ mpTextWindow->Show();
+ InitFromStyle( nWinStyle );
+ StartListening( *mpTextWindow->GetTextEngine() );
+}
+
+void ImpVclMEdit::ImpUpdateScrollBarVis( WinBits nWinStyle )
+{
+ const bool bHaveVScroll = mpVScrollBar->IsVisible();
+ const bool bHaveHScroll = mpHScrollBar->IsVisible();
+ const bool bHaveScrollBox = mpScrollBox->IsVisible();
+
+ bool bNeedVScroll = ( nWinStyle & WB_VSCROLL ) == WB_VSCROLL;
+ const bool bNeedHScroll = ( nWinStyle & WB_HSCROLL ) == WB_HSCROLL;
+
+ const bool bAutoVScroll = ( nWinStyle & WB_AUTOVSCROLL ) == WB_AUTOVSCROLL;
+ if ( !bNeedVScroll && bAutoVScroll )
+ {
+ TextEngine& rEngine( *mpTextWindow->GetTextEngine() );
+ tools::Long nOverallTextHeight(0);
+ for ( sal_uInt32 i=0; i<rEngine.GetParagraphCount(); ++i )
+ nOverallTextHeight += rEngine.GetTextHeight( i );
+ if ( nOverallTextHeight > mpTextWindow->GetOutputSizePixel().Height() )
+ bNeedVScroll = true;
+ }
+
+ const bool bNeedScrollBox = bNeedVScroll && bNeedHScroll;
+
+ bool bScrollbarsChanged = false;
+ if ( bHaveVScroll != bNeedVScroll )
+ {
+ mpVScrollBar->Show(bNeedVScroll);
+ bScrollbarsChanged = true;
+ }
+
+ if ( bHaveHScroll != bNeedHScroll )
+ {
+ mpHScrollBar->Show(bNeedHScroll);
+ bScrollbarsChanged = true;
+ }
+
+ if ( bHaveScrollBox != bNeedScrollBox )
+ {
+ mpScrollBox->Show(bNeedScrollBox);
+ }
+
+ if ( bScrollbarsChanged )
+ {
+ ImpInitScrollBars();
+ Resize();
+ }
+}
+
+void ImpVclMEdit::InitFromStyle( WinBits nWinStyle )
+{
+ ImpUpdateScrollBarVis( nWinStyle );
+ SetAlign( nWinStyle );
+
+ if ( nWinStyle & WB_NOHIDESELECTION )
+ mpTextWindow->SetAutoFocusHide( false );
+ else
+ mpTextWindow->SetAutoFocusHide( true );
+
+ if ( nWinStyle & WB_READONLY )
+ mpTextWindow->GetTextView()->SetReadOnly( true );
+ else
+ mpTextWindow->GetTextView()->SetReadOnly( false );
+
+ if ( nWinStyle & WB_IGNORETAB )
+ {
+ mpTextWindow->SetIgnoreTab( true );
+ }
+ else
+ {
+ mpTextWindow->SetIgnoreTab( false );
+ // #103667# VclMultiLineEdit has the flag, but focusable window also needs this flag
+ WinBits nStyle = mpTextWindow->GetStyle();
+ nStyle |= WB_NODIALOGCONTROL;
+ mpTextWindow->SetStyle( nStyle );
+ }
+}
+
+ImpVclMEdit::~ImpVclMEdit()
+{
+ EndListening( *mpTextWindow->GetTextEngine() );
+ mpTextWindow.disposeAndClear();
+ mpHScrollBar.disposeAndClear();
+ mpVScrollBar.disposeAndClear();
+ mpScrollBox.disposeAndClear();
+ pVclMultiLineEdit.disposeAndClear();
+}
+
+void ImpVclMEdit::ImpSetScrollBarRanges()
+{
+ const tools::Long nTextHeight = mpTextWindow->GetTextEngine()->GetTextHeight();
+ mpVScrollBar->SetRange( Range( 0, nTextHeight-1 ) );
+
+ mpHScrollBar->SetRange( Range( 0, mnTextWidth-1 ) );
+}
+
+void ImpVclMEdit::ImpInitScrollBars()
+{
+ static const sal_Unicode sampleChar = { 'x' };
+
+ ImpSetScrollBarRanges();
+
+ Size aCharBox;
+ aCharBox.setWidth( mpTextWindow->GetTextWidth( OUString(sampleChar) ) );
+ aCharBox.setHeight( mpTextWindow->GetTextHeight() );
+ Size aOutSz = mpTextWindow->GetOutputSizePixel();
+
+ mpHScrollBar->SetVisibleSize( aOutSz.Width() );
+ mpHScrollBar->SetPageSize( aOutSz.Width() * 8 / 10 );
+ mpHScrollBar->SetLineSize( aCharBox.Width()*10 );
+ ImpSetHScrollBarThumbPos();
+
+ mpVScrollBar->SetVisibleSize( aOutSz.Height() );
+ mpVScrollBar->SetPageSize( aOutSz.Height() * 8 / 10 );
+ mpVScrollBar->SetLineSize( aCharBox.Height() );
+ mpVScrollBar->SetThumbPos( mpTextWindow->GetTextView()->GetStartDocPos().Y() );
+}
+
+void ImpVclMEdit::ImpSetHScrollBarThumbPos()
+{
+ tools::Long nX = mpTextWindow->GetTextView()->GetStartDocPos().X();
+ if ( !mpTextWindow->GetTextEngine()->IsRightToLeft() )
+ mpHScrollBar->SetThumbPos( nX );
+ else
+ mpHScrollBar->SetThumbPos( mnTextWidth - mpHScrollBar->GetVisibleSize() - nX );
+
+}
+
+IMPL_LINK( ImpVclMEdit, ScrollHdl, ScrollBar*, pCurScrollBar, void )
+{
+ tools::Long nDiffX = 0, nDiffY = 0;
+
+ if ( pCurScrollBar == mpVScrollBar )
+ nDiffY = mpTextWindow->GetTextView()->GetStartDocPos().Y() - pCurScrollBar->GetThumbPos();
+ else if ( pCurScrollBar == mpHScrollBar )
+ nDiffX = mpTextWindow->GetTextView()->GetStartDocPos().X() - pCurScrollBar->GetThumbPos();
+
+ mpTextWindow->GetTextView()->Scroll( nDiffX, nDiffY );
+ // mpTextWindow->GetTextView()->ShowCursor( false, true );
+}
+
+void ImpVclMEdit::SetAlign( WinBits nWinStyle )
+{
+ bool bRTL = AllSettings::GetLayoutRTL();
+ mpTextWindow->GetTextEngine()->SetRightToLeft( bRTL );
+
+ if ( nWinStyle & WB_CENTER )
+ mpTextWindow->GetTextEngine()->SetTextAlign( TxtAlign::Center );
+ else if ( nWinStyle & WB_RIGHT )
+ mpTextWindow->GetTextEngine()->SetTextAlign( !bRTL ? TxtAlign::Right : TxtAlign::Left );
+ else if ( nWinStyle & WB_LEFT )
+ mpTextWindow->GetTextEngine()->SetTextAlign( !bRTL ? TxtAlign::Left : TxtAlign::Right );
+}
+
+void ImpVclMEdit::SetModified( bool bMod )
+{
+ mpTextWindow->GetTextEngine()->SetModified( bMod );
+}
+
+void ImpVclMEdit::SetReadOnly( bool bRdOnly )
+{
+ mpTextWindow->GetTextView()->SetReadOnly( bRdOnly );
+ // TODO: Adjust color?
+}
+
+bool ImpVclMEdit::IsReadOnly() const
+{
+ return mpTextWindow->GetTextView()->IsReadOnly();
+}
+
+void ImpVclMEdit::SetMaxTextLen(sal_Int32 nLen)
+{
+ mpTextWindow->GetTextEngine()->SetMaxTextLen(nLen);
+}
+
+sal_Int32 ImpVclMEdit::GetMaxTextLen() const
+{
+ return mpTextWindow->GetTextEngine()->GetMaxTextLen();
+}
+
+void ImpVclMEdit::InsertText( const OUString& rStr )
+{
+ mpTextWindow->GetTextView()->InsertText( rStr );
+}
+
+OUString ImpVclMEdit::GetSelected() const
+{
+ return mpTextWindow->GetTextView()->GetSelected();
+}
+
+OUString ImpVclMEdit::GetSelected( LineEnd aSeparator ) const
+{
+ return mpTextWindow->GetTextView()->GetSelected( aSeparator );
+}
+
+void ImpVclMEdit::SetMaxTextWidth(tools::Long nMaxWidth)
+{
+ mpTextWindow->GetTextEngine()->SetMaxTextWidth(nMaxWidth);
+}
+
+void ImpVclMEdit::Resize()
+{
+ int nIteration = 1;
+ do
+ {
+ WinBits nWinStyle( pVclMultiLineEdit->GetStyle() );
+ if ( ( nWinStyle & WB_AUTOVSCROLL ) == WB_AUTOVSCROLL )
+ ImpUpdateScrollBarVis( nWinStyle );
+
+ Size aSz = pVclMultiLineEdit->GetOutputSizePixel();
+ Size aEditSize = aSz;
+ tools::Long nSBWidth = pVclMultiLineEdit->GetSettings().GetStyleSettings().GetScrollBarSize();
+ nSBWidth = pVclMultiLineEdit->CalcZoom( nSBWidth );
+
+ if (mpHScrollBar->IsVisible())
+ aSz.AdjustHeight( -(nSBWidth+1) );
+ if (mpVScrollBar->IsVisible())
+ aSz.AdjustWidth( -(nSBWidth+1) );
+
+ if (!mpHScrollBar->IsVisible())
+ mpTextWindow->GetTextEngine()->SetMaxTextWidth( aSz.Width() );
+ else
+ mpHScrollBar->setPosSizePixel( 0, aEditSize.Height()-nSBWidth, aSz.Width(), nSBWidth );
+
+ Point aTextWindowPos;
+ if (mpVScrollBar->IsVisible())
+ {
+ if( AllSettings::GetLayoutRTL() )
+ {
+ mpVScrollBar->setPosSizePixel( 0, 0, nSBWidth, aSz.Height() );
+ aTextWindowPos.AdjustX(nSBWidth );
+ }
+ else
+ mpVScrollBar->setPosSizePixel( aEditSize.Width()-nSBWidth, 0, nSBWidth, aSz.Height() );
+ }
+
+ if (mpScrollBox->IsVisible())
+ mpScrollBox->setPosSizePixel( aSz.Width(), aSz.Height(), nSBWidth, nSBWidth );
+
+ Size aTextWindowSize( aSz );
+ if ( aTextWindowSize.Width() < 0 )
+ aTextWindowSize.setWidth( 0 );
+ if ( aTextWindowSize.Height() < 0 )
+ aTextWindowSize.setHeight( 0 );
+
+ Size aOldTextWindowSize( mpTextWindow->GetSizePixel() );
+ mpTextWindow->SetPosSizePixel( aTextWindowPos, aTextWindowSize );
+ if ( aOldTextWindowSize == aTextWindowSize )
+ break;
+
+ // Changing the text window size might effectively have changed the need for
+ // scrollbars, so do another iteration.
+ ++nIteration;
+ OSL_ENSURE( nIteration < 3, "ImpVclMEdit::Resize: isn't this expected to terminate with the second iteration?" );
+
+ } while ( nIteration <= 3 ); // artificial break after four iterations
+
+ ImpInitScrollBars();
+}
+
+void ImpVclMEdit::GetFocus()
+{
+ mpTextWindow->GrabFocus();
+}
+
+void ImpVclMEdit::Cut()
+{
+ if ( !mpTextWindow->GetTextView()->IsReadOnly() )
+ mpTextWindow->GetTextView()->Cut();
+}
+
+void ImpVclMEdit::Copy()
+{
+ mpTextWindow->GetTextView()->Copy();
+}
+
+void ImpVclMEdit::Paste()
+{
+ if ( !mpTextWindow->GetTextView()->IsReadOnly() )
+ mpTextWindow->GetTextView()->Paste();
+}
+
+void ImpVclMEdit::SetText( const OUString& rStr )
+{
+ bool bWasModified = mpTextWindow->GetTextEngine()->IsModified();
+ mpTextWindow->GetTextEngine()->SetText( rStr );
+ if ( !bWasModified )
+ mpTextWindow->GetTextEngine()->SetModified( false );
+
+ mpTextWindow->GetTextView()->SetSelection( TextSelection() );
+
+ WinBits nWinStyle( pVclMultiLineEdit->GetStyle() );
+ if ( ( nWinStyle & WB_AUTOVSCROLL ) == WB_AUTOVSCROLL )
+ ImpUpdateScrollBarVis( nWinStyle );
+}
+
+OUString ImpVclMEdit::GetText() const
+{
+ return mpTextWindow->GetTextEngine()->GetText();
+}
+
+OUString ImpVclMEdit::GetText( LineEnd aSeparator ) const
+{
+ return mpTextWindow->GetTextEngine()->GetText( aSeparator );
+}
+
+OUString ImpVclMEdit::GetTextLines( LineEnd aSeparator ) const
+{
+ return mpTextWindow->GetTextEngine()->GetTextLines( aSeparator );
+}
+
+void ImpVclMEdit::Notify( SfxBroadcaster&, const SfxHint& rHint )
+{
+ const TextHint* pTextHint = dynamic_cast<const TextHint*>(&rHint);
+ if ( !pTextHint )
+ return;
+
+ switch (pTextHint->GetId())
+ {
+ case SfxHintId::TextViewScrolled:
+ if (mpHScrollBar->IsVisible())
+ ImpSetHScrollBarThumbPos();
+ if (mpVScrollBar->IsVisible())
+ mpVScrollBar->SetThumbPos( mpTextWindow->GetTextView()->GetStartDocPos().Y() );
+ break;
+
+ case SfxHintId::TextHeightChanged:
+ if ( mpTextWindow->GetTextView()->GetStartDocPos().Y() )
+ {
+ tools::Long nOutHeight = mpTextWindow->GetOutputSizePixel().Height();
+ tools::Long nTextHeight = mpTextWindow->GetTextEngine()->GetTextHeight();
+ if ( nTextHeight < nOutHeight )
+ mpTextWindow->GetTextView()->Scroll( 0, mpTextWindow->GetTextView()->GetStartDocPos().Y() );
+ }
+ ImpSetScrollBarRanges();
+ break;
+
+ case SfxHintId::TextFormatted:
+ if (mpHScrollBar->IsVisible())
+ {
+ const tools::Long nWidth = mpTextWindow->GetTextEngine()->CalcTextWidth();
+ if ( nWidth != mnTextWidth )
+ {
+ mnTextWidth = nWidth;
+ mpHScrollBar->SetRange( Range( 0, mnTextWidth-1 ) );
+ ImpSetHScrollBarThumbPos();
+ }
+ }
+ break;
+
+ case SfxHintId::TextModified:
+ ImpUpdateScrollBarVis(pVclMultiLineEdit->GetStyle());
+ pVclMultiLineEdit->Modify();
+ break;
+
+ case SfxHintId::TextViewSelectionChanged:
+ pVclMultiLineEdit->SelectionChanged();
+ break;
+
+ case SfxHintId::TextViewCaretChanged:
+ pVclMultiLineEdit->CaretChanged();
+ break;
+
+ default: break;
+ }
+}
+
+void ImpVclMEdit::SetSelection( const Selection& rSelection )
+{
+ OUString aText = mpTextWindow->GetTextEngine()->GetText();
+
+ Selection aNewSelection( rSelection );
+ if ( aNewSelection.Min() < 0 )
+ aNewSelection.Min() = 0;
+ else if ( aNewSelection.Min() > aText.getLength() )
+ aNewSelection.Min() = aText.getLength();
+ if ( aNewSelection.Max() < 0 )
+ aNewSelection.Max() = 0;
+ else if ( aNewSelection.Max() > aText.getLength() )
+ aNewSelection.Max() = aText.getLength();
+
+ tools::Long nEnd = std::max( aNewSelection.Min(), aNewSelection.Max() );
+ TextSelection aTextSel;
+ sal_uInt32 nPara = 0;
+ sal_Int32 nChar = 0;
+ tools::Long x = 0;
+ while ( x <= nEnd )
+ {
+ if ( x == aNewSelection.Min() )
+ aTextSel.GetStart() = TextPaM( nPara, nChar );
+ if ( x == aNewSelection.Max() )
+ aTextSel.GetEnd() = TextPaM( nPara, nChar );
+
+ if ( ( x < aText.getLength() ) && ( aText[ x ] == '\n' ) )
+ {
+ nPara++;
+ nChar = 0;
+ }
+ else
+ nChar++;
+ x++;
+ }
+ mpTextWindow->GetTextView()->SetSelection( aTextSel );
+}
+
+const Selection& ImpVclMEdit::GetSelection() const
+{
+ maSelection = Selection();
+ TextSelection aTextSel( mpTextWindow->GetTextView()->GetSelection() );
+ aTextSel.Justify();
+ // flatten selection => every line-break a character
+
+ ExtTextEngine* pExtTextEngine = mpTextWindow->GetTextEngine();
+ // paragraphs before
+ for ( sal_uInt32 n = 0; n < aTextSel.GetStart().GetPara(); ++n )
+ {
+ maSelection.Min() += pExtTextEngine->GetTextLen( n );
+ maSelection.Min()++;
+ }
+
+ // first paragraph with selection
+ maSelection.Max() = maSelection.Min();
+ maSelection.Min() += aTextSel.GetStart().GetIndex();
+
+ for ( sal_uInt32 n = aTextSel.GetStart().GetPara(); n < aTextSel.GetEnd().GetPara(); ++n )
+ {
+ maSelection.Max() += pExtTextEngine->GetTextLen( n );
+ maSelection.Max()++;
+ }
+
+ maSelection.Max() += aTextSel.GetEnd().GetIndex();
+
+ return maSelection;
+}
+
+Size ImpVclMEdit::CalcMinimumSize() const
+{
+ Size aSz( mpTextWindow->GetTextEngine()->CalcTextWidth(),
+ mpTextWindow->GetTextEngine()->GetTextHeight() );
+
+ if (mpHScrollBar->IsVisible())
+ aSz.AdjustHeight(mpHScrollBar->GetSizePixel().Height() );
+ if (mpVScrollBar->IsVisible())
+ aSz.AdjustWidth(mpVScrollBar->GetSizePixel().Width() );
+
+ return aSz;
+}
+
+Size ImpVclMEdit::CalcBlockSize( sal_uInt16 nColumns, sal_uInt16 nLines ) const
+{
+ static const sal_Unicode sampleChar = 'X';
+
+ Size aSz;
+ Size aCharSz;
+ aCharSz.setWidth( mpTextWindow->GetTextWidth( OUString(sampleChar) ) );
+ aCharSz.setHeight( mpTextWindow->GetTextHeight() );
+
+ if ( nLines )
+ aSz.setHeight( nLines*aCharSz.Height() );
+ else
+ aSz.setHeight( mpTextWindow->GetTextEngine()->GetTextHeight() );
+
+ if ( nColumns )
+ aSz.setWidth( nColumns*aCharSz.Width() );
+ else
+ aSz.setWidth( mpTextWindow->GetTextEngine()->CalcTextWidth() );
+
+ if (mpHScrollBar->IsVisible())
+ aSz.AdjustHeight(mpHScrollBar->GetSizePixel().Height() );
+ if (mpVScrollBar->IsVisible())
+ aSz.AdjustWidth(mpVScrollBar->GetSizePixel().Width() );
+
+ return aSz;
+}
+
+void ImpVclMEdit::GetMaxVisColumnsAndLines( sal_uInt16& rnCols, sal_uInt16& rnLines ) const
+{
+ static const sal_Unicode sampleChar = { 'x' };
+ Size aOutSz = mpTextWindow->GetOutputSizePixel();
+ Size aCharSz( mpTextWindow->GetTextWidth( OUString(sampleChar) ), mpTextWindow->GetTextHeight() );
+ rnCols = static_cast<sal_uInt16>(aOutSz.Width()/aCharSz.Width());
+ rnLines = static_cast<sal_uInt16>(aOutSz.Height()/aCharSz.Height());
+}
+
+void ImpVclMEdit::Enable( bool bEnable )
+{
+ mpTextWindow->Enable( bEnable );
+ if (mpHScrollBar->IsVisible())
+ mpHScrollBar->Enable( bEnable );
+ if (mpVScrollBar->IsVisible())
+ mpVScrollBar->Enable( bEnable );
+}
+
+bool ImpVclMEdit::HandleCommand( const CommandEvent& rCEvt )
+{
+ bool bDone = false;
+ CommandEventId nCommand = rCEvt.GetCommand();
+ if (nCommand == CommandEventId::Wheel ||
+ nCommand == CommandEventId::StartAutoScroll ||
+ nCommand == CommandEventId::AutoScroll ||
+ nCommand == CommandEventId::GesturePan)
+ {
+ ScrollBar* pHScrollBar = mpHScrollBar->IsVisible() ? mpHScrollBar.get() : nullptr;
+ ScrollBar* pVScrollBar = mpVScrollBar->IsVisible() ? mpVScrollBar.get() : nullptr;
+ mpTextWindow->HandleScrollCommand(rCEvt, pHScrollBar, pVScrollBar);
+ bDone = true;
+ }
+ return bDone;
+}
+
+TextWindow::TextWindow(Edit* pParent)
+ : Window(pParent)
+ , mxParent(pParent)
+{
+ mbInMBDown = false;
+ mbFocusSelectionHide = false;
+ mbIgnoreTab = false;
+ mbActivePopup = false;
+ mbSelectOnTab = true;
+
+ SetPointer( PointerStyle::Text );
+
+ mpExtTextEngine.reset(new ExtTextEngine);
+ mpExtTextEngine->SetMaxTextLen(EDIT_NOLIMIT);
+ if( pParent->GetStyle() & WB_BORDER )
+ mpExtTextEngine->SetLeftMargin( 2 );
+ mpExtTextEngine->SetLocale( GetSettings().GetLanguageTag().getLocale() );
+ mpExtTextView.reset(new TextView( mpExtTextEngine.get(), this ));
+ mpExtTextEngine->InsertView( mpExtTextView.get() );
+ mpExtTextEngine->EnableUndo( true );
+ mpExtTextView->ShowCursor();
+
+ Color aBackgroundColor = GetSettings().GetStyleSettings().GetWorkspaceColor();
+ SetBackground( aBackgroundColor );
+ pParent->SetBackground( aBackgroundColor );
+}
+
+TextWindow::~TextWindow()
+{
+ disposeOnce();
+}
+
+void TextWindow::dispose()
+{
+ mxParent.clear();
+ mpExtTextView.reset();
+ mpExtTextEngine.reset();
+ Window::dispose();
+}
+
+void TextWindow::MouseMove( const MouseEvent& rMEvt )
+{
+ mpExtTextView->MouseMove( rMEvt );
+ Window::MouseMove( rMEvt );
+}
+
+void TextWindow::MouseButtonDown( const MouseEvent& rMEvt )
+{
+ mbInMBDown = true; // so that GetFocus does not select everything
+ mpExtTextView->MouseButtonDown( rMEvt );
+ GrabFocus();
+ mbInMBDown = false;
+}
+
+void TextWindow::MouseButtonUp( const MouseEvent& rMEvt )
+{
+ mpExtTextView->MouseButtonUp( rMEvt );
+}
+
+void TextWindow::KeyInput( const KeyEvent& rKEvent )
+{
+ bool bDone = false;
+ sal_uInt16 nCode = rKEvent.GetKeyCode().GetCode();
+ if ( nCode == css::awt::Key::SELECT_ALL ||
+ ( (nCode == KEY_A) && rKEvent.GetKeyCode().IsMod1() && !rKEvent.GetKeyCode().IsMod2() )
+ )
+ {
+ mpExtTextView->SetSelection( TextSelection( TextPaM( 0, 0 ), TextPaM( TEXT_PARA_ALL, TEXT_INDEX_ALL ) ) );
+ bDone = true;
+ }
+ else if ( (nCode == KEY_S) && rKEvent.GetKeyCode().IsShift() && rKEvent.GetKeyCode().IsMod1() )
+ {
+ if ( vcl::GetGetSpecialCharsFunction() )
+ {
+ // to maintain the selection
+ mbActivePopup = true;
+ OUString aChars = vcl::GetGetSpecialCharsFunction()(GetFrameWeld(), GetFont());
+ if (!aChars.isEmpty())
+ {
+ mpExtTextView->InsertText( aChars );
+ mpExtTextView->GetTextEngine()->SetModified( true );
+ }
+ mbActivePopup = false;
+ bDone = true;
+ }
+ }
+ else if ( nCode == KEY_TAB )
+ {
+ if (!mbIgnoreTab)
+ {
+ if (!rKEvent.GetKeyCode().IsMod1())
+ bDone = mpExtTextView->KeyInput(rKEvent);
+ else
+ {
+ // tdf#107625 make ctrl+tab act like tab when MultiLine Edit normally accepts tab as an input char
+ vcl::KeyCode aKeyCode(rKEvent.GetKeyCode().GetCode(), rKEvent.GetKeyCode().GetModifier() & ~KEY_MOD1);
+ KeyEvent aKEventWithoutMod1(rKEvent.GetCharCode(), aKeyCode, rKEvent.GetRepeat());
+ Window::KeyInput(aKEventWithoutMod1);
+ bDone = true;
+ }
+ }
+ }
+ else
+ {
+ bDone = mpExtTextView->KeyInput( rKEvent );
+ }
+
+ if ( !bDone )
+ Window::KeyInput( rKEvent );
+}
+
+void TextWindow::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect)
+{
+ mpExtTextView->Paint(rRenderContext, rRect);
+}
+
+void TextWindow::Resize()
+{
+}
+
+void TextWindow::Command( const CommandEvent& rCEvt )
+{
+ if ( rCEvt.GetCommand() == CommandEventId::ContextMenu )
+ {
+ VclPtr<PopupMenu> pPopup = mxParent->CreatePopupMenu();
+ bool bEnableCut = true;
+ bool bEnableCopy = true;
+ bool bEnableDelete = true;
+ bool bEnablePaste = true;
+ bool bEnableSpecialChar = true;
+ bool bEnableUndo = true;
+
+ if ( !mpExtTextView->HasSelection() )
+ {
+ bEnableCut = false;
+ bEnableCopy = false;
+ bEnableDelete = false;
+ }
+ if ( mpExtTextView->IsReadOnly() )
+ {
+ bEnableCut = false;
+ bEnablePaste = false;
+ bEnableDelete = false;
+ bEnableSpecialChar = false;
+ }
+ if ( !mpExtTextView->GetTextEngine()->HasUndoManager() || !mpExtTextView->GetTextEngine()->GetUndoManager().GetUndoActionCount() )
+ {
+ bEnableUndo = false;
+ }
+ pPopup->EnableItem(pPopup->GetItemId(u"cut"), bEnableCut);
+ pPopup->EnableItem(pPopup->GetItemId(u"copy"), bEnableCopy);
+ pPopup->EnableItem(pPopup->GetItemId(u"delete"), bEnableDelete);
+ pPopup->EnableItem(pPopup->GetItemId(u"paste"), bEnablePaste);
+ pPopup->SetItemText(pPopup->GetItemId(u"specialchar"),
+ BuilderUtils::convertMnemonicMarkup(VclResId(STR_SPECIAL_CHARACTER_MENU_ENTRY)));
+ pPopup->EnableItem(pPopup->GetItemId(u"specialchar"), bEnableSpecialChar);
+ pPopup->EnableItem(pPopup->GetItemId(u"undo"), bEnableUndo);
+ pPopup->ShowItem(pPopup->GetItemId(u"specialchar"), !vcl::GetGetSpecialCharsFunction());
+
+ mbActivePopup = true;
+ Point aPos = rCEvt.GetMousePosPixel();
+ if ( !rCEvt.IsMouseEvent() )
+ {
+ // Sometime do show Menu centered in the selection !!!
+ Size aSize = GetOutputSizePixel();
+ aPos = Point( aSize.Width()/2, aSize.Height()/2 );
+ }
+ sal_uInt16 n = pPopup->Execute( this, aPos );
+ OUString sCommand = pPopup->GetItemIdent(n);
+ if (sCommand == "undo")
+ {
+ mpExtTextView->Undo();
+ mpExtTextEngine->SetModified( true );
+ mpExtTextEngine->Broadcast( TextHint( SfxHintId::TextModified ) );
+ }
+ else if (sCommand == "cut")
+ {
+ mpExtTextView->Cut();
+ mpExtTextEngine->SetModified( true );
+ mpExtTextEngine->Broadcast( TextHint( SfxHintId::TextModified ) );
+ }
+ else if (sCommand == "copy")
+ {
+ mpExtTextView->Copy();
+ }
+ else if (sCommand == "paste")
+ {
+ mpExtTextView->Paste();
+ mpExtTextEngine->SetModified( true );
+ mpExtTextEngine->Broadcast( TextHint( SfxHintId::TextModified ) );
+ }
+ else if (sCommand == "delete")
+ {
+ mpExtTextView->DeleteSelected();
+ mpExtTextEngine->SetModified( true );
+ mpExtTextEngine->Broadcast( TextHint( SfxHintId::TextModified ) );
+ }
+ else if (sCommand == "selectall")
+ {
+ mpExtTextView->SetSelection( TextSelection( TextPaM( 0, 0 ), TextPaM( TEXT_PARA_ALL, TEXT_INDEX_ALL ) ) );
+ }
+ else if (sCommand == "specialchar")
+ {
+ OUString aChars = vcl::GetGetSpecialCharsFunction()(GetFrameWeld(), GetFont());
+ if (!aChars.isEmpty())
+ {
+ mpExtTextView->InsertText( aChars );
+ mpExtTextEngine->SetModified( true );
+ mpExtTextEngine->Broadcast( TextHint( SfxHintId::TextModified ) );
+ }
+ }
+ pPopup.clear();
+ mbActivePopup = false;
+ }
+ else
+ {
+ mpExtTextView->Command( rCEvt );
+ }
+ Window::Command( rCEvt );
+}
+
+void TextWindow::GetFocus()
+{
+ Window::GetFocus();
+ if ( mbActivePopup )
+ return;
+
+ bool bGotoCursor = !mpExtTextView->IsReadOnly();
+ if ( mbFocusSelectionHide && IsReallyVisible() && mbSelectOnTab && !mbInMBDown )
+ {
+ // select everything, but do not scroll
+ bool bAutoScroll = mpExtTextView->IsAutoScroll();
+ mpExtTextView->SetAutoScroll( false );
+ mpExtTextView->SetSelection( TextSelection( TextPaM( 0, 0 ), TextPaM( TEXT_PARA_ALL, TEXT_INDEX_ALL ) ) );
+ mpExtTextView->SetAutoScroll( bAutoScroll );
+ bGotoCursor = false;
+ }
+ mpExtTextView->SetPaintSelection( true );
+ mpExtTextView->ShowCursor( bGotoCursor );
+}
+
+void TextWindow::LoseFocus()
+{
+ Window::LoseFocus();
+
+ if ( mbFocusSelectionHide && !mbActivePopup && mpExtTextView )
+ mpExtTextView->SetPaintSelection( false );
+}
+
+VclMultiLineEdit::VclMultiLineEdit( vcl::Window* pParent, WinBits nWinStyle )
+ : Edit( pParent, nWinStyle )
+{
+ SetType( WindowType::MULTILINEEDIT );
+ pImpVclMEdit.reset(new ImpVclMEdit( this, nWinStyle ));
+ ImplInitSettings( true );
+
+ SetCompoundControl( true );
+ SetStyle( ImplInitStyle( nWinStyle ) );
+}
+
+VclMultiLineEdit::~VclMultiLineEdit()
+{
+ disposeOnce();
+}
+
+void VclMultiLineEdit::dispose()
+{
+ pImpVclMEdit.reset();
+ Edit::dispose();
+}
+
+WinBits VclMultiLineEdit::ImplInitStyle( WinBits nStyle )
+{
+ if ( !(nStyle & WB_NOTABSTOP) )
+ nStyle |= WB_TABSTOP;
+
+ if ( !(nStyle & WB_NOGROUP) )
+ nStyle |= WB_GROUP;
+
+ if ( !(nStyle & WB_IGNORETAB ))
+ nStyle |= WB_NODIALOGCONTROL;
+
+ return nStyle;
+}
+
+void VclMultiLineEdit::ApplySettings(vcl::RenderContext& rRenderContext)
+{
+ const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings();
+
+ // The Font has to be adjusted, as the TextEngine does not take care of
+ // TextColor/Background
+
+ Color aTextColor = rStyleSettings.GetFieldTextColor();
+ if (IsControlForeground())
+ aTextColor = GetControlForeground();
+
+ if (!IsEnabled())
+ aTextColor = rStyleSettings.GetDisableColor();
+
+ vcl::Font aFont = rStyleSettings.GetFieldFont();
+ aFont.SetTransparent(IsPaintTransparent());
+ ApplyControlFont(rRenderContext, aFont);
+
+ vcl::Font theFont = rRenderContext.GetFont();
+ theFont.SetColor(aTextColor);
+ if (IsPaintTransparent())
+ theFont.SetFillColor(COL_TRANSPARENT);
+ else
+ theFont.SetFillColor(IsControlBackground() ? GetControlBackground() : rStyleSettings.GetFieldColor());
+
+ pImpVclMEdit->GetTextWindow()->SetFont(theFont);
+ // FIXME: next call causes infinite invalidation loop, rethink how to properly fix this situation
+ // pImpVclMEdit->GetTextWindow()->GetTextEngine()->SetFont(theFont);
+ pImpVclMEdit->GetTextWindow()->SetTextColor(aTextColor);
+
+ if (IsPaintTransparent())
+ {
+ pImpVclMEdit->GetTextWindow()->SetPaintTransparent(true);
+ pImpVclMEdit->GetTextWindow()->SetBackground();
+ pImpVclMEdit->GetTextWindow()->SetControlBackground();
+ rRenderContext.SetBackground();
+ SetControlBackground();
+ }
+ else
+ {
+ if (IsControlBackground())
+ pImpVclMEdit->GetTextWindow()->SetBackground(GetControlBackground());
+ else
+ pImpVclMEdit->GetTextWindow()->SetBackground(rStyleSettings.GetFieldColor());
+ // also adjust for VclMultiLineEdit as the TextComponent might hide Scrollbars
+ rRenderContext.SetBackground(pImpVclMEdit->GetTextWindow()->GetBackground());
+ }
+}
+
+void VclMultiLineEdit::ImplInitSettings(bool bBackground)
+{
+ const StyleSettings& rStyleSettings = GetSettings().GetStyleSettings();
+
+ // The Font has to be adjusted, as the TextEngine does not take care of
+ // TextColor/Background
+
+ Color aTextColor = rStyleSettings.GetFieldTextColor();
+ if (IsControlForeground())
+ aTextColor = GetControlForeground();
+ if (!IsEnabled())
+ aTextColor = rStyleSettings.GetDisableColor();
+
+ vcl::Font aFont = rStyleSettings.GetFieldFont();
+ aFont.SetTransparent(IsPaintTransparent());
+ ApplyControlFont(*GetOutDev(), aFont);
+
+ vcl::Font TheFont = GetFont();
+ TheFont.SetColor(aTextColor);
+ if (IsPaintTransparent())
+ TheFont.SetFillColor(COL_TRANSPARENT);
+ else
+ TheFont.SetFillColor(IsControlBackground() ? GetControlBackground() : rStyleSettings.GetFieldColor());
+ pImpVclMEdit->GetTextWindow()->SetFont(TheFont);
+ pImpVclMEdit->GetTextWindow()->GetTextEngine()->SetFont(TheFont);
+ pImpVclMEdit->GetTextWindow()->SetTextColor(aTextColor);
+
+ if (!bBackground)
+ return;
+
+ if (IsPaintTransparent())
+ {
+ pImpVclMEdit->GetTextWindow()->SetPaintTransparent(true);
+ pImpVclMEdit->GetTextWindow()->SetBackground();
+ pImpVclMEdit->GetTextWindow()->SetControlBackground();
+ SetBackground();
+ SetControlBackground();
+ }
+ else
+ {
+ if (IsControlBackground())
+ pImpVclMEdit->GetTextWindow()->SetBackground(GetControlBackground());
+ else
+ pImpVclMEdit->GetTextWindow()->SetBackground(rStyleSettings.GetFieldColor());
+ // also adjust for VclMultiLineEdit as the TextComponent might hide Scrollbars
+ SetBackground(pImpVclMEdit->GetTextWindow()->GetBackground());
+ }
+}
+
+void VclMultiLineEdit::Modify()
+{
+ aModifyHdlLink.Call( *this );
+
+ CallEventListeners( VclEventId::EditModify );
+}
+
+void VclMultiLineEdit::SelectionChanged()
+{
+ CallEventListeners(VclEventId::EditSelectionChanged);
+}
+
+void VclMultiLineEdit::CaretChanged()
+{
+ CallEventListeners(VclEventId::EditCaretChanged);
+}
+
+void VclMultiLineEdit::SetModifyFlag()
+{
+ pImpVclMEdit->SetModified( true );
+}
+
+void VclMultiLineEdit::SetReadOnly( bool bReadOnly )
+{
+ pImpVclMEdit->SetReadOnly( bReadOnly );
+ Edit::SetReadOnly( bReadOnly );
+
+ // #94921# ReadOnly can be overwritten in InitFromStyle() when WB not set.
+ WinBits nStyle = GetStyle();
+ if ( bReadOnly )
+ nStyle |= WB_READONLY;
+ else
+ nStyle &= ~WB_READONLY;
+ SetStyle( nStyle );
+}
+
+bool VclMultiLineEdit::IsReadOnly() const
+{
+ if (!pImpVclMEdit) // might be called from within the dtor, when pImpVclMEdit == NULL is a valid state
+ return true;
+
+ return pImpVclMEdit->IsReadOnly();
+}
+
+void VclMultiLineEdit::SetMaxTextLen(sal_Int32 nMaxLen)
+{
+ pImpVclMEdit->SetMaxTextLen(nMaxLen);
+}
+
+void VclMultiLineEdit::SetMaxTextWidth(tools::Long nMaxWidth)
+{
+ pImpVclMEdit->SetMaxTextWidth(nMaxWidth );
+}
+
+sal_Int32 VclMultiLineEdit::GetMaxTextLen() const
+{
+ return pImpVclMEdit->GetMaxTextLen();
+}
+
+void VclMultiLineEdit::ReplaceSelected( const OUString& rStr )
+{
+ pImpVclMEdit->InsertText( rStr );
+}
+
+void VclMultiLineEdit::DeleteSelected()
+{
+ pImpVclMEdit->InsertText( OUString() );
+}
+
+OUString VclMultiLineEdit::GetSelected() const
+{
+ return pImpVclMEdit->GetSelected();
+}
+
+OUString VclMultiLineEdit::GetSelected( LineEnd aSeparator ) const
+{
+ return pImpVclMEdit->GetSelected( aSeparator );
+}
+
+void VclMultiLineEdit::Cut()
+{
+ pImpVclMEdit->Cut();
+}
+
+void VclMultiLineEdit::Copy()
+{
+ pImpVclMEdit->Copy();
+}
+
+void VclMultiLineEdit::Paste()
+{
+ pImpVclMEdit->Paste();
+}
+
+void VclMultiLineEdit::SetText( const OUString& rStr )
+{
+ pImpVclMEdit->SetText( rStr );
+}
+
+OUString VclMultiLineEdit::GetText() const
+{
+ return pImpVclMEdit ? pImpVclMEdit->GetText() : OUString();
+}
+
+OUString VclMultiLineEdit::GetText( LineEnd aSeparator ) const
+{
+ return pImpVclMEdit ? pImpVclMEdit->GetText( aSeparator ) : OUString();
+}
+
+OUString VclMultiLineEdit::GetTextLines( LineEnd aSeparator ) const
+{
+ return pImpVclMEdit ? pImpVclMEdit->GetTextLines( aSeparator ) : OUString();
+}
+
+void VclMultiLineEdit::Resize()
+{
+ pImpVclMEdit->Resize();
+}
+
+void VclMultiLineEdit::GetFocus()
+{
+ if ( !pImpVclMEdit ) // might be called from within the dtor, when pImpVclMEdit == NULL is a valid state
+ return;
+
+ pImpVclMEdit->GetFocus();
+}
+
+void VclMultiLineEdit::SetSelection( const Selection& rSelection )
+{
+ pImpVclMEdit->SetSelection( rSelection );
+}
+
+const Selection& VclMultiLineEdit::GetSelection() const
+{
+ return pImpVclMEdit->GetSelection();
+}
+
+Size VclMultiLineEdit::CalcMinimumSize() const
+{
+ Size aSz = pImpVclMEdit->CalcMinimumSize();
+
+ sal_Int32 nLeft, nTop, nRight, nBottom;
+ static_cast<vcl::Window*>(const_cast<VclMultiLineEdit *>(this))->GetBorder( nLeft, nTop, nRight, nBottom );
+ aSz.AdjustWidth(nLeft+nRight );
+ aSz.AdjustHeight(nTop+nBottom );
+
+ return aSz;
+}
+
+Size VclMultiLineEdit::CalcAdjustedSize( const Size& rPrefSize ) const
+{
+ Size aSz = rPrefSize;
+ sal_Int32 nLeft, nTop, nRight, nBottom;
+ static_cast<vcl::Window*>(const_cast<VclMultiLineEdit *>(this))->GetBorder( nLeft, nTop, nRight, nBottom );
+
+ // center vertically for whole lines
+
+ tools::Long nHeight = aSz.Height() - nTop - nBottom;
+ tools::Long nLineHeight = pImpVclMEdit->CalcBlockSize( 1, 1 ).Height();
+ tools::Long nLines = nHeight / nLineHeight;
+ if ( nLines < 1 )
+ nLines = 1;
+
+ aSz.setHeight( nLines * nLineHeight );
+ aSz.AdjustHeight(nTop+nBottom );
+
+ return aSz;
+}
+
+Size VclMultiLineEdit::CalcBlockSize( sal_uInt16 nColumns, sal_uInt16 nLines ) const
+{
+ Size aSz = pImpVclMEdit->CalcBlockSize( nColumns, nLines );
+
+ sal_Int32 nLeft, nTop, nRight, nBottom;
+ static_cast<vcl::Window*>(const_cast<VclMultiLineEdit *>(this))->GetBorder( nLeft, nTop, nRight, nBottom );
+ aSz.AdjustWidth(nLeft+nRight );
+ aSz.AdjustHeight(nTop+nBottom );
+ return aSz;
+}
+
+void VclMultiLineEdit::GetMaxVisColumnsAndLines( sal_uInt16& rnCols, sal_uInt16& rnLines ) const
+{
+ pImpVclMEdit->GetMaxVisColumnsAndLines( rnCols, rnLines );
+}
+
+void VclMultiLineEdit::StateChanged( StateChangedType nType )
+{
+ if( nType == StateChangedType::Enable )
+ {
+ pImpVclMEdit->Enable( IsEnabled() );
+ ImplInitSettings( false );
+ }
+ else if( nType == StateChangedType::ReadOnly )
+ {
+ pImpVclMEdit->SetReadOnly( IsReadOnly() );
+ }
+ else if ( nType == StateChangedType::Zoom )
+ {
+ pImpVclMEdit->GetTextWindow()->SetZoom( GetZoom() );
+ ImplInitSettings( false );
+ Resize();
+ }
+ else if ( nType == StateChangedType::ControlFont )
+ {
+ ImplInitSettings( false );
+ Resize();
+ Invalidate();
+ }
+ else if ( nType == StateChangedType::ControlForeground )
+ {
+ ImplInitSettings( false );
+ Invalidate();
+ }
+ else if ( nType == StateChangedType::ControlBackground )
+ {
+ ImplInitSettings( true );
+ Invalidate();
+ }
+ else if ( nType == StateChangedType::Style )
+ {
+ pImpVclMEdit->InitFromStyle( GetStyle() );
+ SetStyle( ImplInitStyle( GetStyle() ) );
+ }
+ else if ( nType == StateChangedType::InitShow )
+ {
+ if( IsPaintTransparent() )
+ {
+ pImpVclMEdit->GetTextWindow()->SetPaintTransparent( true );
+ pImpVclMEdit->GetTextWindow()->SetBackground();
+ pImpVclMEdit->GetTextWindow()->SetControlBackground();
+ SetBackground();
+ SetControlBackground();
+ }
+ }
+
+ Control::StateChanged( nType );
+}
+
+void VclMultiLineEdit::DataChanged( const DataChangedEvent& rDCEvt )
+{
+ if ( (rDCEvt.GetType() == DataChangedEventType::SETTINGS) &&
+ (rDCEvt.GetFlags() & AllSettingsFlags::STYLE) )
+ {
+ ImplInitSettings( true );
+ Resize();
+ Invalidate();
+ }
+ else
+ Control::DataChanged( rDCEvt );
+}
+
+void VclMultiLineEdit::Draw( OutputDevice* pDev, const Point& rPos, SystemTextColorFlags nFlags )
+{
+ ImplInitSettings(true);
+
+ Point aPos = pDev->LogicToPixel( rPos );
+ Size aSize = GetSizePixel();
+
+ vcl::Font aFont = pImpVclMEdit->GetTextWindow()->GetDrawPixelFont(pDev);
+ aFont.SetTransparent( true );
+
+ pDev->Push();
+ pDev->SetMapMode();
+ pDev->SetFont( aFont );
+ pDev->SetTextFillColor();
+
+ // Border/Background
+ pDev->SetLineColor();
+ pDev->SetFillColor();
+ bool bBorder = (GetStyle() & WB_BORDER);
+ bool bBackground = IsControlBackground();
+ if ( bBorder || bBackground )
+ {
+ tools::Rectangle aRect( aPos, aSize );
+ if ( bBorder )
+ {
+ DecorationView aDecoView( pDev );
+ aRect = aDecoView.DrawFrame( aRect, DrawFrameStyle::DoubleIn );
+ }
+ if ( bBackground )
+ {
+ pDev->SetFillColor( GetControlBackground() );
+ pDev->DrawRect( aRect );
+ }
+ }
+
+ pDev->SetSystemTextColor(nFlags, IsEnabled());
+
+ OUString aText = GetText();
+ Size aTextSz( pDev->GetTextWidth( aText ), pDev->GetTextHeight() );
+ sal_uLong nLines = static_cast<sal_uLong>(aSize.Height() / aTextSz.Height());
+ if ( !nLines )
+ nLines = 1;
+ aTextSz.setHeight( nLines*aTextSz.Height() );
+ tools::Long nOnePixel = GetDrawPixel( pDev, 1 );
+ tools::Long nOffX = 3*nOnePixel;
+ tools::Long nOffY = 2*nOnePixel;
+
+ // Clipping?
+ if ( ( nOffY < 0 ) || ( (nOffY+aTextSz.Height()) > aSize.Height() ) || ( (nOffX+aTextSz.Width()) > aSize.Width() ) )
+ {
+ tools::Rectangle aClip( aPos, aSize );
+ if ( aTextSz.Height() > aSize.Height() )
+ aClip.AdjustBottom(aTextSz.Height() - aSize.Height() + 1 ); // so that HP-printer does not 'optimize-away'
+ pDev->IntersectClipRegion( aClip );
+ }
+
+ ExtTextEngine aTE;
+ aTE.SetText( GetText() );
+ aTE.SetMaxTextWidth( aSize.Width() );
+ aTE.SetFont( aFont );
+ aTE.SetTextAlign( pImpVclMEdit->GetTextWindow()->GetTextEngine()->GetTextAlign() );
+ aTE.Draw( pDev, Point( aPos.X() + nOffX, aPos.Y() + nOffY ) );
+
+ pDev->Pop();
+}
+
+bool VclMultiLineEdit::EventNotify( NotifyEvent& rNEvt )
+{
+ bool bDone = false;
+ if( rNEvt.GetType() == NotifyEventType::COMMAND )
+ {
+ bDone = pImpVclMEdit->HandleCommand( *rNEvt.GetCommandEvent() );
+ }
+ return bDone || Edit::EventNotify( rNEvt );
+}
+
+bool VclMultiLineEdit::PreNotify( NotifyEvent& rNEvt )
+{
+ bool bDone = false;
+
+ if( ( rNEvt.GetType() == NotifyEventType::KEYINPUT ) && ( !GetTextView()->IsCursorEnabled() ) )
+ {
+ const KeyEvent& rKEvent = *rNEvt.GetKeyEvent();
+ if ( !rKEvent.GetKeyCode().IsShift() && ( rKEvent.GetKeyCode().GetGroup() == KEYGROUP_CURSOR ) )
+ {
+ bDone = true;
+ TextSelection aSel = pImpVclMEdit->GetTextWindow()->GetTextView()->GetSelection();
+ if ( aSel.HasRange() )
+ {
+ aSel.GetStart() = aSel.GetEnd();
+ pImpVclMEdit->GetTextWindow()->GetTextView()->SetSelection( aSel );
+ }
+ else
+ {
+ switch ( rKEvent.GetKeyCode().GetCode() )
+ {
+ case KEY_UP:
+ {
+ if ( pImpVclMEdit->GetVScrollBar().IsVisible() )
+ pImpVclMEdit->GetVScrollBar().DoScrollAction( ScrollType::LineUp );
+ }
+ break;
+ case KEY_DOWN:
+ {
+ if ( pImpVclMEdit->GetVScrollBar().IsVisible() )
+ pImpVclMEdit->GetVScrollBar().DoScrollAction( ScrollType::LineDown );
+ }
+ break;
+ case KEY_PAGEUP :
+ {
+ if ( pImpVclMEdit->GetVScrollBar().IsVisible() )
+ pImpVclMEdit->GetVScrollBar().DoScrollAction( ScrollType::PageUp );
+ }
+ break;
+ case KEY_PAGEDOWN:
+ {
+ if ( pImpVclMEdit->GetVScrollBar().IsVisible() )
+ pImpVclMEdit->GetVScrollBar().DoScrollAction( ScrollType::PageDown );
+ }
+ break;
+ case KEY_LEFT:
+ {
+ if ( pImpVclMEdit->GetHScrollBar().IsVisible() )
+ pImpVclMEdit->GetHScrollBar().DoScrollAction( ScrollType::LineUp );
+ }
+ break;
+ case KEY_RIGHT:
+ {
+ if ( pImpVclMEdit->GetHScrollBar().IsVisible() )
+ pImpVclMEdit->GetHScrollBar().DoScrollAction( ScrollType::LineDown );
+ }
+ break;
+ case KEY_HOME:
+ {
+ if ( rKEvent.GetKeyCode().IsMod1() )
+ pImpVclMEdit->GetTextWindow()->GetTextView()->
+ SetSelection( TextSelection( TextPaM( 0, 0 ) ) );
+ }
+ break;
+ case KEY_END:
+ {
+ if ( rKEvent.GetKeyCode().IsMod1() )
+ pImpVclMEdit->GetTextWindow()->GetTextView()->
+ SetSelection( TextSelection( TextPaM( TEXT_PARA_ALL, TEXT_INDEX_ALL ) ) );
+ }
+ break;
+ default:
+ {
+ bDone = false;
+ }
+ }
+ }
+ }
+ }
+
+ return bDone || Edit::PreNotify( rNEvt );
+}
+
+// Internals for derived classes, e.g. TextComponent
+
+ExtTextEngine* VclMultiLineEdit::GetTextEngine() const
+{
+ return pImpVclMEdit->GetTextWindow()->GetTextEngine();
+}
+
+TextView* VclMultiLineEdit::GetTextView() const
+{
+ return pImpVclMEdit->GetTextWindow()->GetTextView();
+}
+
+ScrollBar& VclMultiLineEdit::GetVScrollBar() const
+{
+ return pImpVclMEdit->GetVScrollBar();
+}
+
+void VclMultiLineEdit::EnableFocusSelectionHide( bool bHide )
+{
+ pImpVclMEdit->GetTextWindow()->SetAutoFocusHide( bHide );
+}
+
+void VclMultiLineEdit::DisableSelectionOnFocus()
+{
+ pImpVclMEdit->GetTextWindow()->DisableSelectionOnFocus();
+}
+
+void VclMultiLineEdit::EnableCursor( bool bEnable )
+{
+ GetTextView()->EnableCursor( bEnable );
+}
+
+bool VclMultiLineEdit::CanUp() const
+{
+ TextView* pTextView = GetTextView();
+ const TextSelection& rTextSelection = pTextView->GetSelection();
+ TextPaM aPaM(rTextSelection.GetEnd());
+ return aPaM != pTextView->CursorUp(aPaM);
+}
+
+bool VclMultiLineEdit::CanDown() const
+{
+ TextView* pTextView = GetTextView();
+ const TextSelection& rTextSelection = pTextView->GetSelection();
+ TextPaM aPaM(rTextSelection.GetEnd());
+ return aPaM != pTextView->CursorDown(aPaM);
+}
+
+TextWindow* VclMultiLineEdit::GetTextWindow()
+{
+ return pImpVclMEdit->GetTextWindow();
+}
+
+FactoryFunction VclMultiLineEdit::GetUITestFactory() const
+{
+ return MultiLineEditUIObject::create;
+}
+
+bool VclMultiLineEdit::set_property(const OUString &rKey, const OUString &rValue)
+{
+ if (rKey == "cursor-visible")
+ EnableCursor(toBool(rValue));
+ else if (rKey == "accepts-tab")
+ pImpVclMEdit->GetTextWindow()->SetIgnoreTab(!toBool(rValue));
+ else
+ return Edit::set_property(rKey, rValue);
+ return true;
+}
+
+void VclMultiLineEdit::DumpAsPropertyTree(tools::JsonWriter& rJsonWriter)
+{
+ Edit::DumpAsPropertyTree(rJsonWriter);
+
+ rJsonWriter.put("cursor", pImpVclMEdit->GetTextWindow()->GetTextView()->IsCursorEnabled());
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/edit/xtextedt.cxx b/vcl/source/edit/xtextedt.cxx
new file mode 100644
index 0000000000..87097a5be2
--- /dev/null
+++ b/vcl/source/edit/xtextedt.cxx
@@ -0,0 +1,227 @@
+/* -*- 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 <i18nutil/searchopt.hxx>
+#include <i18nlangtag/languagetag.hxx>
+#include <vcl/textdata.hxx>
+#include <vcl/xtextedt.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/settings.hxx>
+#include <unotools/textsearch.hxx>
+#include <com/sun/star/util/SearchFlags.hpp>
+
+using namespace ::com::sun::star;
+
+const std::wstring gaGroupChars = L"(){}[]";
+
+ExtTextEngine::ExtTextEngine()
+{
+}
+
+ExtTextEngine::~ExtTextEngine()
+{
+}
+
+TextSelection ExtTextEngine::MatchGroup( const TextPaM& rCursor ) const
+{
+ TextSelection aSel( rCursor );
+ const sal_Int32 nPos = rCursor.GetIndex();
+ sal_uInt32 nPara = rCursor.GetPara();
+ const sal_uInt32 nParas = GetParagraphCount();
+ if ( ( nPara < nParas ) && ( nPos < GetTextLen( nPara ) ) )
+ {
+ size_t nMatchIndex = gaGroupChars.find( GetText( rCursor.GetPara() )[ nPos ] );
+ if ( nMatchIndex != std::wstring::npos )
+ {
+ if ( ( nMatchIndex % 2 ) == 0 )
+ {
+ // search forwards
+ sal_Unicode nSC = gaGroupChars[ nMatchIndex ];
+ sal_Unicode nEC = gaGroupChars[ nMatchIndex+1 ];
+
+ sal_Int32 nCur = nPos+1;
+ sal_uInt16 nLevel = 1;
+ while ( nLevel && ( nPara < nParas ) )
+ {
+ OUString aStr = GetText( nPara );
+ while ( nCur < aStr.getLength() )
+ {
+ if ( aStr[nCur] == nSC )
+ nLevel++;
+ else if ( aStr[nCur] == nEC )
+ {
+ nLevel--;
+ if ( !nLevel )
+ break; // while nCur...
+ }
+ nCur++;
+ }
+
+ if ( nLevel )
+ {
+ nPara++;
+ nCur = 0;
+ }
+ }
+ if ( nLevel == 0 ) // found
+ {
+ aSel.GetStart() = rCursor;
+ aSel.GetEnd() = TextPaM( nPara, nCur+1 );
+ }
+ }
+ else
+ {
+ // search backwards
+ sal_Unicode nEC = gaGroupChars[ nMatchIndex ];
+ sal_Unicode nSC = gaGroupChars[ nMatchIndex-1 ];
+
+ sal_Int32 nCur = rCursor.GetIndex()-1;
+ sal_uInt16 nLevel = 1;
+ while ( nLevel )
+ {
+ if ( GetTextLen( nPara ) )
+ {
+ OUString aStr = GetText( nPara );
+ while ( nCur )
+ {
+ if ( aStr[nCur] == nSC )
+ {
+ nLevel--;
+ if ( !nLevel )
+ break; // while nCur...
+ }
+ else if ( aStr[nCur] == nEC )
+ nLevel++;
+
+ nCur--;
+ }
+ }
+
+ if ( nLevel )
+ {
+ if ( nPara )
+ {
+ nPara--;
+ nCur = GetTextLen( nPara )-1; // no matter if negative, as if Len()
+ }
+ else
+ break;
+ }
+ }
+
+ if ( nLevel == 0 ) // found
+ {
+ aSel.GetStart() = rCursor;
+ ++aSel.GetStart().GetIndex(); // behind the char
+ aSel.GetEnd() = TextPaM( nPara, nCur );
+ }
+ }
+ }
+ }
+ return aSel;
+}
+
+bool ExtTextEngine::Search( TextSelection& rSel, const i18nutil::SearchOptions2& rSearchOptions, bool bForward ) const
+{
+ TextSelection aSel( rSel );
+ aSel.Justify();
+
+ bool bSearchInSelection = (0 != (rSearchOptions.searchFlag & util::SearchFlags::REG_NOT_BEGINOFLINE) );
+
+ TextPaM aStartPaM( aSel.GetEnd() );
+ if ( aSel.HasRange() && ( ( bSearchInSelection && bForward ) || ( !bSearchInSelection && !bForward ) ) )
+ {
+ aStartPaM = aSel.GetStart();
+ }
+
+ bool bFound = false;
+ sal_uInt32 nEndNode;
+
+ if ( bSearchInSelection )
+ nEndNode = bForward ? aSel.GetEnd().GetPara() : aSel.GetStart().GetPara();
+ else
+ nEndNode = bForward ? (GetParagraphCount()-1) : 0;
+
+ const sal_uInt32 nStartNode = aStartPaM.GetPara();
+
+ i18nutil::SearchOptions2 aOptions( rSearchOptions );
+ aOptions.Locale = Application::GetSettings().GetLanguageTag().getLocale();
+ utl::TextSearch aSearcher(aOptions);
+
+ // iterate over the paragraphs
+ for ( sal_uInt32 nNode = nStartNode;
+ bForward ? ( nNode <= nEndNode) : ( nNode >= nEndNode );
+ bForward ? nNode++ : nNode-- )
+ {
+ OUString aText = GetText( nNode );
+ sal_Int32 nStartPos = 0;
+ sal_Int32 nEndPos = aText.getLength();
+ if ( nNode == nStartNode )
+ {
+ if ( bForward )
+ nStartPos = aStartPaM.GetIndex();
+ else
+ nEndPos = aStartPaM.GetIndex();
+ }
+ if ( ( nNode == nEndNode ) && bSearchInSelection )
+ {
+ if ( bForward )
+ nEndPos = aSel.GetEnd().GetIndex();
+ else
+ nStartPos = aSel.GetStart().GetIndex();
+ }
+
+ if ( bForward )
+ bFound = aSearcher.SearchForward( aText, &nStartPos, &nEndPos );
+ else
+ bFound = aSearcher.SearchBackward( aText, &nEndPos, &nStartPos );
+
+ if ( bFound )
+ {
+ rSel.GetStart().GetPara() = nNode;
+ rSel.GetStart().GetIndex() = nStartPos;
+ rSel.GetEnd().GetPara() = nNode;
+ rSel.GetEnd().GetIndex() = nEndPos;
+ // Select over the paragraph?
+ // FIXME This should be max long...
+ if( nEndPos == -1)
+ {
+ if ( (rSel.GetEnd().GetPara()+1) < GetParagraphCount() )
+ {
+ rSel.GetEnd().GetPara()++;
+ rSel.GetEnd().GetIndex() = 0;
+ }
+ else
+ {
+ rSel.GetEnd().GetIndex() = nStartPos;
+ bFound = false;
+ }
+ }
+
+ break;
+ }
+
+ if ( !bForward && !nNode ) // if searching backwards, if nEndNode == 0:
+ break;
+ }
+
+ return bFound;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */