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