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