xref: /core/vcl/source/edit/textview.cxx (revision be53f326)
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
5  * This Source Code Form is subject to the terms of the Mozilla Public
6  * License, v. 2.0. If a copy of the MPL was not distributed with this
7  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8  *
9  * This file incorporates work covered by the following license notice:
10  *
11  *   Licensed to the Apache Software Foundation (ASF) under one or more
12  *   contributor license agreements. See the NOTICE file distributed
13  *   with this work for additional information regarding copyright
14  *   ownership. The ASF licenses this file to you under the Apache
15  *   License, Version 2.0 (the "License"); you may not use this file
16  *   except in compliance with the License. You may obtain a copy of
17  *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
18  */
19 
20 #include <memory>
21 #include <i18nutil/searchopt.hxx>
22 #include <o3tl/deleter.hxx>
23 #include <vcl/textview.hxx>
24 #include <vcl/texteng.hxx>
25 #include <vcl/settings.hxx>
26 #include "textdoc.hxx"
27 #include <vcl/textdata.hxx>
28 #include <vcl/xtextedt.hxx>
29 #include "textdat2.hxx"
30 #include <vcl/commandevent.hxx>
31 #include <vcl/inputctx.hxx>
32 
33 #include <svl/undo.hxx>
34 #include <vcl/cursor.hxx>
35 #include <vcl/weld.hxx>
36 #include <vcl/window.hxx>
37 #include <vcl/svapp.hxx>
38 #include <tools/stream.hxx>
39 
40 #include <sal/log.hxx>
41 #include <sot/formats.hxx>
42 
43 #include <cppuhelper/weak.hxx>
44 #include <cppuhelper/queryinterface.hxx>
45 #include <com/sun/star/i18n/XBreakIterator.hpp>
46 #include <com/sun/star/i18n/CharacterIteratorMode.hpp>
47 #include <com/sun/star/i18n/WordType.hpp>
48 #include <com/sun/star/datatransfer/UnsupportedFlavorException.hpp>
49 #include <com/sun/star/datatransfer/XTransferable.hpp>
50 #include <com/sun/star/datatransfer/clipboard/XClipboard.hpp>
51 #include <com/sun/star/datatransfer/clipboard/XFlushableClipboard.hpp>
52 #include <com/sun/star/datatransfer/dnd/DNDConstants.hpp>
53 #include <com/sun/star/datatransfer/dnd/XDragGestureRecognizer.hpp>
54 #include <com/sun/star/datatransfer/dnd/XDropTarget.hpp>
55 #include <com/sun/star/util/SearchFlags.hpp>
56 
57 #include <vcl/edit.hxx>
58 
59 #include <sot/exchange.hxx>
60 
61 #include <algorithm>
62 #include <cstddef>
63 
64 TETextDataObject::TETextDataObject( const OUString& rText ) : maText( rText )
65 {
66 }
67 
68 // css::uno::XInterface
69 css::uno::Any TETextDataObject::queryInterface( const css::uno::Type & rType )
70 {
71     css::uno::Any aRet = ::cppu::queryInterface( rType, static_cast< css::datatransfer::XTransferable* >(this) );
72     return (aRet.hasValue() ? aRet : OWeakObject::queryInterface( rType ));
73 }
74 
75 // css::datatransfer::XTransferable
76 css::uno::Any TETextDataObject::getTransferData( const css::datatransfer::DataFlavor& rFlavor )
77 {
78     css::uno::Any aAny;
79 
80     SotClipboardFormatId nT = SotExchange::GetFormat( rFlavor );
81     if ( nT == SotClipboardFormatId::STRING )
82     {
83         aAny <<= maText;
84     }
85     else if ( nT == SotClipboardFormatId::HTML )
86     {
87         sal_uLong nLen = GetHTMLStream().TellEnd();
88         GetHTMLStream().Seek(0);
89 
90         css::uno::Sequence< sal_Int8 > aSeq( nLen );
91         memcpy( aSeq.getArray(), GetHTMLStream().GetData(), nLen );
92         aAny <<= aSeq;
93     }
94     else
95     {
96         throw css::datatransfer::UnsupportedFlavorException();
97     }
98     return aAny;
99 }
100 
101 css::uno::Sequence< css::datatransfer::DataFlavor > TETextDataObject::getTransferDataFlavors(  )
102 {
103     GetHTMLStream().Seek( STREAM_SEEK_TO_END );
104     bool bHTML = GetHTMLStream().Tell() > 0;
105     css::uno::Sequence< css::datatransfer::DataFlavor > aDataFlavors( bHTML ? 2 : 1 );
106     SotExchange::GetFormatDataFlavor( SotClipboardFormatId::STRING, aDataFlavors.getArray()[0] );
107     if ( bHTML )
108         SotExchange::GetFormatDataFlavor( SotClipboardFormatId::HTML, aDataFlavors.getArray()[1] );
109     return aDataFlavors;
110 }
111 
112 sal_Bool TETextDataObject::isDataFlavorSupported( const css::datatransfer::DataFlavor& rFlavor )
113 {
114     SotClipboardFormatId nT = SotExchange::GetFormat( rFlavor );
115     return ( nT == SotClipboardFormatId::STRING );
116 }
117 
118 struct ImpTextView
119 {
120     ExtTextEngine*      mpTextEngine;
121 
122     VclPtr<vcl::Window> mpWindow;
123     TextSelection       maSelection;
124     Point               maStartDocPos;
125 
126     std::unique_ptr<vcl::Cursor, o3tl::default_delete<vcl::Cursor>> mpCursor;
127 
128     std::unique_ptr<TextDDInfo, o3tl::default_delete<TextDDInfo>> mpDDInfo;
129 
130     std::unique_ptr<SelectionEngine> mpSelEngine;
131     std::unique_ptr<TextSelFunctionSet> mpSelFuncSet;
132 
133     css::uno::Reference< css::datatransfer::dnd::XDragSourceListener > mxDnDListener;
134 
135     sal_uInt16              mnTravelXPos;
136 
137     bool                mbAutoScroll            : 1;
138     bool                mbInsertMode            : 1;
139     bool                mbReadOnly              : 1;
140     bool                mbPaintSelection        : 1;
141     bool                mbAutoIndent            : 1;
142     bool                mbHighlightSelection    : 1;
143     bool                mbCursorEnabled         : 1;
144     bool                mbClickedInSelection    : 1;
145     bool                mbCursorAtEndOfLine;
146 };
147 
148 TextView::TextView( ExtTextEngine* pEng, vcl::Window* pWindow ) :
149     mpImpl(new ImpTextView)
150 {
151     pWindow->EnableRTL( false );
152 
153     mpImpl->mpWindow = pWindow;
154     mpImpl->mpTextEngine = pEng;
155 
156     mpImpl->mbPaintSelection = true;
157     mpImpl->mbAutoScroll = true;
158     mpImpl->mbInsertMode = true;
159     mpImpl->mbReadOnly = false;
160     mpImpl->mbHighlightSelection = false;
161     mpImpl->mbAutoIndent = false;
162     mpImpl->mbCursorEnabled = true;
163     mpImpl->mbClickedInSelection = false;
164 //  mbInSelection = false;
165 
166     mpImpl->mnTravelXPos = TRAVEL_X_DONTKNOW;
167 
168     mpImpl->mpSelFuncSet = std::make_unique<TextSelFunctionSet>( this );
169     mpImpl->mpSelEngine = std::make_unique<SelectionEngine>( mpImpl->mpWindow, mpImpl->mpSelFuncSet.get() );
170     mpImpl->mpSelEngine->SetSelectionMode( SelectionMode::Range );
171     mpImpl->mpSelEngine->EnableDrag( true );
172 
173     mpImpl->mpCursor.reset(new vcl::Cursor);
174     mpImpl->mpCursor->Show();
175     pWindow->SetCursor( mpImpl->mpCursor.get() );
176     pWindow->SetInputContext( InputContext( pEng->GetFont(), InputContextFlags::Text|InputContextFlags::ExtText ) );
177 
178     if ( pWindow->GetSettings().GetStyleSettings().GetSelectionOptions() & SelectionOptions::Invert )
179         mpImpl->mbHighlightSelection = true;
180 
181     pWindow->SetLineColor();
182 
183     if ( pWindow->GetDragGestureRecognizer().is() )
184     {
185         vcl::unohelper::DragAndDropWrapper* pDnDWrapper = new vcl::unohelper::DragAndDropWrapper( this );
186         mpImpl->mxDnDListener = pDnDWrapper;
187 
188         css::uno::Reference< css::datatransfer::dnd::XDragGestureListener> xDGL( mpImpl->mxDnDListener, css::uno::UNO_QUERY );
189         pWindow->GetDragGestureRecognizer()->addDragGestureListener( xDGL );
190         css::uno::Reference< css::datatransfer::dnd::XDropTargetListener> xDTL( xDGL, css::uno::UNO_QUERY );
191         pWindow->GetDropTarget()->addDropTargetListener( xDTL );
192         pWindow->GetDropTarget()->setActive( true );
193         pWindow->GetDropTarget()->setDefaultActions( css::datatransfer::dnd::DNDConstants::ACTION_COPY_OR_MOVE );
194     }
195 }
196 
197 TextView::~TextView()
198 {
199     mpImpl->mpSelEngine.reset();
200     mpImpl->mpSelFuncSet.reset();
201 
202     if ( mpImpl->mpWindow->GetCursor() == mpImpl->mpCursor.get() )
203         mpImpl->mpWindow->SetCursor( nullptr );
204 
205     mpImpl->mpCursor.reset();
206     mpImpl->mpDDInfo.reset();
207 }
208 
209 void TextView::Invalidate()
210 {
211     mpImpl->mpWindow->Invalidate();
212 }
213 
214 void TextView::SetSelection( const TextSelection& rTextSel, bool bGotoCursor )
215 {
216     // if someone left an empty attribute and then the Outliner manipulated the selection
217     if ( !mpImpl->maSelection.HasRange() )
218         mpImpl->mpTextEngine->CursorMoved( mpImpl->maSelection.GetStart().GetPara() );
219 
220     // if the selection is manipulated after a KeyInput
221     mpImpl->mpTextEngine->CheckIdleFormatter();
222 
223     HideSelection();
224     TextSelection aNewSel( rTextSel );
225     mpImpl->mpTextEngine->ValidateSelection( aNewSel );
226     ImpSetSelection( aNewSel );
227     ShowSelection();
228     ShowCursor( bGotoCursor );
229 }
230 
231 void TextView::SetSelection( const TextSelection& rTextSel )
232 {
233     SetSelection( rTextSel, mpImpl->mbAutoScroll );
234 }
235 
236 const TextSelection& TextView::GetSelection() const
237 {
238     return mpImpl->maSelection;
239 }
240 TextSelection&      TextView::GetSelection()
241 {
242     return mpImpl->maSelection;
243 }
244 
245 void TextView::DeleteSelected()
246 {
247 //  HideSelection();
248 
249     mpImpl->mpTextEngine->UndoActionStart();
250     TextPaM aPaM = mpImpl->mpTextEngine->ImpDeleteText( mpImpl->maSelection );
251     mpImpl->mpTextEngine->UndoActionEnd();
252 
253     ImpSetSelection( aPaM );
254     mpImpl->mpTextEngine->FormatAndUpdate( this );
255     ShowCursor();
256 }
257 
258 void TextView::ImpPaint(vcl::RenderContext& rRenderContext, const Point& rStartPos, tools::Rectangle const* pPaintArea, TextSelection const* pSelection)
259 {
260     if (!mpImpl->mbPaintSelection)
261     {
262         pSelection = nullptr;
263     }
264     else
265     {
266         // set correct background color;
267         // unfortunately we cannot detect if it has changed
268         vcl::Font aFont = mpImpl->mpTextEngine->GetFont();
269         Color aColor = rRenderContext.GetBackground().GetColor();
270         aColor.SetTransparency(0);
271         if (aColor != aFont.GetFillColor())
272         {
273             if (aFont.IsTransparent())
274                 aColor = COL_TRANSPARENT;
275             aFont.SetFillColor(aColor);
276             mpImpl->mpTextEngine->maFont = aFont;
277         }
278     }
279 
280     mpImpl->mpTextEngine->ImpPaint(&rRenderContext, rStartPos, pPaintArea, pSelection);
281 }
282 
283 void TextView::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect)
284 {
285     ImpPaint(rRenderContext, rRect);
286 }
287 
288 void TextView::ImpPaint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect)
289 {
290     if ( !mpImpl->mpTextEngine->GetUpdateMode() || mpImpl->mpTextEngine->IsInUndo() )
291         return;
292 
293     TextSelection *pDrawSelection = nullptr;
294     if (!mpImpl->mbHighlightSelection && mpImpl->maSelection.HasRange())
295         pDrawSelection = &mpImpl->maSelection;
296 
297     Point aStartPos = ImpGetOutputStartPos(mpImpl->maStartDocPos);
298     ImpPaint(rRenderContext, aStartPos, &rRect, pDrawSelection);
299     if (mpImpl->mbHighlightSelection)
300         ImpHighlight(mpImpl->maSelection);
301 }
302 
303 void TextView::ImpHighlight( const TextSelection& rSel )
304 {
305     TextSelection aSel( rSel );
306     aSel.Justify();
307     if ( aSel.HasRange() && !mpImpl->mpTextEngine->IsInUndo() && mpImpl->mpTextEngine->GetUpdateMode() )
308     {
309         mpImpl->mpCursor->Hide();
310 
311         SAL_WARN_IF( mpImpl->mpTextEngine->mpIdleFormatter->IsActive(), "vcl", "ImpHighlight: Not formatted!" );
312 
313         tools::Rectangle aVisArea( mpImpl->maStartDocPos, mpImpl->mpWindow->GetOutputSizePixel() );
314         long nY = 0;
315         const sal_uInt32 nStartPara = aSel.GetStart().GetPara();
316         const sal_uInt32 nEndPara = aSel.GetEnd().GetPara();
317         for ( sal_uInt32 nPara = 0; nPara <= nEndPara; ++nPara )
318         {
319             const long nParaHeight = mpImpl->mpTextEngine->CalcParaHeight( nPara );
320             if ( ( nPara >= nStartPara ) && ( ( nY + nParaHeight ) > aVisArea.Top() ) )
321             {
322                 TEParaPortion* pTEParaPortion = mpImpl->mpTextEngine->mpTEParaPortions->GetObject( nPara );
323                 std::vector<TextLine>::size_type nStartLine = 0;
324                 std::vector<TextLine>::size_type nEndLine = pTEParaPortion->GetLines().size() -1;
325                 if ( nPara == nStartPara )
326                     nStartLine = pTEParaPortion->GetLineNumber( aSel.GetStart().GetIndex(), false );
327                 if ( nPara == nEndPara )
328                     nEndLine = pTEParaPortion->GetLineNumber( aSel.GetEnd().GetIndex(), true );
329 
330                 // iterate over all lines
331                 for ( std::vector<TextLine>::size_type nLine = nStartLine; nLine <= nEndLine; nLine++ )
332                 {
333                     TextLine& rLine = pTEParaPortion->GetLines()[ nLine ];
334                     sal_Int32 nStartIndex = rLine.GetStart();
335                     sal_Int32 nEndIndex = rLine.GetEnd();
336                     if ( ( nPara == nStartPara ) && ( nLine == nStartLine ) )
337                         nStartIndex = aSel.GetStart().GetIndex();
338                     if ( ( nPara == nEndPara ) && ( nLine == nEndLine ) )
339                         nEndIndex = aSel.GetEnd().GetIndex();
340 
341                     // possible if at the beginning of a wrapped line
342                     if ( nEndIndex < nStartIndex )
343                         nEndIndex = nStartIndex;
344 
345                     tools::Rectangle aTmpRect( mpImpl->mpTextEngine->GetEditCursor( TextPaM( nPara, nStartIndex ), false ) );
346                     aTmpRect.AdjustTop(nY );
347                     aTmpRect.AdjustBottom(nY );
348                     Point aTopLeft( aTmpRect.TopLeft() );
349 
350                     aTmpRect = mpImpl->mpTextEngine->GetEditCursor( TextPaM( nPara, nEndIndex ), true );
351                     aTmpRect.AdjustTop(nY );
352                     aTmpRect.AdjustBottom(nY );
353                     Point aBottomRight( aTmpRect.BottomRight() );
354                     aBottomRight.AdjustX( -1 );
355 
356                     // only paint if in the visible region
357                     if ( ( aTopLeft.X() < aBottomRight.X() ) && ( aBottomRight.Y() >= aVisArea.Top() ) )
358                     {
359                         Point aPnt1( GetWindowPos( aTopLeft ) );
360                         Point aPnt2( GetWindowPos( aBottomRight ) );
361 
362                         tools::Rectangle aRect( aPnt1, aPnt2 );
363                         mpImpl->mpWindow->Invert( aRect );
364                     }
365                 }
366             }
367             nY += nParaHeight;
368 
369             if ( nY >= aVisArea.Bottom() )
370                 break;
371         }
372     }
373 }
374 
375 void TextView::ImpSetSelection( const TextSelection& rSelection )
376 {
377     if (rSelection != mpImpl->maSelection)
378     {
379         bool bCaret = false, bSelection = false;
380         const TextPaM &rEnd = rSelection.GetEnd();
381         const TextPaM &rOldEnd = mpImpl->maSelection.GetEnd();
382         bool bGap = rSelection.HasRange(), bOldGap = mpImpl->maSelection.HasRange();
383         if (rEnd != rOldEnd)
384             bCaret = true;
385         if (bGap || bOldGap)
386             bSelection = true;
387 
388         mpImpl->maSelection = rSelection;
389 
390         if (bSelection)
391             mpImpl->mpTextEngine->Broadcast(TextHint(SfxHintId::TextViewSelectionChanged));
392 
393         if (bCaret)
394             mpImpl->mpTextEngine->Broadcast(TextHint(SfxHintId::TextViewCaretChanged));
395     }
396 }
397 
398 void TextView::ShowSelection()
399 {
400     ImpShowHideSelection();
401 }
402 
403 void TextView::HideSelection()
404 {
405     ImpShowHideSelection();
406 }
407 
408 void TextView::ShowSelection( const TextSelection& rRange )
409 {
410     ImpShowHideSelection( &rRange );
411 }
412 
413 void TextView::ImpShowHideSelection(const TextSelection* pRange)
414 {
415     const TextSelection* pRangeOrSelection = pRange ? pRange : &mpImpl->maSelection;
416 
417     if ( pRangeOrSelection->HasRange() )
418     {
419         if ( mpImpl->mbHighlightSelection )
420         {
421             ImpHighlight( *pRangeOrSelection );
422         }
423         else
424         {
425             if( mpImpl->mpWindow->IsPaintTransparent() )
426                 mpImpl->mpWindow->Invalidate();
427             else
428             {
429                 TextSelection aRange( *pRangeOrSelection );
430                 aRange.Justify();
431                 bool bVisCursor = mpImpl->mpCursor->IsVisible();
432                 mpImpl->mpCursor->Hide();
433                 Invalidate();
434                 if (bVisCursor)
435                     mpImpl->mpCursor->Show();
436             }
437         }
438     }
439 }
440 
441 bool TextView::KeyInput( const KeyEvent& rKeyEvent )
442 {
443     bool bDone      = true;
444     bool bModified  = false;
445     bool bMoved     = false;
446     bool bEndKey    = false;    // special CursorPosition
447     bool bAllowIdle = true;
448 
449     // check mModified;
450     // the local bModified is not set e.g. by Cut/Paste, as here
451     // the update happens somewhere else
452     bool bWasModified = mpImpl->mpTextEngine->IsModified();
453     mpImpl->mpTextEngine->SetModified( false );
454 
455     TextSelection aCurSel( mpImpl->maSelection );
456     TextSelection aOldSel( aCurSel );
457 
458     sal_uInt16 nCode = rKeyEvent.GetKeyCode().GetCode();
459     KeyFuncType eFunc = rKeyEvent.GetKeyCode().GetFunction();
460     if ( eFunc != KeyFuncType::DONTKNOW )
461     {
462         switch ( eFunc )
463         {
464             case KeyFuncType::CUT:
465             {
466                 if ( !mpImpl->mbReadOnly )
467                     Cut();
468             }
469             break;
470             case KeyFuncType::COPY:
471             {
472                 Copy();
473             }
474             break;
475             case KeyFuncType::PASTE:
476             {
477                 if ( !mpImpl->mbReadOnly )
478                     Paste();
479             }
480             break;
481             case KeyFuncType::UNDO:
482             {
483                 if ( !mpImpl->mbReadOnly )
484                     Undo();
485             }
486             break;
487             case KeyFuncType::REDO:
488             {
489                 if ( !mpImpl->mbReadOnly )
490                     Redo();
491             }
492             break;
493 
494             default:    // might get processed below
495                         eFunc = KeyFuncType::DONTKNOW;
496         }
497     }
498     if ( eFunc == KeyFuncType::DONTKNOW )
499     {
500         switch ( nCode )
501         {
502             case KEY_UP:
503             case KEY_DOWN:
504             case KEY_LEFT:
505             case KEY_RIGHT:
506             case KEY_HOME:
507             case KEY_END:
508             case KEY_PAGEUP:
509             case KEY_PAGEDOWN:
510             case css::awt::Key::MOVE_WORD_FORWARD:
511             case css::awt::Key::SELECT_WORD_FORWARD:
512             case css::awt::Key::MOVE_WORD_BACKWARD:
513             case css::awt::Key::SELECT_WORD_BACKWARD:
514             case css::awt::Key::MOVE_TO_BEGIN_OF_LINE:
515             case css::awt::Key::MOVE_TO_END_OF_LINE:
516             case css::awt::Key::SELECT_TO_BEGIN_OF_LINE:
517             case css::awt::Key::SELECT_TO_END_OF_LINE:
518             case css::awt::Key::MOVE_TO_BEGIN_OF_PARAGRAPH:
519             case css::awt::Key::MOVE_TO_END_OF_PARAGRAPH:
520             case css::awt::Key::SELECT_TO_BEGIN_OF_PARAGRAPH:
521             case css::awt::Key::SELECT_TO_END_OF_PARAGRAPH:
522             case css::awt::Key::MOVE_TO_BEGIN_OF_DOCUMENT:
523             case css::awt::Key::MOVE_TO_END_OF_DOCUMENT:
524             case css::awt::Key::SELECT_TO_BEGIN_OF_DOCUMENT:
525             case css::awt::Key::SELECT_TO_END_OF_DOCUMENT:
526             {
527                 if ( ( !rKeyEvent.GetKeyCode().IsMod2() || ( nCode == KEY_LEFT ) || ( nCode == KEY_RIGHT ) )
528                       && !( rKeyEvent.GetKeyCode().IsMod1() && ( nCode == KEY_PAGEUP || nCode == KEY_PAGEDOWN ) ) )
529                 {
530                     aCurSel = ImpMoveCursor( rKeyEvent );
531                     if ( aCurSel.HasRange() ) {
532                         css::uno::Reference<css::datatransfer::clipboard::XClipboard> aSelection(GetWindow()->GetPrimarySelection());
533                         Copy( aSelection );
534                     }
535                     bMoved = true;
536                     if ( nCode == KEY_END )
537                         bEndKey = true;
538                 }
539                 else
540                     bDone = false;
541             }
542             break;
543             case KEY_BACKSPACE:
544             case KEY_DELETE:
545             case css::awt::Key::DELETE_WORD_BACKWARD:
546             case css::awt::Key::DELETE_WORD_FORWARD:
547             case css::awt::Key::DELETE_TO_BEGIN_OF_LINE:
548             case css::awt::Key::DELETE_TO_END_OF_LINE:
549             {
550                 if ( !mpImpl->mbReadOnly && !rKeyEvent.GetKeyCode().IsMod2() )
551                 {
552                     sal_uInt8 nDel = ( nCode == KEY_DELETE ) ? DEL_RIGHT : DEL_LEFT;
553                     sal_uInt8 nMode = rKeyEvent.GetKeyCode().IsMod1() ? DELMODE_RESTOFWORD : DELMODE_SIMPLE;
554                     if ( ( nMode == DELMODE_RESTOFWORD ) && rKeyEvent.GetKeyCode().IsShift() )
555                         nMode = DELMODE_RESTOFCONTENT;
556 
557                     switch( nCode )
558                     {
559                     case css::awt::Key::DELETE_WORD_BACKWARD:
560                         nDel = DEL_LEFT;
561                         nMode = DELMODE_RESTOFWORD;
562                         break;
563                     case css::awt::Key::DELETE_WORD_FORWARD:
564                         nDel = DEL_RIGHT;
565                         nMode = DELMODE_RESTOFWORD;
566                         break;
567                     case css::awt::Key::DELETE_TO_BEGIN_OF_LINE:
568                         nDel = DEL_LEFT;
569                         nMode = DELMODE_RESTOFCONTENT;
570                         break;
571                     case css::awt::Key::DELETE_TO_END_OF_LINE:
572                         nDel = DEL_RIGHT;
573                         nMode = DELMODE_RESTOFCONTENT;
574                         break;
575                     default: break;
576                     }
577 
578                     mpImpl->mpTextEngine->UndoActionStart();
579                     aCurSel = ImpDelete( nDel, nMode );
580                     mpImpl->mpTextEngine->UndoActionEnd();
581                     bModified = true;
582                     bAllowIdle = false;
583                 }
584                 else
585                     bDone = false;
586             }
587             break;
588             case KEY_TAB:
589             {
590                 if ( !mpImpl->mbReadOnly && !rKeyEvent.GetKeyCode().IsShift() &&
591                         !rKeyEvent.GetKeyCode().IsMod1() && !rKeyEvent.GetKeyCode().IsMod2() &&
592                         ImplCheckTextLen( OUString('x') ) )
593                 {
594                     aCurSel = mpImpl->mpTextEngine->ImpInsertText( aCurSel, '\t', !IsInsertMode() );
595                     bModified = true;
596                 }
597                 else
598                     bDone = false;
599             }
600             break;
601             case KEY_RETURN:
602             {
603                 // do not swallow Shift-RETURN, as this would disable multi-line entries
604                 // in dialogs & property editors
605                 if ( !mpImpl->mbReadOnly && !rKeyEvent.GetKeyCode().IsMod1() &&
606                         !rKeyEvent.GetKeyCode().IsMod2() && ImplCheckTextLen( OUString('x') ) )
607                 {
608                     mpImpl->mpTextEngine->UndoActionStart();
609                     aCurSel = mpImpl->mpTextEngine->ImpInsertParaBreak( aCurSel );
610                     if ( mpImpl->mbAutoIndent )
611                     {
612                         TextNode* pPrev = mpImpl->mpTextEngine->mpDoc->GetNodes()[ aCurSel.GetEnd().GetPara() - 1 ].get();
613                         sal_Int32 n = 0;
614                         while ( ( n < pPrev->GetText().getLength() ) && (
615                                     ( pPrev->GetText()[ n ] == ' ' ) ||
616                                     ( pPrev->GetText()[ n ] == '\t' ) ) )
617                         {
618                             n++;
619                         }
620                         if ( n )
621                             aCurSel = mpImpl->mpTextEngine->ImpInsertText( aCurSel, pPrev->GetText().copy( 0, n ) );
622                     }
623                     mpImpl->mpTextEngine->UndoActionEnd();
624                     bModified = true;
625                 }
626                 else
627                     bDone = false;
628             }
629             break;
630             case KEY_INSERT:
631             {
632                 if ( !mpImpl->mbReadOnly )
633                     SetInsertMode( !IsInsertMode() );
634             }
635             break;
636             default:
637             {
638                 if ( TextEngine::IsSimpleCharInput( rKeyEvent ) )
639                 {
640                     sal_Unicode nCharCode = rKeyEvent.GetCharCode();
641                     if ( !mpImpl->mbReadOnly && ImplCheckTextLen( OUString(nCharCode) ) )    // otherwise swallow the character anyway
642                     {
643                         aCurSel = mpImpl->mpTextEngine->ImpInsertText( nCharCode, aCurSel, !IsInsertMode(), true );
644                         bModified = true;
645                     }
646                 }
647                 else
648                     bDone = false;
649             }
650         }
651     }
652 
653     if ( aCurSel != aOldSel )   // Check if changed, maybe other method already changed mpImpl->maSelection, don't overwrite that!
654         ImpSetSelection( aCurSel );
655 
656     if ( ( nCode != KEY_UP ) && ( nCode != KEY_DOWN ) )
657         mpImpl->mnTravelXPos = TRAVEL_X_DONTKNOW;
658 
659     if ( bModified )
660     {
661         // Idle-Formatter only if AnyInput
662         if ( bAllowIdle && Application::AnyInput( VclInputFlags::KEYBOARD) )
663             mpImpl->mpTextEngine->IdleFormatAndUpdate( this );
664         else
665             mpImpl->mpTextEngine->FormatAndUpdate( this);
666     }
667     else if ( bMoved )
668     {
669         // selection is painted now in ImpMoveCursor
670         ImpShowCursor( mpImpl->mbAutoScroll, true, bEndKey );
671     }
672 
673     if ( mpImpl->mpTextEngine->IsModified() )
674         mpImpl->mpTextEngine->Broadcast( TextHint( SfxHintId::TextModified ) );
675     else if ( bWasModified )
676         mpImpl->mpTextEngine->SetModified( true );
677 
678     return bDone;
679 }
680 
681 void TextView::MouseButtonUp( const MouseEvent& rMouseEvent )
682 {
683     mpImpl->mbClickedInSelection = false;
684     mpImpl->mnTravelXPos = TRAVEL_X_DONTKNOW;
685     mpImpl->mpSelEngine->SelMouseButtonUp( rMouseEvent );
686     if ( rMouseEvent.IsMiddle() && !IsReadOnly() &&
687          ( GetWindow()->GetSettings().GetMouseSettings().GetMiddleButtonAction() == MouseMiddleButtonAction::PasteSelection ) )
688     {
689         css::uno::Reference<css::datatransfer::clipboard::XClipboard> aSelection(GetWindow()->GetPrimarySelection());
690         Paste( aSelection );
691         if ( mpImpl->mpTextEngine->IsModified() )
692             mpImpl->mpTextEngine->Broadcast( TextHint( SfxHintId::TextModified ) );
693     }
694     else if ( rMouseEvent.IsLeft() && GetSelection().HasRange() )
695     {
696         css::uno::Reference<css::datatransfer::clipboard::XClipboard> aSelection(GetWindow()->GetPrimarySelection());
697         Copy( aSelection );
698     }
699 }
700 
701 void TextView::MouseButtonDown( const MouseEvent& rMouseEvent )
702 {
703     mpImpl->mpTextEngine->CheckIdleFormatter();    // for fast typing and MouseButtonDown
704     mpImpl->mnTravelXPos = TRAVEL_X_DONTKNOW;
705     mpImpl->mbClickedInSelection = IsSelectionAtPoint( rMouseEvent.GetPosPixel() );
706 
707     mpImpl->mpTextEngine->SetActiveView( this );
708 
709     mpImpl->mpSelEngine->SelMouseButtonDown( rMouseEvent );
710 
711     // mbu 20.01.2005 - SelMouseButtonDown() possibly triggers a 'selection changed'
712     // notification. The appropriate handler could change the current selection,
713     // which is the case in the MailMerge address block control. To enable select'n'drag
714     // we need to reevaluate the selection after the notification has been fired.
715     mpImpl->mbClickedInSelection = IsSelectionAtPoint( rMouseEvent.GetPosPixel() );
716 
717     // special cases
718     if ( !rMouseEvent.IsShift() && ( rMouseEvent.GetClicks() >= 2 ) )
719     {
720         if ( rMouseEvent.IsMod2() )
721         {
722             HideSelection();
723             ImpSetSelection( mpImpl->maSelection.GetEnd() );
724             SetCursorAtPoint( rMouseEvent.GetPosPixel() );  // not set by SelectionEngine for MOD2
725         }
726 
727         if ( rMouseEvent.GetClicks() == 2 )
728         {
729             // select word
730             if ( mpImpl->maSelection.GetEnd().GetIndex() < mpImpl->mpTextEngine->GetTextLen( mpImpl->maSelection.GetEnd().GetPara() ) )
731             {
732                 HideSelection();
733                 // tdf#57879 - expand selection to include connector punctuations
734                 TextSelection aNewSel;
735                 mpImpl->mpTextEngine->GetWord( mpImpl->maSelection.GetEnd(), &aNewSel.GetStart(), &aNewSel.GetEnd() );
736                 ImpSetSelection( aNewSel );
737                 ShowSelection();
738                 ShowCursor();
739             }
740         }
741         else if ( rMouseEvent.GetClicks() == 3 )
742         {
743             // select paragraph
744             if ( mpImpl->maSelection.GetStart().GetIndex() || ( mpImpl->maSelection.GetEnd().GetIndex() < mpImpl->mpTextEngine->GetTextLen( mpImpl->maSelection.GetEnd().GetPara() ) ) )
745             {
746                 HideSelection();
747                 TextSelection aNewSel( mpImpl->maSelection );
748                 aNewSel.GetStart().GetIndex() = 0;
749                 aNewSel.GetEnd().GetIndex() = mpImpl->mpTextEngine->mpDoc->GetNodes()[ mpImpl->maSelection.GetEnd().GetPara() ]->GetText().getLength();
750                 ImpSetSelection( aNewSel );
751                 ShowSelection();
752                 ShowCursor();
753             }
754         }
755     }
756 }
757 
758 void TextView::MouseMove( const MouseEvent& rMouseEvent )
759 {
760     mpImpl->mnTravelXPos = TRAVEL_X_DONTKNOW;
761     mpImpl->mpSelEngine->SelMouseMove( rMouseEvent );
762 }
763 
764 void TextView::Command( const CommandEvent& rCEvt )
765 {
766     mpImpl->mpTextEngine->CheckIdleFormatter();    // for fast typing and MouseButtonDown
767     mpImpl->mpTextEngine->SetActiveView( this );
768 
769     if ( rCEvt.GetCommand() == CommandEventId::StartExtTextInput )
770     {
771         DeleteSelected();
772         TextNode* pNode = mpImpl->mpTextEngine->mpDoc->GetNodes()[ GetSelection().GetEnd().GetPara() ].get();
773         mpImpl->mpTextEngine->mpIMEInfos = std::make_unique<TEIMEInfos>( GetSelection().GetEnd(), pNode->GetText().copy( GetSelection().GetEnd().GetIndex() ) );
774         mpImpl->mpTextEngine->mpIMEInfos->bWasCursorOverwrite = !IsInsertMode();
775     }
776     else if ( rCEvt.GetCommand() == CommandEventId::EndExtTextInput )
777     {
778         SAL_WARN_IF( !mpImpl->mpTextEngine->mpIMEInfos, "vcl", "CommandEventId::EndExtTextInput => No Start ?" );
779         if( mpImpl->mpTextEngine->mpIMEInfos )
780         {
781             TEParaPortion* pPortion = mpImpl->mpTextEngine->mpTEParaPortions->GetObject( mpImpl->mpTextEngine->mpIMEInfos->aPos.GetPara() );
782             pPortion->MarkSelectionInvalid( mpImpl->mpTextEngine->mpIMEInfos->aPos.GetIndex() );
783 
784             bool bInsertMode = !mpImpl->mpTextEngine->mpIMEInfos->bWasCursorOverwrite;
785 
786             mpImpl->mpTextEngine->mpIMEInfos.reset();
787 
788             mpImpl->mpTextEngine->TextModified();
789             mpImpl->mpTextEngine->FormatAndUpdate( this );
790 
791             SetInsertMode( bInsertMode );
792 
793             if ( mpImpl->mpTextEngine->IsModified() )
794                 mpImpl->mpTextEngine->Broadcast( TextHint( SfxHintId::TextModified ) );
795         }
796     }
797     else if ( rCEvt.GetCommand() == CommandEventId::ExtTextInput )
798     {
799         SAL_WARN_IF( !mpImpl->mpTextEngine->mpIMEInfos, "vcl", "CommandEventId::ExtTextInput => No Start ?" );
800         if( mpImpl->mpTextEngine->mpIMEInfos )
801         {
802             const CommandExtTextInputData* pData = rCEvt.GetExtTextInputData();
803 
804             if ( !pData->IsOnlyCursorChanged() )
805             {
806                 TextSelection aSelect( mpImpl->mpTextEngine->mpIMEInfos->aPos );
807                 aSelect.GetEnd().GetIndex() += mpImpl->mpTextEngine->mpIMEInfos->nLen;
808                 aSelect = mpImpl->mpTextEngine->ImpDeleteText( aSelect );
809                 aSelect = mpImpl->mpTextEngine->ImpInsertText( aSelect, pData->GetText() );
810 
811                 if ( mpImpl->mpTextEngine->mpIMEInfos->bWasCursorOverwrite )
812                 {
813                     const sal_Int32 nOldIMETextLen = mpImpl->mpTextEngine->mpIMEInfos->nLen;
814                     const sal_Int32 nNewIMETextLen = pData->GetText().getLength();
815 
816                     if ( ( nOldIMETextLen > nNewIMETextLen ) &&
817                          ( nNewIMETextLen < mpImpl->mpTextEngine->mpIMEInfos->aOldTextAfterStartPos.getLength() ) )
818                     {
819                         // restore old characters
820                         sal_Int32 nRestore = nOldIMETextLen - nNewIMETextLen;
821                         TextPaM aPaM( mpImpl->mpTextEngine->mpIMEInfos->aPos );
822                         aPaM.GetIndex() += nNewIMETextLen;
823                         mpImpl->mpTextEngine->ImpInsertText( aPaM, mpImpl->mpTextEngine->mpIMEInfos->aOldTextAfterStartPos.copy( nNewIMETextLen, nRestore ) );
824                     }
825                     else if ( ( nOldIMETextLen < nNewIMETextLen ) &&
826                               ( nOldIMETextLen < mpImpl->mpTextEngine->mpIMEInfos->aOldTextAfterStartPos.getLength() ) )
827                     {
828                         // overwrite
829                         const sal_Int32 nOverwrite = std::min( nNewIMETextLen, mpImpl->mpTextEngine->mpIMEInfos->aOldTextAfterStartPos.getLength() ) - nOldIMETextLen;
830                         SAL_WARN_IF( !nOverwrite || (nOverwrite >= 0xFF00), "vcl", "IME Overwrite?!" );
831                         TextPaM aPaM( mpImpl->mpTextEngine->mpIMEInfos->aPos );
832                         aPaM.GetIndex() += nNewIMETextLen;
833                         TextSelection aSel( aPaM );
834                         aSel.GetEnd().GetIndex() += nOverwrite;
835                         mpImpl->mpTextEngine->ImpDeleteText( aSel );
836                     }
837                 }
838 
839                 if ( pData->GetTextAttr() )
840                 {
841                     mpImpl->mpTextEngine->mpIMEInfos->CopyAttribs( pData->GetTextAttr(), pData->GetText().getLength() );
842                 }
843                 else
844                 {
845                     mpImpl->mpTextEngine->mpIMEInfos->DestroyAttribs();
846                 }
847 
848                 TEParaPortion* pPPortion = mpImpl->mpTextEngine->mpTEParaPortions->GetObject( mpImpl->mpTextEngine->mpIMEInfos->aPos.GetPara() );
849                 pPPortion->MarkSelectionInvalid( mpImpl->mpTextEngine->mpIMEInfos->aPos.GetIndex() );
850                 mpImpl->mpTextEngine->FormatAndUpdate( this );
851             }
852 
853             TextSelection aNewSel = TextPaM( mpImpl->mpTextEngine->mpIMEInfos->aPos.GetPara(), mpImpl->mpTextEngine->mpIMEInfos->aPos.GetIndex()+pData->GetCursorPos() );
854             SetSelection( aNewSel );
855             SetInsertMode( !pData->IsCursorOverwrite() );
856 
857             if ( pData->IsCursorVisible() )
858                 ShowCursor();
859             else
860                 HideCursor();
861         }
862     }
863     else if ( rCEvt.GetCommand() == CommandEventId::CursorPos )
864     {
865         if ( mpImpl->mpTextEngine->mpIMEInfos && mpImpl->mpTextEngine->mpIMEInfos->nLen )
866         {
867             TextPaM aPaM( GetSelection().GetEnd() );
868             tools::Rectangle aR1 = mpImpl->mpTextEngine->PaMtoEditCursor( aPaM );
869 
870             sal_Int32 nInputEnd = mpImpl->mpTextEngine->mpIMEInfos->aPos.GetIndex() + mpImpl->mpTextEngine->mpIMEInfos->nLen;
871 
872             if ( !mpImpl->mpTextEngine->IsFormatted() )
873                 mpImpl->mpTextEngine->FormatDoc();
874 
875             TEParaPortion* pParaPortion = mpImpl->mpTextEngine->mpTEParaPortions->GetObject( aPaM.GetPara() );
876             std::vector<TextLine>::size_type nLine = pParaPortion->GetLineNumber( aPaM.GetIndex(), true );
877             TextLine& rLine = pParaPortion->GetLines()[ nLine ];
878             if ( nInputEnd > rLine.GetEnd() )
879                 nInputEnd = rLine.GetEnd();
880             tools::Rectangle aR2 = mpImpl->mpTextEngine->PaMtoEditCursor( TextPaM( aPaM.GetPara(), nInputEnd ) );
881 
882             long nWidth = aR2.Left()-aR1.Right();
883             aR1.Move( -GetStartDocPos().X(), -GetStartDocPos().Y() );
884             GetWindow()->SetCursorRect( &aR1, nWidth );
885         }
886         else
887         {
888             GetWindow()->SetCursorRect();
889         }
890     }
891     else
892     {
893         mpImpl->mpSelEngine->Command( rCEvt );
894     }
895 }
896 
897 void TextView::ShowCursor( bool bGotoCursor, bool bForceVisCursor )
898 {
899     // this setting has more weight
900     if ( !mpImpl->mbAutoScroll )
901         bGotoCursor = false;
902     ImpShowCursor( bGotoCursor, bForceVisCursor, false );
903 }
904 
905 void TextView::HideCursor()
906 {
907     mpImpl->mpCursor->Hide();
908 }
909 
910 void TextView::Scroll( long ndX, long ndY )
911 {
912     SAL_WARN_IF( !mpImpl->mpTextEngine->IsFormatted(), "vcl", "Scroll: Not formatted!" );
913 
914     if ( !ndX && !ndY )
915         return;
916 
917     Point aNewStartPos( mpImpl->maStartDocPos );
918 
919     // Vertical:
920     aNewStartPos.AdjustY( -ndY );
921     if ( aNewStartPos.Y() < 0 )
922         aNewStartPos.setY( 0 );
923 
924     // Horizontal:
925     aNewStartPos.AdjustX( -ndX );
926     if ( aNewStartPos.X() < 0 )
927         aNewStartPos.setX( 0 );
928 
929     long nDiffX = mpImpl->maStartDocPos.X() - aNewStartPos.X();
930     long nDiffY = mpImpl->maStartDocPos.Y() - aNewStartPos.Y();
931 
932     if ( nDiffX || nDiffY )
933     {
934         bool bVisCursor = mpImpl->mpCursor->IsVisible();
935         mpImpl->mpCursor->Hide();
936         mpImpl->mpWindow->PaintImmediately();
937         mpImpl->maStartDocPos = aNewStartPos;
938 
939         if ( mpImpl->mpTextEngine->IsRightToLeft() )
940             nDiffX = -nDiffX;
941         mpImpl->mpWindow->Scroll( nDiffX, nDiffY );
942         mpImpl->mpWindow->PaintImmediately();
943         mpImpl->mpCursor->SetPos( mpImpl->mpCursor->GetPos() + Point( nDiffX, nDiffY ) );
944         if ( bVisCursor && !mpImpl->mbReadOnly )
945             mpImpl->mpCursor->Show();
946     }
947 
948     mpImpl->mpTextEngine->Broadcast( TextHint( SfxHintId::TextViewScrolled ) );
949 }
950 
951 void TextView::Undo()
952 {
953     mpImpl->mpTextEngine->SetActiveView( this );
954     mpImpl->mpTextEngine->GetUndoManager().Undo();
955 }
956 
957 void TextView::Redo()
958 {
959     mpImpl->mpTextEngine->SetActiveView( this );
960     mpImpl->mpTextEngine->GetUndoManager().Redo();
961 }
962 
963 void TextView::Cut()
964 {
965     mpImpl->mpTextEngine->UndoActionStart();
966     Copy();
967     DeleteSelected();
968     mpImpl->mpTextEngine->UndoActionEnd();
969 }
970 
971 void TextView::Copy( css::uno::Reference< css::datatransfer::clipboard::XClipboard > const & rxClipboard )
972 {
973     if ( rxClipboard.is() )
974     {
975         TETextDataObject* pDataObj = new TETextDataObject( GetSelected() );
976 
977         SolarMutexReleaser aReleaser;
978 
979         try
980         {
981             rxClipboard->setContents( pDataObj, nullptr );
982 
983             css::uno::Reference< css::datatransfer::clipboard::XFlushableClipboard > xFlushableClipboard( rxClipboard, css::uno::UNO_QUERY );
984             if( xFlushableClipboard.is() )
985                 xFlushableClipboard->flushClipboard();
986         }
987         catch( const css::uno::Exception& )
988         {
989         }
990     }
991 }
992 
993 void TextView::Copy()
994 {
995     css::uno::Reference<css::datatransfer::clipboard::XClipboard> aClipboard(GetWindow()->GetClipboard());
996     Copy( aClipboard );
997 }
998 
999 void TextView::Paste( css::uno::Reference< css::datatransfer::clipboard::XClipboard > const & rxClipboard )
1000 {
1001     if ( rxClipboard.is() )
1002     {
1003         css::uno::Reference< css::datatransfer::XTransferable > xDataObj;
1004 
1005         try
1006             {
1007                 SolarMutexReleaser aReleaser;
1008                 xDataObj = rxClipboard->getContents();
1009             }
1010         catch( const css::uno::Exception& )
1011             {
1012             }
1013 
1014         if ( xDataObj.is() )
1015         {
1016             css::datatransfer::DataFlavor aFlavor;
1017             SotExchange::GetFormatDataFlavor( SotClipboardFormatId::STRING, aFlavor );
1018             if ( xDataObj->isDataFlavorSupported( aFlavor ) )
1019             {
1020                 try
1021                 {
1022                     css::uno::Any aData = xDataObj->getTransferData( aFlavor );
1023                     OUString aText;
1024                     aData >>= aText;
1025                     bool bWasTruncated = false;
1026                     if( mpImpl->mpTextEngine->GetMaxTextLen() != 0 )
1027                         bWasTruncated = ImplTruncateNewText( aText );
1028                     InsertText( aText );
1029                     mpImpl->mpTextEngine->Broadcast( TextHint( SfxHintId::TextModified ) );
1030 
1031                     if( bWasTruncated )
1032                         Edit::ShowTruncationWarning(mpImpl->mpWindow->GetFrameWeld());
1033                 }
1034                 catch( const css::datatransfer::UnsupportedFlavorException& )
1035                 {
1036                 }
1037             }
1038         }
1039     }
1040 }
1041 
1042 void TextView::Paste()
1043 {
1044     css::uno::Reference<css::datatransfer::clipboard::XClipboard> aClipboard(GetWindow()->GetClipboard());
1045     Paste( aClipboard );
1046 }
1047 
1048 OUString TextView::GetSelected()
1049 {
1050     return GetSelected( GetSystemLineEnd() );
1051 }
1052 
1053 OUString TextView::GetSelected( LineEnd aSeparator )
1054 {
1055     return mpImpl->mpTextEngine->GetText( mpImpl->maSelection, aSeparator );
1056 }
1057 
1058 void TextView::SetInsertMode( bool bInsert )
1059 {
1060     if ( mpImpl->mbInsertMode != bInsert )
1061     {
1062         mpImpl->mbInsertMode = bInsert;
1063         ShowCursor( mpImpl->mbAutoScroll, false );
1064     }
1065 }
1066 
1067 void TextView::SetReadOnly( bool bReadOnly )
1068 {
1069     if ( mpImpl->mbReadOnly != bReadOnly )
1070     {
1071         mpImpl->mbReadOnly = bReadOnly;
1072         if ( !mpImpl->mbReadOnly )
1073             ShowCursor( mpImpl->mbAutoScroll, false );
1074         else
1075             HideCursor();
1076 
1077         GetWindow()->SetInputContext( InputContext( mpImpl->mpTextEngine->GetFont(), bReadOnly ? InputContextFlags::Text|InputContextFlags::ExtText : InputContextFlags::NONE ) );
1078     }
1079 }
1080 
1081 TextSelection const & TextView::ImpMoveCursor( const KeyEvent& rKeyEvent )
1082 {
1083     // normally only needed for Up/Down; but who cares
1084     mpImpl->mpTextEngine->CheckIdleFormatter();
1085 
1086     TextPaM aPaM( mpImpl->maSelection.GetEnd() );
1087     TextPaM aOldEnd( aPaM );
1088 
1089     TextDirectionality eTextDirection = TextDirectionality::LeftToRight_TopToBottom;
1090     if ( mpImpl->mpTextEngine->IsRightToLeft() )
1091         eTextDirection = TextDirectionality::RightToLeft_TopToBottom;
1092 
1093     KeyEvent aTranslatedKeyEvent = rKeyEvent.LogicalTextDirectionality( eTextDirection );
1094 
1095     bool bCtrl = aTranslatedKeyEvent.GetKeyCode().IsMod1();
1096     sal_uInt16 nCode = aTranslatedKeyEvent.GetKeyCode().GetCode();
1097 
1098     bool bSelect = aTranslatedKeyEvent.GetKeyCode().IsShift();
1099     switch ( nCode )
1100     {
1101         case KEY_UP:        aPaM = CursorUp( aPaM );
1102                             break;
1103         case KEY_DOWN:      aPaM = CursorDown( aPaM );
1104                             break;
1105         case KEY_HOME:      aPaM = bCtrl ? CursorStartOfDoc() : CursorStartOfLine( aPaM );
1106                             break;
1107         case KEY_END:       aPaM = bCtrl ? CursorEndOfDoc() : CursorEndOfLine( aPaM );
1108                             break;
1109         case KEY_PAGEUP:    aPaM = bCtrl ? CursorStartOfDoc() : PageUp( aPaM );
1110                             break;
1111         case KEY_PAGEDOWN:  aPaM = bCtrl ? CursorEndOfDoc() : PageDown( aPaM );
1112                             break;
1113         case KEY_LEFT:      aPaM = bCtrl ? CursorWordLeft( aPaM ) : CursorLeft( aPaM, aTranslatedKeyEvent.GetKeyCode().IsMod2() ? sal_uInt16(css::i18n::CharacterIteratorMode::SKIPCHARACTER) : sal_uInt16(css::i18n::CharacterIteratorMode::SKIPCELL) );
1114                             break;
1115         case KEY_RIGHT:     aPaM = bCtrl ? CursorWordRight( aPaM ) : CursorRight( aPaM, aTranslatedKeyEvent.GetKeyCode().IsMod2() ? sal_uInt16(css::i18n::CharacterIteratorMode::SKIPCHARACTER) : sal_uInt16(css::i18n::CharacterIteratorMode::SKIPCELL) );
1116                             break;
1117         case css::awt::Key::SELECT_WORD_FORWARD:
1118                             bSelect = true;
1119                             [[fallthrough]];
1120         case css::awt::Key::MOVE_WORD_FORWARD:
1121                             aPaM = CursorWordRight( aPaM );
1122                             break;
1123         case css::awt::Key::SELECT_WORD_BACKWARD:
1124                             bSelect = true;
1125                             [[fallthrough]];
1126         case css::awt::Key::MOVE_WORD_BACKWARD:
1127                             aPaM = CursorWordLeft( aPaM );
1128                             break;
1129         case css::awt::Key::SELECT_TO_BEGIN_OF_LINE:
1130                             bSelect = true;
1131                             [[fallthrough]];
1132         case css::awt::Key::MOVE_TO_BEGIN_OF_LINE:
1133                             aPaM = CursorStartOfLine( aPaM );
1134                             break;
1135         case css::awt::Key::SELECT_TO_END_OF_LINE:
1136                             bSelect = true;
1137                             [[fallthrough]];
1138         case css::awt::Key::MOVE_TO_END_OF_LINE:
1139                             aPaM = CursorEndOfLine( aPaM );
1140                             break;
1141         case css::awt::Key::SELECT_TO_BEGIN_OF_PARAGRAPH:
1142                             bSelect = true;
1143                             [[fallthrough]];
1144         case css::awt::Key::MOVE_TO_BEGIN_OF_PARAGRAPH:
1145                             aPaM = CursorStartOfParagraph( aPaM );
1146                             break;
1147         case css::awt::Key::SELECT_TO_END_OF_PARAGRAPH:
1148                             bSelect = true;
1149                             [[fallthrough]];
1150         case css::awt::Key::MOVE_TO_END_OF_PARAGRAPH:
1151                             aPaM = CursorEndOfParagraph( aPaM );
1152                             break;
1153         case css::awt::Key::SELECT_TO_BEGIN_OF_DOCUMENT:
1154                             bSelect = true;
1155                             [[fallthrough]];
1156         case css::awt::Key::MOVE_TO_BEGIN_OF_DOCUMENT:
1157                             aPaM = CursorStartOfDoc();
1158                             break;
1159         case css::awt::Key::SELECT_TO_END_OF_DOCUMENT:
1160                             bSelect = true;
1161                             [[fallthrough]];
1162         case css::awt::Key::MOVE_TO_END_OF_DOCUMENT:
1163                             aPaM = CursorEndOfDoc();
1164                             break;
1165     }
1166 
1167     // might cause a CreateAnchor or Deselection all
1168     mpImpl->mpSelEngine->CursorPosChanging( bSelect, aTranslatedKeyEvent.GetKeyCode().IsMod1() );
1169 
1170     if ( aOldEnd != aPaM )
1171     {
1172         mpImpl->mpTextEngine->CursorMoved( aOldEnd.GetPara() );
1173 
1174         TextSelection aNewSelection( mpImpl->maSelection );
1175         aNewSelection.GetEnd() = aPaM;
1176         if ( bSelect )
1177         {
1178             // extend the selection
1179             ImpSetSelection( aNewSelection );
1180             ShowSelection( TextSelection( aOldEnd, aPaM ) );
1181         }
1182         else
1183         {
1184             aNewSelection.GetStart() = aPaM;
1185             ImpSetSelection( aNewSelection );
1186         }
1187     }
1188 
1189     return mpImpl->maSelection;
1190 }
1191 
1192 void TextView::InsertText( const OUString& rStr )
1193 {
1194     mpImpl->mpTextEngine->UndoActionStart();
1195 
1196     TextSelection aNewSel = mpImpl->mpTextEngine->ImpInsertText( mpImpl->maSelection, rStr );
1197 
1198     ImpSetSelection( aNewSel );
1199 
1200     mpImpl->mpTextEngine->UndoActionEnd();
1201 
1202     mpImpl->mpTextEngine->FormatAndUpdate( this );
1203 }
1204 
1205 TextPaM TextView::CursorLeft( const TextPaM& rPaM, sal_uInt16 nCharacterIteratorMode )
1206 {
1207     TextPaM aPaM( rPaM );
1208 
1209     if ( aPaM.GetIndex() )
1210     {
1211         TextNode* pNode = mpImpl->mpTextEngine->mpDoc->GetNodes()[ aPaM.GetPara() ].get();
1212         css::uno::Reference < css::i18n::XBreakIterator > xBI = mpImpl->mpTextEngine->GetBreakIterator();
1213         sal_Int32 nCount = 1;
1214         aPaM.GetIndex() = xBI->previousCharacters( pNode->GetText(), aPaM.GetIndex(), mpImpl->mpTextEngine->GetLocale(), nCharacterIteratorMode, nCount, nCount );
1215     }
1216     else if ( aPaM.GetPara() )
1217     {
1218         aPaM.GetPara()--;
1219         TextNode* pNode = mpImpl->mpTextEngine->mpDoc->GetNodes()[ aPaM.GetPara() ].get();
1220         aPaM.GetIndex() = pNode->GetText().getLength();
1221     }
1222     return aPaM;
1223 }
1224 
1225 TextPaM TextView::CursorRight( const TextPaM& rPaM, sal_uInt16 nCharacterIteratorMode )
1226 {
1227     TextPaM aPaM( rPaM );
1228 
1229     TextNode* pNode = mpImpl->mpTextEngine->mpDoc->GetNodes()[ aPaM.GetPara() ].get();
1230     if ( aPaM.GetIndex() < pNode->GetText().getLength() )
1231     {
1232         css::uno::Reference < css::i18n::XBreakIterator > xBI = mpImpl->mpTextEngine->GetBreakIterator();
1233         sal_Int32 nCount = 1;
1234         aPaM.GetIndex() = xBI->nextCharacters( pNode->GetText(), aPaM.GetIndex(), mpImpl->mpTextEngine->GetLocale(), nCharacterIteratorMode, nCount, nCount );
1235     }
1236     else if ( aPaM.GetPara() < ( mpImpl->mpTextEngine->mpDoc->GetNodes().size()-1) )
1237     {
1238         aPaM.GetPara()++;
1239         aPaM.GetIndex() = 0;
1240     }
1241 
1242     return aPaM;
1243 }
1244 
1245 TextPaM TextView::CursorWordLeft( const TextPaM& rPaM )
1246 {
1247     TextPaM aPaM( rPaM );
1248 
1249     if ( aPaM.GetIndex() )
1250     {
1251         // tdf#57879 - expand selection to the left to include connector punctuations
1252         mpImpl->mpTextEngine->GetWord( rPaM, &aPaM );
1253         if ( aPaM.GetIndex() >= rPaM.GetIndex() )
1254         {
1255             TextNode* pNode = mpImpl->mpTextEngine->mpDoc->GetNodes()[ aPaM.GetPara() ].get();
1256             css::uno::Reference < css::i18n::XBreakIterator > xBI = mpImpl->mpTextEngine->GetBreakIterator();
1257             aPaM.GetIndex() = xBI->previousWord( pNode->GetText(), rPaM.GetIndex(), mpImpl->mpTextEngine->GetLocale(), css::i18n::WordType::ANYWORD_IGNOREWHITESPACES ).startPos;
1258             if ( aPaM.GetIndex() > 0 )
1259                 mpImpl->mpTextEngine->GetWord( aPaM, &aPaM );
1260             else
1261                 aPaM.GetIndex() = 0;
1262         }
1263     }
1264     else if ( aPaM.GetPara() )
1265     {
1266         aPaM.GetPara()--;
1267         TextNode* pNode = mpImpl->mpTextEngine->mpDoc->GetNodes()[ aPaM.GetPara() ].get();
1268         aPaM.GetIndex() = pNode->GetText().getLength();
1269     }
1270     return aPaM;
1271 }
1272 
1273 TextPaM TextView::CursorWordRight( const TextPaM& rPaM )
1274 {
1275     TextPaM aPaM( rPaM );
1276 
1277     TextNode* pNode = mpImpl->mpTextEngine->mpDoc->GetNodes()[ aPaM.GetPara() ].get();
1278     if ( aPaM.GetIndex() < pNode->GetText().getLength() )
1279     {
1280         css::uno::Reference < css::i18n::XBreakIterator > xBI = mpImpl->mpTextEngine->GetBreakIterator();
1281         aPaM.GetIndex() = xBI->nextWord(  pNode->GetText(), aPaM.GetIndex(), mpImpl->mpTextEngine->GetLocale(), css::i18n::WordType::ANYWORD_IGNOREWHITESPACES ).endPos;
1282         mpImpl->mpTextEngine->GetWord( aPaM, nullptr, &aPaM );
1283     }
1284     else if ( aPaM.GetPara() < ( mpImpl->mpTextEngine->mpDoc->GetNodes().size()-1) )
1285     {
1286         aPaM.GetPara()++;
1287         aPaM.GetIndex() = 0;
1288     }
1289 
1290     return aPaM;
1291 }
1292 
1293 TextPaM TextView::ImpDelete( sal_uInt8 nMode, sal_uInt8 nDelMode )
1294 {
1295     if ( mpImpl->maSelection.HasRange() )  // only delete selection
1296         return mpImpl->mpTextEngine->ImpDeleteText( mpImpl->maSelection );
1297 
1298     TextPaM aStartPaM = mpImpl->maSelection.GetStart();
1299     TextPaM aEndPaM = aStartPaM;
1300     if ( nMode == DEL_LEFT )
1301     {
1302         if ( nDelMode == DELMODE_SIMPLE )
1303         {
1304             aEndPaM = CursorLeft( aEndPaM, sal_uInt16(css::i18n::CharacterIteratorMode::SKIPCHARACTER) );
1305         }
1306         else if ( nDelMode == DELMODE_RESTOFWORD )
1307         {
1308             TextNode* pNode = mpImpl->mpTextEngine->mpDoc->GetNodes()[  aEndPaM.GetPara() ].get();
1309             css::uno::Reference < css::i18n::XBreakIterator > xBI = mpImpl->mpTextEngine->GetBreakIterator();
1310             css::i18n::Boundary aBoundary = xBI->getWordBoundary( pNode->GetText(), mpImpl->maSelection.GetEnd().GetIndex(), mpImpl->mpTextEngine->GetLocale(), css::i18n::WordType::ANYWORD_IGNOREWHITESPACES, true );
1311             if ( aBoundary.startPos == mpImpl->maSelection.GetEnd().GetIndex() )
1312                 aBoundary = xBI->previousWord( pNode->GetText(), mpImpl->maSelection.GetEnd().GetIndex(), mpImpl->mpTextEngine->GetLocale(), css::i18n::WordType::ANYWORD_IGNOREWHITESPACES );
1313             // #i63506# startPos is -1 when the paragraph starts with a tab
1314             aEndPaM.GetIndex() = std::max<sal_Int32>(aBoundary.startPos, 0);
1315         }
1316         else    // DELMODE_RESTOFCONTENT
1317         {
1318             if ( aEndPaM.GetIndex() != 0 )
1319                 aEndPaM.GetIndex() = 0;
1320             else if ( aEndPaM.GetPara() )
1321             {
1322                 // previous paragraph
1323                 aEndPaM.GetPara()--;
1324                 aEndPaM.GetIndex() = 0;
1325             }
1326         }
1327     }
1328     else
1329     {
1330         if ( nDelMode == DELMODE_SIMPLE )
1331         {
1332             aEndPaM = CursorRight( aEndPaM, sal_uInt16(css::i18n::CharacterIteratorMode::SKIPCELL) );
1333         }
1334         else if ( nDelMode == DELMODE_RESTOFWORD )
1335         {
1336             TextNode* pNode = mpImpl->mpTextEngine->mpDoc->GetNodes()[  aEndPaM.GetPara() ].get();
1337             css::uno::Reference < css::i18n::XBreakIterator > xBI = mpImpl->mpTextEngine->GetBreakIterator();
1338             css::i18n::Boundary aBoundary = xBI->nextWord( pNode->GetText(), mpImpl->maSelection.GetEnd().GetIndex(), mpImpl->mpTextEngine->GetLocale(), css::i18n::WordType::ANYWORD_IGNOREWHITESPACES );
1339             aEndPaM.GetIndex() = aBoundary.startPos;
1340         }
1341         else    // DELMODE_RESTOFCONTENT
1342         {
1343             TextNode* pNode = mpImpl->mpTextEngine->mpDoc->GetNodes()[ aEndPaM.GetPara() ].get();
1344             if ( aEndPaM.GetIndex() < pNode->GetText().getLength() )
1345                 aEndPaM.GetIndex() = pNode->GetText().getLength();
1346             else if ( aEndPaM.GetPara() < ( mpImpl->mpTextEngine->mpDoc->GetNodes().size() - 1 ) )
1347             {
1348                 // next paragraph
1349                 aEndPaM.GetPara()++;
1350                 TextNode* pNextNode = mpImpl->mpTextEngine->mpDoc->GetNodes()[ aEndPaM.GetPara() ].get();
1351                 aEndPaM.GetIndex() = pNextNode->GetText().getLength();
1352             }
1353         }
1354     }
1355 
1356     return mpImpl->mpTextEngine->ImpDeleteText( TextSelection( aStartPaM, aEndPaM ) );
1357 }
1358 
1359 TextPaM TextView::CursorUp( const TextPaM& rPaM )
1360 {
1361     TextPaM aPaM( rPaM );
1362 
1363     long nX;
1364     if ( mpImpl->mnTravelXPos == TRAVEL_X_DONTKNOW )
1365     {
1366         nX = mpImpl->mpTextEngine->GetEditCursor( rPaM, false ).Left();
1367         mpImpl->mnTravelXPos = static_cast<sal_uInt16>(nX)+1;
1368     }
1369     else
1370         nX = mpImpl->mnTravelXPos;
1371 
1372     TEParaPortion* pPPortion = mpImpl->mpTextEngine->mpTEParaPortions->GetObject( rPaM.GetPara() );
1373     std::vector<TextLine>::size_type nLine = pPPortion->GetLineNumber( rPaM.GetIndex(), false );
1374     if ( nLine )    // same paragraph
1375     {
1376         aPaM.GetIndex() = mpImpl->mpTextEngine->GetCharPos( rPaM.GetPara(), nLine-1, nX );
1377         // If we need to go to the end of a line that was wrapped automatically,
1378         // the cursor ends up at the beginning of the 2nd line
1379         // Problem: Last character of an automatically wrapped line = Cursor
1380         TextLine& rLine = pPPortion->GetLines()[ nLine - 1 ];
1381         if ( aPaM.GetIndex() && ( aPaM.GetIndex() == rLine.GetEnd() ) )
1382             --aPaM.GetIndex();
1383     }
1384     else if ( rPaM.GetPara() )  // previous paragraph
1385     {
1386         aPaM.GetPara()--;
1387         pPPortion = mpImpl->mpTextEngine->mpTEParaPortions->GetObject( aPaM.GetPara() );
1388         std::vector<TextLine>::size_type nL = pPPortion->GetLines().size() - 1;
1389         aPaM.GetIndex() = mpImpl->mpTextEngine->GetCharPos( aPaM.GetPara(), nL, nX+1 );
1390     }
1391 
1392     return aPaM;
1393 }
1394 
1395 TextPaM TextView::CursorDown( const TextPaM& rPaM )
1396 {
1397     TextPaM aPaM( rPaM );
1398 
1399     long nX;
1400     if ( mpImpl->mnTravelXPos == TRAVEL_X_DONTKNOW )
1401     {
1402         nX = mpImpl->mpTextEngine->GetEditCursor( rPaM, false ).Left();
1403         mpImpl->mnTravelXPos = static_cast<sal_uInt16>(nX)+1;
1404     }
1405     else
1406         nX = mpImpl->mnTravelXPos;
1407 
1408     TEParaPortion* pPPortion = mpImpl->mpTextEngine->mpTEParaPortions->GetObject( rPaM.GetPara() );
1409     std::vector<TextLine>::size_type nLine = pPPortion->GetLineNumber( rPaM.GetIndex(), false );
1410     if ( nLine < ( pPPortion->GetLines().size() - 1 ) )
1411     {
1412         aPaM.GetIndex() = mpImpl->mpTextEngine->GetCharPos( rPaM.GetPara(), nLine+1, nX );
1413 
1414         // special case CursorUp
1415         TextLine& rLine = pPPortion->GetLines()[ nLine + 1 ];
1416         if ( ( aPaM.GetIndex() == rLine.GetEnd() ) && ( aPaM.GetIndex() > rLine.GetStart() ) && aPaM.GetIndex() < pPPortion->GetNode()->GetText().getLength() )
1417             --aPaM.GetIndex();
1418     }
1419     else if ( rPaM.GetPara() < ( mpImpl->mpTextEngine->mpDoc->GetNodes().size() - 1 ) )   // next paragraph
1420     {
1421         aPaM.GetPara()++;
1422         pPPortion = mpImpl->mpTextEngine->mpTEParaPortions->GetObject( aPaM.GetPara() );
1423         aPaM.GetIndex() = mpImpl->mpTextEngine->GetCharPos( aPaM.GetPara(), 0, nX+1 );
1424         TextLine& rLine = pPPortion->GetLines().front();
1425         if ( ( aPaM.GetIndex() == rLine.GetEnd() ) && ( aPaM.GetIndex() > rLine.GetStart() ) && ( pPPortion->GetLines().size() > 1 ) )
1426             --aPaM.GetIndex();
1427     }
1428 
1429     return aPaM;
1430 }
1431 
1432 TextPaM TextView::CursorStartOfLine( const TextPaM& rPaM )
1433 {
1434     TextPaM aPaM( rPaM );
1435 
1436     TEParaPortion* pPPortion = mpImpl->mpTextEngine->mpTEParaPortions->GetObject( rPaM.GetPara() );
1437     std::vector<TextLine>::size_type nLine = pPPortion->GetLineNumber( aPaM.GetIndex(), false );
1438     TextLine& rLine = pPPortion->GetLines()[ nLine ];
1439     aPaM.GetIndex() = rLine.GetStart();
1440 
1441     return aPaM;
1442 }
1443 
1444 TextPaM TextView::CursorEndOfLine( const TextPaM& rPaM )
1445 {
1446     TextPaM aPaM( rPaM );
1447 
1448     TEParaPortion* pPPortion = mpImpl->mpTextEngine->mpTEParaPortions->GetObject( rPaM.GetPara() );
1449     std::vector<TextLine>::size_type nLine = pPPortion->GetLineNumber( aPaM.GetIndex(), false );
1450     TextLine& rLine = pPPortion->GetLines()[ nLine ];
1451     aPaM.GetIndex() = rLine.GetEnd();
1452 
1453     if ( rLine.GetEnd() > rLine.GetStart() )  // empty line
1454     {
1455         sal_Unicode cLastChar = pPPortion->GetNode()->GetText()[ aPaM.GetIndex()-1 ];
1456         if ( ( cLastChar == ' ' ) && ( aPaM.GetIndex() != pPPortion->GetNode()->GetText().getLength() ) )
1457         {
1458             // for a blank in an automatically-wrapped line it is better to stand before it,
1459             // as the user will intend to stand behind the prior word.
1460             // If there is a change, special case for Pos1 after End!
1461             --aPaM.GetIndex();
1462         }
1463     }
1464     return aPaM;
1465 }
1466 
1467 TextPaM TextView::CursorStartOfParagraph( const TextPaM& rPaM )
1468 {
1469     TextPaM aPaM( rPaM );
1470     aPaM.GetIndex() = 0;
1471     return aPaM;
1472 }
1473 
1474 TextPaM TextView::CursorEndOfParagraph( const TextPaM& rPaM )
1475 {
1476     TextNode* pNode = mpImpl->mpTextEngine->mpDoc->GetNodes()[ rPaM.GetPara() ].get();
1477     TextPaM aPaM( rPaM );
1478     aPaM.GetIndex() = pNode->GetText().getLength();
1479     return aPaM;
1480 }
1481 
1482 TextPaM TextView::CursorStartOfDoc()
1483 {
1484     TextPaM aPaM( 0, 0 );
1485     return aPaM;
1486 }
1487 
1488 TextPaM TextView::CursorEndOfDoc()
1489 {
1490     const sal_uInt32 nNode = static_cast<sal_uInt32>(mpImpl->mpTextEngine->mpDoc->GetNodes().size() - 1);
1491     TextNode* pNode = mpImpl->mpTextEngine->mpDoc->GetNodes()[ nNode ].get();
1492     TextPaM aPaM( nNode, pNode->GetText().getLength() );
1493     return aPaM;
1494 }
1495 
1496 TextPaM TextView::PageUp( const TextPaM& rPaM )
1497 {
1498     tools::Rectangle aRect = mpImpl->mpTextEngine->PaMtoEditCursor( rPaM );
1499     Point aTopLeft = aRect.TopLeft();
1500     aTopLeft.AdjustY( -(mpImpl->mpWindow->GetOutputSizePixel().Height() * 9/10) );
1501     aTopLeft.AdjustX(1 );
1502     if ( aTopLeft.Y() < 0 )
1503         aTopLeft.setY( 0 );
1504 
1505     TextPaM aPaM = mpImpl->mpTextEngine->GetPaM( aTopLeft );
1506     return aPaM;
1507 }
1508 
1509 TextPaM TextView::PageDown( const TextPaM& rPaM )
1510 {
1511     tools::Rectangle aRect = mpImpl->mpTextEngine->PaMtoEditCursor( rPaM );
1512     Point aBottomRight = aRect.BottomRight();
1513     aBottomRight.AdjustY(mpImpl->mpWindow->GetOutputSizePixel().Height() * 9/10 );
1514     aBottomRight.AdjustX(1 );
1515     long nHeight = mpImpl->mpTextEngine->GetTextHeight();
1516     if ( aBottomRight.Y() > nHeight )
1517         aBottomRight.setY( nHeight-1 );
1518 
1519     TextPaM aPaM = mpImpl->mpTextEngine->GetPaM( aBottomRight );
1520     return aPaM;
1521 }
1522 
1523 void TextView::ImpShowCursor( bool bGotoCursor, bool bForceVisCursor, bool bSpecial )
1524 {
1525     if ( mpImpl->mpTextEngine->IsFormatting() )
1526         return;
1527     if ( !mpImpl->mpTextEngine->GetUpdateMode() )
1528         return;
1529     if ( mpImpl->mpTextEngine->IsInUndo() )
1530         return;
1531 
1532     mpImpl->mpTextEngine->CheckIdleFormatter();
1533     if ( !mpImpl->mpTextEngine->IsFormatted() )
1534         mpImpl->mpTextEngine->FormatAndUpdate( this );
1535 
1536     TextPaM aPaM( mpImpl->maSelection.GetEnd() );
1537     tools::Rectangle aEditCursor = mpImpl->mpTextEngine->PaMtoEditCursor( aPaM, bSpecial );
1538 
1539     // Remember that we placed the cursor behind the last character of a line
1540     mpImpl->mbCursorAtEndOfLine = false;
1541     if( bSpecial )
1542     {
1543         TEParaPortion* pParaPortion = mpImpl->mpTextEngine->mpTEParaPortions->GetObject( aPaM.GetPara() );
1544         mpImpl->mbCursorAtEndOfLine =
1545             pParaPortion->GetLineNumber( aPaM.GetIndex(), true ) != pParaPortion->GetLineNumber( aPaM.GetIndex(), false );
1546     }
1547 
1548     if ( !IsInsertMode() && !mpImpl->maSelection.HasRange() )
1549     {
1550         TextNode* pNode = mpImpl->mpTextEngine->mpDoc->GetNodes()[ aPaM.GetPara() ].get();
1551         if ( !pNode->GetText().isEmpty() && ( aPaM.GetIndex() < pNode->GetText().getLength() ) )
1552         {
1553             // If we are behind a portion, and the next portion has other direction, we must change position...
1554             aEditCursor.SetLeft( mpImpl->mpTextEngine->GetEditCursor( aPaM, false, true ).Left() );
1555             aEditCursor.SetRight( aEditCursor.Left() );
1556 
1557             TEParaPortion* pParaPortion = mpImpl->mpTextEngine->mpTEParaPortions->GetObject( aPaM.GetPara() );
1558 
1559             sal_Int32 nTextPortionStart = 0;
1560             std::size_t nTextPortion = pParaPortion->GetTextPortions().FindPortion( aPaM.GetIndex(), nTextPortionStart, true );
1561             TETextPortion* pTextPortion = pParaPortion->GetTextPortions()[ nTextPortion ];
1562             if ( pTextPortion->GetKind() == PORTIONKIND_TAB )
1563             {
1564                 aEditCursor.AdjustRight(pTextPortion->GetWidth() );
1565             }
1566             else
1567             {
1568                 TextPaM aNext = CursorRight( TextPaM( aPaM.GetPara(), aPaM.GetIndex() ), sal_uInt16(css::i18n::CharacterIteratorMode::SKIPCELL) );
1569                 aEditCursor.SetRight( mpImpl->mpTextEngine->GetEditCursor( aNext, true ).Left() );
1570             }
1571         }
1572     }
1573 
1574     Size aOutSz = mpImpl->mpWindow->GetOutputSizePixel();
1575     if ( aEditCursor.GetHeight() > aOutSz.Height() )
1576         aEditCursor.SetBottom( aEditCursor.Top() + aOutSz.Height() - 1 );
1577 
1578     aEditCursor.AdjustLeft( -1 );
1579 
1580     if ( bGotoCursor
1581         // #i81283# protect maStartDocPos against initialization problems
1582         && aOutSz.Width() && aOutSz.Height()
1583     )
1584     {
1585         long nVisStartY = mpImpl->maStartDocPos.Y();
1586         long nVisEndY = mpImpl->maStartDocPos.Y() + aOutSz.Height();
1587         long nVisStartX = mpImpl->maStartDocPos.X();
1588         long nVisEndX = mpImpl->maStartDocPos.X() + aOutSz.Width();
1589         long nMoreX = aOutSz.Width() / 4;
1590 
1591         Point aNewStartPos( mpImpl->maStartDocPos );
1592 
1593         if ( aEditCursor.Bottom() > nVisEndY )
1594         {
1595             aNewStartPos.AdjustY( aEditCursor.Bottom() - nVisEndY);
1596         }
1597         else if ( aEditCursor.Top() < nVisStartY )
1598         {
1599             aNewStartPos.AdjustY( -( nVisStartY - aEditCursor.Top() ) );
1600         }
1601 
1602         if ( aEditCursor.Right() >= nVisEndX )
1603         {
1604             aNewStartPos.AdjustX( aEditCursor.Right() - nVisEndX );
1605 
1606             // do you want some more?
1607             aNewStartPos.AdjustX(nMoreX );
1608         }
1609         else if ( aEditCursor.Left() <= nVisStartX )
1610         {
1611             aNewStartPos.AdjustX( -( nVisStartX - aEditCursor.Left() ) );
1612 
1613             // do you want some more?
1614             aNewStartPos.AdjustX( -nMoreX );
1615         }
1616 
1617         // X can be wrong for the 'some more' above:
1618 //      sal_uLong nMaxTextWidth = mpImpl->mpTextEngine->GetMaxTextWidth();
1619 //      if ( !nMaxTextWidth || ( nMaxTextWidth > 0x7FFFFFFF ) )
1620 //          nMaxTextWidth = 0x7FFFFFFF;
1621 //      long nMaxX = (long)nMaxTextWidth - aOutSz.Width();
1622         long nMaxX = mpImpl->mpTextEngine->CalcTextWidth() - aOutSz.Width();
1623         if ( nMaxX < 0 )
1624             nMaxX = 0;
1625 
1626         if ( aNewStartPos.X() < 0 )
1627             aNewStartPos.setX( 0 );
1628         else if ( aNewStartPos.X() > nMaxX )
1629             aNewStartPos.setX( nMaxX );
1630 
1631         // Y should not be further down than needed
1632         long nYMax = mpImpl->mpTextEngine->GetTextHeight() - aOutSz.Height();
1633         if ( nYMax < 0 )
1634             nYMax = 0;
1635         if ( aNewStartPos.Y() > nYMax )
1636             aNewStartPos.setY( nYMax );
1637 
1638         if ( aNewStartPos != mpImpl->maStartDocPos )
1639             Scroll( -(aNewStartPos.X() - mpImpl->maStartDocPos.X()), -(aNewStartPos.Y() - mpImpl->maStartDocPos.Y()) );
1640     }
1641 
1642     if ( aEditCursor.Right() < aEditCursor.Left() )
1643     {
1644         long n = aEditCursor.Left();
1645         aEditCursor.SetLeft( aEditCursor.Right() );
1646         aEditCursor.SetRight( n );
1647     }
1648 
1649     Point aPoint( GetWindowPos( !mpImpl->mpTextEngine->IsRightToLeft() ? aEditCursor.TopLeft() : aEditCursor.TopRight() ) );
1650     mpImpl->mpCursor->SetPos( aPoint );
1651     mpImpl->mpCursor->SetSize( aEditCursor.GetSize() );
1652     if ( bForceVisCursor && mpImpl->mbCursorEnabled )
1653         mpImpl->mpCursor->Show();
1654 }
1655 
1656 void TextView::SetCursorAtPoint( const Point& rPosPixel )
1657 {
1658     mpImpl->mpTextEngine->CheckIdleFormatter();
1659 
1660     Point aDocPos = GetDocPos( rPosPixel );
1661 
1662     TextPaM aPaM = mpImpl->mpTextEngine->GetPaM( aDocPos );
1663 
1664     // aTmpNewSel: Diff between old and new; not the new selection
1665     TextSelection aTmpNewSel( mpImpl->maSelection.GetEnd(), aPaM );
1666     TextSelection aNewSel( mpImpl->maSelection );
1667     aNewSel.GetEnd() = aPaM;
1668 
1669     if ( !mpImpl->mpSelEngine->HasAnchor() )
1670     {
1671         if ( mpImpl->maSelection.GetStart() != aPaM )
1672             mpImpl->mpTextEngine->CursorMoved( mpImpl->maSelection.GetStart().GetPara() );
1673         aNewSel.GetStart() = aPaM;
1674         ImpSetSelection( aNewSel );
1675     }
1676     else
1677     {
1678         ImpSetSelection( aNewSel );
1679         ShowSelection( aTmpNewSel );
1680     }
1681 
1682     bool bForceCursor = !mpImpl->mpDDInfo; // && !mbInSelection
1683     ImpShowCursor( mpImpl->mbAutoScroll, bForceCursor, false );
1684 }
1685 
1686 bool TextView::IsSelectionAtPoint( const Point& rPosPixel )
1687 {
1688     Point aDocPos = GetDocPos( rPosPixel );
1689     TextPaM aPaM = mpImpl->mpTextEngine->GetPaM( aDocPos );
1690     // BeginDrag is only called, however, if IsSelectionAtPoint()
1691     // Problem: IsSelectionAtPoint is not called by Command()
1692     // if before MBDown returned false.
1693     return IsInSelection( aPaM );
1694 }
1695 
1696 bool TextView::IsInSelection( const TextPaM& rPaM )
1697 {
1698     TextSelection aSel = mpImpl->maSelection;
1699     aSel.Justify();
1700 
1701     const sal_uInt32 nStartNode = aSel.GetStart().GetPara();
1702     const sal_uInt32 nEndNode = aSel.GetEnd().GetPara();
1703     const sal_uInt32 nCurNode = rPaM.GetPara();
1704 
1705     if ( ( nCurNode > nStartNode ) && ( nCurNode < nEndNode ) )
1706         return true;
1707 
1708     if ( nStartNode == nEndNode )
1709     {
1710         if ( nCurNode == nStartNode )
1711             if ( ( rPaM.GetIndex() >= aSel.GetStart().GetIndex() ) && ( rPaM.GetIndex() < aSel.GetEnd().GetIndex() ) )
1712                 return true;
1713     }
1714     else if ( ( nCurNode == nStartNode ) && ( rPaM.GetIndex() >= aSel.GetStart().GetIndex() ) )
1715         return true;
1716     else if ( ( nCurNode == nEndNode ) && ( rPaM.GetIndex() < aSel.GetEnd().GetIndex() ) )
1717         return true;
1718 
1719     return false;
1720 }
1721 
1722 void TextView::ImpHideDDCursor()
1723 {
1724     if ( mpImpl->mpDDInfo && mpImpl->mpDDInfo->mbVisCursor )
1725     {
1726         mpImpl->mpDDInfo->maCursor.Hide();
1727         mpImpl->mpDDInfo->mbVisCursor = false;
1728     }
1729 }
1730 
1731 void TextView::ImpShowDDCursor()
1732 {
1733     if ( !mpImpl->mpDDInfo->mbVisCursor )
1734     {
1735         tools::Rectangle aCursor = mpImpl->mpTextEngine->PaMtoEditCursor( mpImpl->mpDDInfo->maDropPos, true );
1736         aCursor.AdjustRight( 1 );
1737         aCursor.SetPos( GetWindowPos( aCursor.TopLeft() ) );
1738 
1739         mpImpl->mpDDInfo->maCursor.SetWindow( mpImpl->mpWindow );
1740         mpImpl->mpDDInfo->maCursor.SetPos( aCursor.TopLeft() );
1741         mpImpl->mpDDInfo->maCursor.SetSize( aCursor.GetSize() );
1742         mpImpl->mpDDInfo->maCursor.Show();
1743         mpImpl->mpDDInfo->mbVisCursor = true;
1744     }
1745 }
1746 
1747 void TextView::SetPaintSelection( bool bPaint )
1748 {
1749     if ( bPaint != mpImpl->mbPaintSelection )
1750     {
1751         mpImpl->mbPaintSelection = bPaint;
1752         ShowSelection( mpImpl->maSelection );
1753     }
1754 }
1755 
1756 void TextView::Read( SvStream& rInput )
1757 {
1758     mpImpl->mpTextEngine->Read( rInput, &mpImpl->maSelection );
1759     ShowCursor();
1760 }
1761 
1762 bool TextView::ImplTruncateNewText( OUString& rNewText ) const
1763 {
1764     bool bTruncated = false;
1765 
1766     const sal_Int32 nMaxLen = mpImpl->mpTextEngine->GetMaxTextLen();
1767     // 0 means unlimited
1768     if( nMaxLen != 0 )
1769     {
1770         const sal_Int32 nCurLen = mpImpl->mpTextEngine->GetTextLen();
1771 
1772         const sal_Int32 nNewLen = rNewText.getLength();
1773         if ( nCurLen + nNewLen > nMaxLen )
1774         {
1775             // see how much text will be replaced
1776             const sal_Int32 nSelLen = mpImpl->mpTextEngine->GetTextLen( mpImpl->maSelection );
1777             if ( nCurLen + nNewLen - nSelLen > nMaxLen )
1778             {
1779                 const sal_Int32 nTruncatedLen = nMaxLen - (nCurLen - nSelLen);
1780                 rNewText = rNewText.copy( 0, nTruncatedLen );
1781                 bTruncated = true;
1782             }
1783         }
1784     }
1785     return bTruncated;
1786 }
1787 
1788 bool TextView::ImplCheckTextLen( const OUString& rNewText )
1789 {
1790     bool bOK = true;
1791     if ( mpImpl->mpTextEngine->GetMaxTextLen() )
1792     {
1793         sal_Int32 n = mpImpl->mpTextEngine->GetTextLen() + rNewText.getLength();
1794         if ( n > mpImpl->mpTextEngine->GetMaxTextLen() )
1795         {
1796             // calculate how much text is being deleted
1797             n -= mpImpl->mpTextEngine->GetTextLen( mpImpl->maSelection );
1798             if ( n > mpImpl->mpTextEngine->GetMaxTextLen() )
1799                 bOK = false;
1800         }
1801     }
1802     return bOK;
1803 }
1804 
1805 void TextView::dragGestureRecognized( const css::datatransfer::dnd::DragGestureEvent& rDGE )
1806 {
1807     if ( mpImpl->mbClickedInSelection )
1808     {
1809         SolarMutexGuard aVclGuard;
1810 
1811         SAL_WARN_IF( !mpImpl->maSelection.HasRange(), "vcl", "TextView::dragGestureRecognized: mpImpl->mbClickedInSelection, but no selection?" );
1812 
1813         mpImpl->mpDDInfo.reset(new TextDDInfo);
1814         mpImpl->mpDDInfo->mbStarterOfDD = true;
1815 
1816         TETextDataObject* pDataObj = new TETextDataObject( GetSelected() );
1817 
1818         mpImpl->mpCursor->Hide();
1819 
1820         sal_Int8 nActions = css::datatransfer::dnd::DNDConstants::ACTION_COPY;
1821         if ( !IsReadOnly() )
1822             nActions |= css::datatransfer::dnd::DNDConstants::ACTION_MOVE;
1823         rDGE.DragSource->startDrag( rDGE, nActions, 0 /*cursor*/, 0 /*image*/, pDataObj, mpImpl->mxDnDListener );
1824     }
1825 }
1826 
1827 void TextView::dragDropEnd( const css::datatransfer::dnd::DragSourceDropEvent& )
1828 {
1829     ImpHideDDCursor();
1830     mpImpl->mpDDInfo.reset();
1831 }
1832 
1833 void TextView::drop( const css::datatransfer::dnd::DropTargetDropEvent& rDTDE )
1834 {
1835     SolarMutexGuard aVclGuard;
1836 
1837     if ( !mpImpl->mbReadOnly && mpImpl->mpDDInfo )
1838     {
1839         ImpHideDDCursor();
1840 
1841         // Data for deleting after DROP_MOVE:
1842         TextSelection aPrevSel( mpImpl->maSelection );
1843         aPrevSel.Justify();
1844         const sal_uInt32 nPrevParaCount = mpImpl->mpTextEngine->GetParagraphCount();
1845         const sal_Int32 nPrevStartParaLen = mpImpl->mpTextEngine->GetTextLen( aPrevSel.GetStart().GetPara() );
1846 
1847         bool bStarterOfDD = false;
1848         for ( sal_uInt16 nView = mpImpl->mpTextEngine->GetViewCount(); nView && !bStarterOfDD; )
1849             bStarterOfDD = mpImpl->mpTextEngine->GetView( --nView )->mpImpl->mpDDInfo && mpImpl->mpTextEngine->GetView( nView )->mpImpl->mpDDInfo->mbStarterOfDD;
1850 
1851         HideSelection();
1852         ImpSetSelection( mpImpl->mpDDInfo->maDropPos );
1853 
1854         mpImpl->mpTextEngine->UndoActionStart();
1855 
1856         OUString aText;
1857         css::uno::Reference< css::datatransfer::XTransferable > xDataObj = rDTDE.Transferable;
1858         if ( xDataObj.is() )
1859         {
1860             css::datatransfer::DataFlavor aFlavor;
1861             SotExchange::GetFormatDataFlavor( SotClipboardFormatId::STRING, aFlavor );
1862             if ( xDataObj->isDataFlavorSupported( aFlavor ) )
1863             {
1864                 css::uno::Any aData = xDataObj->getTransferData( aFlavor );
1865                 OUString aOUString;
1866                 aData >>= aOUString;
1867                 aText = convertLineEnd(aOUString, LINEEND_LF);
1868             }
1869         }
1870 
1871         if ( !aText.isEmpty() && ( aText[ aText.getLength()-1 ] == LINE_SEP ) )
1872             aText = aText.copy(0, aText.getLength()-1);
1873 
1874         if ( ImplCheckTextLen( aText ) )
1875             ImpSetSelection( mpImpl->mpTextEngine->ImpInsertText( mpImpl->mpDDInfo->maDropPos, aText ) );
1876 
1877         if ( aPrevSel.HasRange() &&
1878                 (( rDTDE.DropAction & css::datatransfer::dnd::DNDConstants::ACTION_MOVE ) || !bStarterOfDD) )
1879         {
1880             // adjust selection if necessary
1881             if ( ( mpImpl->mpDDInfo->maDropPos.GetPara() < aPrevSel.GetStart().GetPara() ) ||
1882                  ( ( mpImpl->mpDDInfo->maDropPos.GetPara() == aPrevSel.GetStart().GetPara() )
1883                         && ( mpImpl->mpDDInfo->maDropPos.GetIndex() < aPrevSel.GetStart().GetIndex() ) ) )
1884             {
1885                 const sal_uInt32 nNewParasBeforeSelection =
1886                     mpImpl->mpTextEngine->GetParagraphCount() - nPrevParaCount;
1887 
1888                 aPrevSel.GetStart().GetPara() += nNewParasBeforeSelection;
1889                 aPrevSel.GetEnd().GetPara() += nNewParasBeforeSelection;
1890 
1891                 if ( mpImpl->mpDDInfo->maDropPos.GetPara() == aPrevSel.GetStart().GetPara() )
1892                 {
1893                     const sal_Int32 nNewChars =
1894                         mpImpl->mpTextEngine->GetTextLen( aPrevSel.GetStart().GetPara() ) - nPrevStartParaLen;
1895 
1896                     aPrevSel.GetStart().GetIndex() += nNewChars;
1897                     if ( aPrevSel.GetStart().GetPara() == aPrevSel.GetEnd().GetPara() )
1898                         aPrevSel.GetEnd().GetIndex() += nNewChars;
1899                 }
1900             }
1901             else
1902             {
1903                 // adjust current selection
1904                 TextPaM aPaM = mpImpl->maSelection.GetStart();
1905                 aPaM.GetPara() -= ( aPrevSel.GetEnd().GetPara() - aPrevSel.GetStart().GetPara() );
1906                 if ( aPrevSel.GetEnd().GetPara() == mpImpl->mpDDInfo->maDropPos.GetPara() )
1907                 {
1908                     aPaM.GetIndex() -= aPrevSel.GetEnd().GetIndex();
1909                     if ( aPrevSel.GetStart().GetPara() == mpImpl->mpDDInfo->maDropPos.GetPara() )
1910                         aPaM.GetIndex() += aPrevSel.GetStart().GetIndex();
1911                 }
1912                 ImpSetSelection( aPaM );
1913 
1914             }
1915             mpImpl->mpTextEngine->ImpDeleteText( aPrevSel );
1916         }
1917 
1918         mpImpl->mpTextEngine->UndoActionEnd();
1919 
1920         mpImpl->mpDDInfo.reset();
1921 
1922         mpImpl->mpTextEngine->FormatAndUpdate( this );
1923 
1924         mpImpl->mpTextEngine->Broadcast( TextHint( SfxHintId::TextModified ) );
1925     }
1926     rDTDE.Context->dropComplete( false/*bChanges*/ );
1927 }
1928 
1929 void TextView::dragEnter( const css::datatransfer::dnd::DropTargetDragEnterEvent& )
1930 {
1931 }
1932 
1933 void TextView::dragExit( const css::datatransfer::dnd::DropTargetEvent& )
1934 {
1935     SolarMutexGuard aVclGuard;
1936     ImpHideDDCursor();
1937 }
1938 
1939 void TextView::dragOver( const css::datatransfer::dnd::DropTargetDragEvent& rDTDE )
1940 {
1941     SolarMutexGuard aVclGuard;
1942 
1943     if (!mpImpl->mpDDInfo)
1944         mpImpl->mpDDInfo.reset(new TextDDInfo);
1945 
1946     TextPaM aPrevDropPos = mpImpl->mpDDInfo->maDropPos;
1947     Point aMousePos( rDTDE.LocationX, rDTDE.LocationY );
1948     Point aDocPos = GetDocPos( aMousePos );
1949     mpImpl->mpDDInfo->maDropPos = mpImpl->mpTextEngine->GetPaM( aDocPos );
1950 
1951     // Don't drop in selection or in read only engine
1952     if ( IsReadOnly() || IsInSelection( mpImpl->mpDDInfo->maDropPos ))
1953     {
1954         ImpHideDDCursor();
1955         rDTDE.Context->rejectDrag();
1956     }
1957     else
1958     {
1959         // delete old Cursor
1960         if ( !mpImpl->mpDDInfo->mbVisCursor || ( aPrevDropPos != mpImpl->mpDDInfo->maDropPos ) )
1961         {
1962             ImpHideDDCursor();
1963             ImpShowDDCursor();
1964         }
1965         rDTDE.Context->acceptDrag( rDTDE.DropAction );
1966     }
1967 }
1968 
1969 Point TextView::ImpGetOutputStartPos( const Point& rStartDocPos ) const
1970 {
1971     Point aStartPos( -rStartDocPos.X(), -rStartDocPos.Y() );
1972     if ( mpImpl->mpTextEngine->IsRightToLeft() )
1973     {
1974         Size aSz = mpImpl->mpWindow->GetOutputSizePixel();
1975         aStartPos.setX( rStartDocPos.X() + aSz.Width() - 1 ); // -1: Start is 0
1976     }
1977     return aStartPos;
1978 }
1979 
1980 Point TextView::GetDocPos( const Point& rWindowPos ) const
1981 {
1982     // Window Position => Document Position
1983 
1984     Point aPoint;
1985 
1986     aPoint.setY( rWindowPos.Y() + mpImpl->maStartDocPos.Y() );
1987 
1988     if ( !mpImpl->mpTextEngine->IsRightToLeft() )
1989     {
1990         aPoint.setX( rWindowPos.X() + mpImpl->maStartDocPos.X() );
1991     }
1992     else
1993     {
1994         Size aSz = mpImpl->mpWindow->GetOutputSizePixel();
1995         aPoint.setX( ( aSz.Width() - 1 ) - rWindowPos.X() + mpImpl->maStartDocPos.X() );
1996     }
1997 
1998     return aPoint;
1999 }
2000 
2001 Point TextView::GetWindowPos( const Point& rDocPos ) const
2002 {
2003     // Document Position => Window Position
2004 
2005     Point aPoint;
2006 
2007     aPoint.setY( rDocPos.Y() - mpImpl->maStartDocPos.Y() );
2008 
2009     if ( !mpImpl->mpTextEngine->IsRightToLeft() )
2010     {
2011         aPoint.setX( rDocPos.X() - mpImpl->maStartDocPos.X() );
2012     }
2013     else
2014     {
2015         Size aSz = mpImpl->mpWindow->GetOutputSizePixel();
2016         aPoint.setX( ( aSz.Width() - 1 ) - ( rDocPos.X() - mpImpl->maStartDocPos.X() ) );
2017     }
2018 
2019     return aPoint;
2020 }
2021 
2022 sal_Int32 TextView::GetLineNumberOfCursorInSelection() const
2023 {
2024  // PROGRESS
2025     sal_Int32 nLineNo = -1;
2026     if( mpImpl->mbCursorEnabled )
2027     {
2028         TextPaM aPaM = GetSelection().GetEnd();
2029         TEParaPortion* pPPortion = mpImpl->mpTextEngine->mpTEParaPortions->GetObject( aPaM.GetPara() );
2030         nLineNo = pPPortion->GetLineNumber( aPaM.GetIndex(), false );
2031             //TODO: std::vector<TextLine>::size_type -> sal_Int32!
2032         if( mpImpl->mbCursorAtEndOfLine )
2033             --nLineNo;
2034     }
2035     return nLineNo;
2036 }
2037 
2038 // (+) class TextSelFunctionSet
2039 
2040 TextSelFunctionSet::TextSelFunctionSet( TextView* pView )
2041 {
2042     mpView = pView;
2043 }
2044 
2045 void TextSelFunctionSet::BeginDrag()
2046 {
2047 }
2048 
2049 void TextSelFunctionSet::CreateAnchor()
2050 {
2051 //  TextSelection aSel( mpView->GetSelection() );
2052 //  aSel.GetStart() = aSel.GetEnd();
2053 //  mpView->SetSelection( aSel );
2054 
2055     // may not be followed by ShowCursor
2056     mpView->HideSelection();
2057     mpView->ImpSetSelection( mpView->mpImpl->maSelection.GetEnd() );
2058 }
2059 
2060 void TextSelFunctionSet::SetCursorAtPoint( const Point& rPointPixel, bool )
2061 {
2062     mpView->SetCursorAtPoint( rPointPixel );
2063 }
2064 
2065 bool TextSelFunctionSet::IsSelectionAtPoint( const Point& rPointPixel )
2066 {
2067     return mpView->IsSelectionAtPoint( rPointPixel );
2068 }
2069 
2070 void TextSelFunctionSet::DeselectAll()
2071 {
2072     CreateAnchor();
2073 }
2074 
2075 void TextSelFunctionSet::DeselectAtPoint( const Point& )
2076 {
2077     // only for multiple selection
2078 }
2079 
2080 void TextSelFunctionSet::DestroyAnchor()
2081 {
2082     // only for multiple selection
2083 }
2084 TextEngine*         TextView::GetTextEngine() const
2085 { return mpImpl->mpTextEngine; }
2086 vcl::Window*             TextView::GetWindow() const
2087 { return mpImpl->mpWindow; }
2088 void                TextView::EnableCursor( bool bEnable )
2089 { mpImpl->mbCursorEnabled = bEnable; }
2090 bool                TextView::IsCursorEnabled() const
2091 { return mpImpl->mbCursorEnabled; }
2092 void                TextView::SetStartDocPos( const Point& rPos )
2093 { mpImpl->maStartDocPos = rPos; }
2094 const Point&        TextView::GetStartDocPos() const
2095 { return mpImpl->maStartDocPos; }
2096 void                TextView::SetAutoIndentMode( bool bAutoIndent )
2097 { mpImpl->mbAutoIndent = bAutoIndent; }
2098 bool                TextView::IsReadOnly() const
2099 { return mpImpl->mbReadOnly; }
2100 void                TextView::SetAutoScroll( bool bAutoScroll )
2101 { mpImpl->mbAutoScroll = bAutoScroll; }
2102 bool                TextView::IsAutoScroll() const
2103 { return mpImpl->mbAutoScroll; }
2104 bool                TextView::HasSelection() const
2105 { return mpImpl->maSelection.HasRange(); }
2106 bool                TextView::IsInsertMode() const
2107 { return mpImpl->mbInsertMode; }
2108 
2109 void TextView::MatchGroup()
2110 {
2111     TextSelection aTmpSel( GetSelection() );
2112     aTmpSel.Justify();
2113     if ( ( aTmpSel.GetStart().GetPara() != aTmpSel.GetEnd().GetPara() ) ||
2114          ( ( aTmpSel.GetEnd().GetIndex() - aTmpSel.GetStart().GetIndex() ) > 1 ) )
2115     {
2116         return;
2117     }
2118 
2119     TextSelection aMatchSel = static_cast<ExtTextEngine*>(GetTextEngine())->MatchGroup( aTmpSel.GetStart() );
2120     if ( aMatchSel.HasRange() )
2121         SetSelection( aMatchSel );
2122 }
2123 
2124 void TextView::CenterPaM( const TextPaM& rPaM )
2125 {
2126     // Get textview size and the corresponding y-coordinates
2127     Size aOutSz = mpImpl->mpWindow->GetOutputSizePixel();
2128     long nVisStartY = mpImpl->maStartDocPos.Y();
2129     long nVisEndY = mpImpl->maStartDocPos.Y() + aOutSz.Height();
2130 
2131     // Retrieve the coordinates of the PaM
2132     tools::Rectangle aRect = mpImpl->mpTextEngine->PaMtoEditCursor(rPaM);
2133 
2134     // Recalculate the offset of the center y-coordinates and scroll
2135     Scroll(0, (nVisStartY + nVisEndY) / 2 - aRect.TopLeft().getY());
2136 }
2137 
2138 bool TextView::Search( const i18nutil::SearchOptions& rSearchOptions, bool bForward )
2139 {
2140     bool bFound = false;
2141     TextSelection aSel( GetSelection() );
2142     if ( static_cast<ExtTextEngine*>(GetTextEngine())->Search( aSel, rSearchOptions, bForward ) )
2143     {
2144         bFound = true;
2145         // First add the beginning of the word to the selection,
2146         // so that the whole word is in the visible region.
2147         SetSelection( aSel.GetStart() );
2148         ShowCursor( true, false );
2149     }
2150     else
2151     {
2152         aSel = GetSelection().GetEnd();
2153     }
2154 
2155     SetSelection( aSel );
2156     // tdf#49482: Move the start of the selection to the center of the textview
2157     if (bFound)
2158     {
2159         CenterPaM( aSel.GetStart() );
2160     }
2161     ShowCursor();
2162 
2163     return bFound;
2164 }
2165 
2166 sal_uInt16 TextView::Replace( const i18nutil::SearchOptions& rSearchOptions, bool bAll, bool bForward )
2167 {
2168     sal_uInt16 nFound = 0;
2169 
2170     if ( !bAll )
2171     {
2172         if ( GetSelection().HasRange() )
2173         {
2174             InsertText( rSearchOptions.replaceString );
2175             nFound = 1;
2176             Search( rSearchOptions, bForward ); // right away to the next
2177         }
2178         else
2179         {
2180             if( Search( rSearchOptions, bForward ) )
2181                 nFound = 1;
2182         }
2183     }
2184     else
2185     {
2186         // the writer replaces all, from beginning to end
2187 
2188         ExtTextEngine* pTextEngine = static_cast<ExtTextEngine*>(GetTextEngine());
2189 
2190         // HideSelection();
2191         TextSelection aSel;
2192 
2193         bool bSearchInSelection = (0 != (rSearchOptions.searchFlag & css::util::SearchFlags::REG_NOT_BEGINOFLINE) );
2194         if ( bSearchInSelection )
2195         {
2196             aSel = GetSelection();
2197             aSel.Justify();
2198         }
2199 
2200         TextSelection aSearchSel( aSel );
2201 
2202         bool bFound = pTextEngine->Search( aSel, rSearchOptions );
2203         if ( bFound )
2204             pTextEngine->UndoActionStart();
2205         while ( bFound )
2206         {
2207             nFound++;
2208 
2209             TextPaM aNewStart = pTextEngine->ImpInsertText( aSel, rSearchOptions.replaceString );
2210             aSel = aSearchSel;
2211             aSel.GetStart() = aNewStart;
2212             bFound = pTextEngine->Search( aSel, rSearchOptions );
2213         }
2214         if ( nFound )
2215         {
2216             SetSelection( aSel.GetStart() );
2217             pTextEngine->FormatAndUpdate( this );
2218             pTextEngine->UndoActionEnd();
2219         }
2220     }
2221     return nFound;
2222 }
2223 
2224 bool TextView::ImpIndentBlock( bool bRight )
2225 {
2226     bool bDone = false;
2227 
2228     TextSelection aSel = GetSelection();
2229     aSel.Justify();
2230 
2231     HideSelection();
2232     GetTextEngine()->UndoActionStart();
2233 
2234     const sal_uInt32 nStartPara = aSel.GetStart().GetPara();
2235     sal_uInt32 nEndPara = aSel.GetEnd().GetPara();
2236     if ( aSel.HasRange() && !aSel.GetEnd().GetIndex() )
2237     {
2238         nEndPara--; // do not indent
2239     }
2240 
2241     for ( sal_uInt32 nPara = nStartPara; nPara <= nEndPara; ++nPara )
2242     {
2243         if ( bRight )
2244         {
2245             // add tabs
2246             GetTextEngine()->ImpInsertText( TextPaM( nPara, 0 ), '\t' );
2247             bDone = true;
2248         }
2249         else
2250         {
2251             // remove Tabs/Blanks
2252             OUString aText = GetTextEngine()->GetText( nPara );
2253             if ( !aText.isEmpty() && (
2254                     ( aText[ 0 ] == '\t' ) ||
2255                     ( aText[ 0 ] == ' ' ) ) )
2256             {
2257                 GetTextEngine()->ImpDeleteText( TextSelection( TextPaM( nPara, 0 ), TextPaM( nPara, 1 ) ) );
2258                 bDone = true;
2259             }
2260         }
2261     }
2262 
2263     GetTextEngine()->UndoActionEnd();
2264 
2265     bool bRange = aSel.HasRange();
2266     if ( bRight )
2267     {
2268         ++aSel.GetStart().GetIndex();
2269         if ( bRange && ( aSel.GetEnd().GetPara() == nEndPara ) )
2270             ++aSel.GetEnd().GetIndex();
2271     }
2272     else
2273     {
2274         if ( aSel.GetStart().GetIndex() )
2275             --aSel.GetStart().GetIndex();
2276         if ( bRange && aSel.GetEnd().GetIndex() )
2277             --aSel.GetEnd().GetIndex();
2278     }
2279 
2280     ImpSetSelection( aSel );
2281     GetTextEngine()->FormatAndUpdate( this );
2282 
2283     return bDone;
2284 }
2285 
2286 bool TextView::IndentBlock()
2287 {
2288     return ImpIndentBlock( true );
2289 }
2290 
2291 bool TextView::UnindentBlock()
2292 {
2293     return ImpIndentBlock( false );
2294 }
2295 
2296 
2297 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
2298