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