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