nss: upgrade to release 3.73
[LibreOffice.git] / vcl / source / edit / textview.cxx
blob998bd68952bb5f535b90a355ea629a3783863c78
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
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/.
9 * This file incorporates work covered by the following license notice:
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 .
20 #include <memory>
21 #include <i18nutil/searchopt.hxx>
22 #include <o3tl/deleter.hxx>
23 #include <vcl/textview.hxx>
24 #include <vcl/texteng.hxx>
25 #include <vcl/settings.hxx>
26 #include "textdoc.hxx"
27 #include <vcl/textdata.hxx>
28 #include <vcl/xtextedt.hxx>
29 #include "textdat2.hxx"
30 #include <vcl/commandevent.hxx>
31 #include <vcl/inputctx.hxx>
33 #include <svl/undo.hxx>
34 #include <vcl/cursor.hxx>
35 #include <vcl/weld.hxx>
36 #include <vcl/window.hxx>
37 #include <vcl/svapp.hxx>
38 #include <tools/stream.hxx>
40 #include <sal/log.hxx>
41 #include <sot/formats.hxx>
43 #include <cppuhelper/weak.hxx>
44 #include <cppuhelper/queryinterface.hxx>
45 #include <com/sun/star/i18n/XBreakIterator.hpp>
46 #include <com/sun/star/i18n/CharacterIteratorMode.hpp>
47 #include <com/sun/star/i18n/WordType.hpp>
48 #include <com/sun/star/datatransfer/UnsupportedFlavorException.hpp>
49 #include <com/sun/star/datatransfer/XTransferable.hpp>
50 #include <com/sun/star/datatransfer/clipboard/XClipboard.hpp>
51 #include <com/sun/star/datatransfer/clipboard/XFlushableClipboard.hpp>
52 #include <com/sun/star/datatransfer/dnd/DNDConstants.hpp>
53 #include <com/sun/star/datatransfer/dnd/XDragGestureRecognizer.hpp>
54 #include <com/sun/star/datatransfer/dnd/XDropTarget.hpp>
55 #include <com/sun/star/util/SearchFlags.hpp>
57 #include <vcl/toolkit/edit.hxx>
59 #include <sot/exchange.hxx>
61 #include <algorithm>
62 #include <cstddef>
64 TETextDataObject::TETextDataObject( const OUString& rText ) : maText( rText )
68 // css::uno::XInterface
69 css::uno::Any TETextDataObject::queryInterface( const css::uno::Type & rType )
71 css::uno::Any aRet = ::cppu::queryInterface( rType, static_cast< css::datatransfer::XTransferable* >(this) );
72 return (aRet.hasValue() ? aRet : OWeakObject::queryInterface( rType ));
75 // css::datatransfer::XTransferable
76 css::uno::Any TETextDataObject::getTransferData( const css::datatransfer::DataFlavor& rFlavor )
78 css::uno::Any aAny;
80 SotClipboardFormatId nT = SotExchange::GetFormat( rFlavor );
81 if ( nT == SotClipboardFormatId::STRING )
83 aAny <<= maText;
85 else if ( nT == SotClipboardFormatId::HTML )
87 sal_uLong nLen = GetHTMLStream().TellEnd();
88 GetHTMLStream().Seek(0);
90 css::uno::Sequence< sal_Int8 > aSeq( nLen );
91 memcpy( aSeq.getArray(), GetHTMLStream().GetData(), nLen );
92 aAny <<= aSeq;
94 else
96 throw css::datatransfer::UnsupportedFlavorException();
98 return aAny;
101 css::uno::Sequence< css::datatransfer::DataFlavor > TETextDataObject::getTransferDataFlavors( )
103 GetHTMLStream().Seek( STREAM_SEEK_TO_END );
104 bool bHTML = GetHTMLStream().Tell() > 0;
105 css::uno::Sequence< css::datatransfer::DataFlavor > aDataFlavors( bHTML ? 2 : 1 );
106 SotExchange::GetFormatDataFlavor( SotClipboardFormatId::STRING, aDataFlavors.getArray()[0] );
107 if ( bHTML )
108 SotExchange::GetFormatDataFlavor( SotClipboardFormatId::HTML, aDataFlavors.getArray()[1] );
109 return aDataFlavors;
112 sal_Bool TETextDataObject::isDataFlavorSupported( const css::datatransfer::DataFlavor& rFlavor )
114 SotClipboardFormatId nT = SotExchange::GetFormat( rFlavor );
115 return ( nT == SotClipboardFormatId::STRING );
118 struct ImpTextView
120 ExtTextEngine* mpTextEngine;
122 VclPtr<vcl::Window> mpWindow;
123 TextSelection maSelection;
124 Point maStartDocPos;
126 std::unique_ptr<vcl::Cursor, o3tl::default_delete<vcl::Cursor>> mpCursor;
128 std::unique_ptr<TextDDInfo, o3tl::default_delete<TextDDInfo>> mpDDInfo;
130 std::unique_ptr<SelectionEngine> mpSelEngine;
131 std::unique_ptr<TextSelFunctionSet> mpSelFuncSet;
133 css::uno::Reference< css::datatransfer::dnd::XDragSourceListener > mxDnDListener;
135 sal_uInt16 mnTravelXPos;
137 bool mbAutoScroll : 1;
138 bool mbInsertMode : 1;
139 bool mbReadOnly : 1;
140 bool mbPaintSelection : 1;
141 bool mbAutoIndent : 1;
142 bool mbHighlightSelection : 1;
143 bool mbCursorEnabled : 1;
144 bool mbClickedInSelection : 1;
145 bool mbCursorAtEndOfLine;
148 TextView::TextView( ExtTextEngine* pEng, vcl::Window* pWindow ) :
149 mpImpl(new ImpTextView)
151 pWindow->EnableRTL( false );
153 mpImpl->mpWindow = pWindow;
154 mpImpl->mpTextEngine = pEng;
156 mpImpl->mbPaintSelection = true;
157 mpImpl->mbAutoScroll = true;
158 mpImpl->mbInsertMode = true;
159 mpImpl->mbReadOnly = false;
160 mpImpl->mbHighlightSelection = false;
161 mpImpl->mbAutoIndent = false;
162 mpImpl->mbCursorEnabled = true;
163 mpImpl->mbClickedInSelection = false;
164 // mbInSelection = false;
166 mpImpl->mnTravelXPos = TRAVEL_X_DONTKNOW;
168 mpImpl->mpSelFuncSet = std::make_unique<TextSelFunctionSet>( this );
169 mpImpl->mpSelEngine = std::make_unique<SelectionEngine>( mpImpl->mpWindow, mpImpl->mpSelFuncSet.get() );
170 mpImpl->mpSelEngine->SetSelectionMode( SelectionMode::Range );
171 mpImpl->mpSelEngine->EnableDrag( true );
173 mpImpl->mpCursor.reset(new vcl::Cursor);
174 mpImpl->mpCursor->Show();
175 pWindow->SetCursor( mpImpl->mpCursor.get() );
176 pWindow->SetInputContext( InputContext( pEng->GetFont(), InputContextFlags::Text|InputContextFlags::ExtText ) );
178 if ( pWindow->GetSettings().GetStyleSettings().GetSelectionOptions() & SelectionOptions::Invert )
179 mpImpl->mbHighlightSelection = true;
181 pWindow->SetLineColor();
183 if ( pWindow->GetDragGestureRecognizer().is() )
185 vcl::unohelper::DragAndDropWrapper* pDnDWrapper = new vcl::unohelper::DragAndDropWrapper( this );
186 mpImpl->mxDnDListener = pDnDWrapper;
188 css::uno::Reference< css::datatransfer::dnd::XDragGestureListener> xDGL( mpImpl->mxDnDListener, css::uno::UNO_QUERY );
189 pWindow->GetDragGestureRecognizer()->addDragGestureListener( xDGL );
190 css::uno::Reference< css::datatransfer::dnd::XDropTargetListener> xDTL( xDGL, css::uno::UNO_QUERY );
191 pWindow->GetDropTarget()->addDropTargetListener( xDTL );
192 pWindow->GetDropTarget()->setActive( true );
193 pWindow->GetDropTarget()->setDefaultActions( css::datatransfer::dnd::DNDConstants::ACTION_COPY_OR_MOVE );
197 TextView::~TextView()
199 mpImpl->mpSelEngine.reset();
200 mpImpl->mpSelFuncSet.reset();
202 if ( mpImpl->mpWindow->GetCursor() == mpImpl->mpCursor.get() )
203 mpImpl->mpWindow->SetCursor( nullptr );
205 mpImpl->mpCursor.reset();
206 mpImpl->mpDDInfo.reset();
209 void TextView::Invalidate()
211 mpImpl->mpWindow->Invalidate();
214 void TextView::SetSelection( const TextSelection& rTextSel, bool bGotoCursor )
216 // if someone left an empty attribute and then the Outliner manipulated the selection
217 if ( !mpImpl->maSelection.HasRange() )
218 mpImpl->mpTextEngine->CursorMoved( mpImpl->maSelection.GetStart().GetPara() );
220 // if the selection is manipulated after a KeyInput
221 mpImpl->mpTextEngine->CheckIdleFormatter();
223 HideSelection();
224 TextSelection aNewSel( rTextSel );
225 mpImpl->mpTextEngine->ValidateSelection( aNewSel );
226 ImpSetSelection( aNewSel );
227 ShowSelection();
228 ShowCursor( bGotoCursor );
231 void TextView::SetSelection( const TextSelection& rTextSel )
233 SetSelection( rTextSel, mpImpl->mbAutoScroll );
236 const TextSelection& TextView::GetSelection() const
238 return mpImpl->maSelection;
240 TextSelection& TextView::GetSelection()
242 return mpImpl->maSelection;
245 void TextView::DeleteSelected()
247 // HideSelection();
249 mpImpl->mpTextEngine->UndoActionStart();
250 TextPaM aPaM = mpImpl->mpTextEngine->ImpDeleteText( mpImpl->maSelection );
251 mpImpl->mpTextEngine->UndoActionEnd();
253 ImpSetSelection( aPaM );
254 mpImpl->mpTextEngine->FormatAndUpdate( this );
255 ShowCursor();
258 void TextView::ImpPaint(vcl::RenderContext& rRenderContext, const Point& rStartPos, tools::Rectangle const* pPaintArea, TextSelection const* pSelection)
260 if (!mpImpl->mbPaintSelection)
262 pSelection = nullptr;
264 else
266 // set correct background color;
267 // unfortunately we cannot detect if it has changed
268 vcl::Font aFont = mpImpl->mpTextEngine->GetFont();
269 Color aColor = rRenderContext.GetBackground().GetColor();
270 aColor.SetTransparency(0);
271 if (aColor != aFont.GetFillColor())
273 if (aFont.IsTransparent())
274 aColor = COL_TRANSPARENT;
275 aFont.SetFillColor(aColor);
276 mpImpl->mpTextEngine->maFont = aFont;
280 mpImpl->mpTextEngine->ImpPaint(&rRenderContext, rStartPos, pPaintArea, pSelection);
283 void TextView::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect)
285 ImpPaint(rRenderContext, rRect);
288 void TextView::ImpPaint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect)
290 if ( !mpImpl->mpTextEngine->GetUpdateMode() || mpImpl->mpTextEngine->IsInUndo() )
291 return;
293 TextSelection *pDrawSelection = nullptr;
294 if (!mpImpl->mbHighlightSelection && mpImpl->maSelection.HasRange())
295 pDrawSelection = &mpImpl->maSelection;
297 Point aStartPos = ImpGetOutputStartPos(mpImpl->maStartDocPos);
298 ImpPaint(rRenderContext, aStartPos, &rRect, pDrawSelection);
299 if (mpImpl->mbHighlightSelection)
300 ImpHighlight(mpImpl->maSelection);
303 void TextView::ImpHighlight( const TextSelection& rSel )
305 TextSelection aSel( rSel );
306 aSel.Justify();
307 if ( !(aSel.HasRange() && !mpImpl->mpTextEngine->IsInUndo() && mpImpl->mpTextEngine->GetUpdateMode()) )
308 return;
310 mpImpl->mpCursor->Hide();
312 SAL_WARN_IF( mpImpl->mpTextEngine->mpIdleFormatter->IsActive(), "vcl", "ImpHighlight: Not formatted!" );
314 tools::Rectangle aVisArea( mpImpl->maStartDocPos, mpImpl->mpWindow->GetOutputSizePixel() );
315 tools::Long nY = 0;
316 const sal_uInt32 nStartPara = aSel.GetStart().GetPara();
317 const sal_uInt32 nEndPara = aSel.GetEnd().GetPara();
318 for ( sal_uInt32 nPara = 0; nPara <= nEndPara; ++nPara )
320 const tools::Long nParaHeight = mpImpl->mpTextEngine->CalcParaHeight( nPara );
321 if ( ( nPara >= nStartPara ) && ( ( nY + nParaHeight ) > aVisArea.Top() ) )
323 TEParaPortion* pTEParaPortion = mpImpl->mpTextEngine->mpTEParaPortions->GetObject( nPara );
324 std::vector<TextLine>::size_type nStartLine = 0;
325 std::vector<TextLine>::size_type nEndLine = pTEParaPortion->GetLines().size() -1;
326 if ( nPara == nStartPara )
327 nStartLine = pTEParaPortion->GetLineNumber( aSel.GetStart().GetIndex(), false );
328 if ( nPara == nEndPara )
329 nEndLine = pTEParaPortion->GetLineNumber( aSel.GetEnd().GetIndex(), true );
331 // iterate over all lines
332 for ( std::vector<TextLine>::size_type nLine = nStartLine; nLine <= nEndLine; nLine++ )
334 TextLine& rLine = pTEParaPortion->GetLines()[ nLine ];
335 sal_Int32 nStartIndex = rLine.GetStart();
336 sal_Int32 nEndIndex = rLine.GetEnd();
337 if ( ( nPara == nStartPara ) && ( nLine == nStartLine ) )
338 nStartIndex = aSel.GetStart().GetIndex();
339 if ( ( nPara == nEndPara ) && ( nLine == nEndLine ) )
340 nEndIndex = aSel.GetEnd().GetIndex();
342 // possible if at the beginning of a wrapped line
343 if ( nEndIndex < nStartIndex )
344 nEndIndex = nStartIndex;
346 tools::Rectangle aTmpRect( mpImpl->mpTextEngine->GetEditCursor( TextPaM( nPara, nStartIndex ), false ) );
347 aTmpRect.AdjustTop(nY );
348 aTmpRect.AdjustBottom(nY );
349 Point aTopLeft( aTmpRect.TopLeft() );
351 aTmpRect = mpImpl->mpTextEngine->GetEditCursor( TextPaM( nPara, nEndIndex ), true );
352 aTmpRect.AdjustTop(nY );
353 aTmpRect.AdjustBottom(nY );
354 Point aBottomRight( aTmpRect.BottomRight() );
355 aBottomRight.AdjustX( -1 );
357 // only paint if in the visible region
358 if ( ( aTopLeft.X() < aBottomRight.X() ) && ( aBottomRight.Y() >= aVisArea.Top() ) )
360 Point aPnt1( GetWindowPos( aTopLeft ) );
361 Point aPnt2( GetWindowPos( aBottomRight ) );
363 tools::Rectangle aRect( aPnt1, aPnt2 );
364 mpImpl->mpWindow->Invert( aRect );
368 nY += nParaHeight;
370 if ( nY >= aVisArea.Bottom() )
371 break;
375 void TextView::ImpSetSelection( const TextSelection& rSelection )
377 if (rSelection == mpImpl->maSelection)
378 return;
380 bool bCaret = false, bSelection = false;
381 const TextPaM &rEnd = rSelection.GetEnd();
382 const TextPaM &rOldEnd = mpImpl->maSelection.GetEnd();
383 bool bGap = rSelection.HasRange(), bOldGap = mpImpl->maSelection.HasRange();
384 if (rEnd != rOldEnd)
385 bCaret = true;
386 if (bGap || bOldGap)
387 bSelection = true;
389 mpImpl->maSelection = rSelection;
391 if (bSelection)
392 mpImpl->mpTextEngine->Broadcast(TextHint(SfxHintId::TextViewSelectionChanged));
394 if (bCaret)
395 mpImpl->mpTextEngine->Broadcast(TextHint(SfxHintId::TextViewCaretChanged));
398 void TextView::ShowSelection()
400 ImpShowHideSelection();
403 void TextView::HideSelection()
405 ImpShowHideSelection();
408 void TextView::ShowSelection( const TextSelection& rRange )
410 ImpShowHideSelection( &rRange );
413 void TextView::ImpShowHideSelection(const TextSelection* pRange)
415 const TextSelection* pRangeOrSelection = pRange ? pRange : &mpImpl->maSelection;
417 if ( !pRangeOrSelection->HasRange() )
418 return;
420 if ( mpImpl->mbHighlightSelection )
422 ImpHighlight( *pRangeOrSelection );
424 else
426 if( mpImpl->mpWindow->IsPaintTransparent() )
427 mpImpl->mpWindow->Invalidate();
428 else
430 TextSelection aRange( *pRangeOrSelection );
431 aRange.Justify();
432 bool bVisCursor = mpImpl->mpCursor->IsVisible();
433 mpImpl->mpCursor->Hide();
434 Invalidate();
435 if (bVisCursor)
436 mpImpl->mpCursor->Show();
441 bool TextView::KeyInput( const KeyEvent& rKeyEvent )
443 bool bDone = true;
444 bool bModified = false;
445 bool bMoved = false;
446 bool bEndKey = false; // special CursorPosition
447 bool bAllowIdle = true;
449 // check mModified;
450 // the local bModified is not set e.g. by Cut/Paste, as here
451 // the update happens somewhere else
452 bool bWasModified = mpImpl->mpTextEngine->IsModified();
453 mpImpl->mpTextEngine->SetModified( false );
455 TextSelection aCurSel( mpImpl->maSelection );
456 TextSelection aOldSel( aCurSel );
458 sal_uInt16 nCode = rKeyEvent.GetKeyCode().GetCode();
459 KeyFuncType eFunc = rKeyEvent.GetKeyCode().GetFunction();
460 if ( eFunc != KeyFuncType::DONTKNOW )
462 switch ( eFunc )
464 case KeyFuncType::CUT:
466 if ( !mpImpl->mbReadOnly )
467 Cut();
469 break;
470 case KeyFuncType::COPY:
472 Copy();
474 break;
475 case KeyFuncType::PASTE:
477 if ( !mpImpl->mbReadOnly )
478 Paste();
480 break;
481 case KeyFuncType::UNDO:
483 if ( !mpImpl->mbReadOnly )
484 Undo();
486 break;
487 case KeyFuncType::REDO:
489 if ( !mpImpl->mbReadOnly )
490 Redo();
492 break;
494 default: // might get processed below
495 eFunc = KeyFuncType::DONTKNOW;
498 if ( eFunc == KeyFuncType::DONTKNOW )
500 switch ( nCode )
502 case KEY_UP:
503 case KEY_DOWN:
504 case KEY_LEFT:
505 case KEY_RIGHT:
506 case KEY_HOME:
507 case KEY_END:
508 case KEY_PAGEUP:
509 case KEY_PAGEDOWN:
510 case css::awt::Key::MOVE_WORD_FORWARD:
511 case css::awt::Key::SELECT_WORD_FORWARD:
512 case css::awt::Key::MOVE_WORD_BACKWARD:
513 case css::awt::Key::SELECT_WORD_BACKWARD:
514 case css::awt::Key::MOVE_TO_BEGIN_OF_LINE:
515 case css::awt::Key::MOVE_TO_END_OF_LINE:
516 case css::awt::Key::SELECT_TO_BEGIN_OF_LINE:
517 case css::awt::Key::SELECT_TO_END_OF_LINE:
518 case css::awt::Key::MOVE_TO_BEGIN_OF_PARAGRAPH:
519 case css::awt::Key::MOVE_TO_END_OF_PARAGRAPH:
520 case css::awt::Key::SELECT_TO_BEGIN_OF_PARAGRAPH:
521 case css::awt::Key::SELECT_TO_END_OF_PARAGRAPH:
522 case css::awt::Key::MOVE_TO_BEGIN_OF_DOCUMENT:
523 case css::awt::Key::MOVE_TO_END_OF_DOCUMENT:
524 case css::awt::Key::SELECT_TO_BEGIN_OF_DOCUMENT:
525 case css::awt::Key::SELECT_TO_END_OF_DOCUMENT:
527 if ( ( !rKeyEvent.GetKeyCode().IsMod2() || ( nCode == KEY_LEFT ) || ( nCode == KEY_RIGHT ) )
528 && !( rKeyEvent.GetKeyCode().IsMod1() && ( nCode == KEY_PAGEUP || nCode == KEY_PAGEDOWN ) ) )
530 aCurSel = ImpMoveCursor( rKeyEvent );
531 if ( aCurSel.HasRange() ) {
532 css::uno::Reference<css::datatransfer::clipboard::XClipboard> aSelection(GetWindow()->GetPrimarySelection());
533 Copy( aSelection );
535 bMoved = true;
536 if ( nCode == KEY_END )
537 bEndKey = true;
539 else
540 bDone = false;
542 break;
543 case KEY_BACKSPACE:
544 case KEY_DELETE:
545 case css::awt::Key::DELETE_WORD_BACKWARD:
546 case css::awt::Key::DELETE_WORD_FORWARD:
547 case css::awt::Key::DELETE_TO_BEGIN_OF_LINE:
548 case css::awt::Key::DELETE_TO_END_OF_LINE:
550 if ( !mpImpl->mbReadOnly && !rKeyEvent.GetKeyCode().IsMod2() )
552 sal_uInt8 nDel = ( nCode == KEY_DELETE ) ? DEL_RIGHT : DEL_LEFT;
553 sal_uInt8 nMode = rKeyEvent.GetKeyCode().IsMod1() ? DELMODE_RESTOFWORD : DELMODE_SIMPLE;
554 if ( ( nMode == DELMODE_RESTOFWORD ) && rKeyEvent.GetKeyCode().IsShift() )
555 nMode = DELMODE_RESTOFCONTENT;
557 switch( nCode )
559 case css::awt::Key::DELETE_WORD_BACKWARD:
560 nDel = DEL_LEFT;
561 nMode = DELMODE_RESTOFWORD;
562 break;
563 case css::awt::Key::DELETE_WORD_FORWARD:
564 nDel = DEL_RIGHT;
565 nMode = DELMODE_RESTOFWORD;
566 break;
567 case css::awt::Key::DELETE_TO_BEGIN_OF_LINE:
568 nDel = DEL_LEFT;
569 nMode = DELMODE_RESTOFCONTENT;
570 break;
571 case css::awt::Key::DELETE_TO_END_OF_LINE:
572 nDel = DEL_RIGHT;
573 nMode = DELMODE_RESTOFCONTENT;
574 break;
575 default: break;
578 mpImpl->mpTextEngine->UndoActionStart();
579 aCurSel = ImpDelete( nDel, nMode );
580 mpImpl->mpTextEngine->UndoActionEnd();
581 bModified = true;
582 bAllowIdle = false;
584 else
585 bDone = false;
587 break;
588 case KEY_TAB:
590 if ( !mpImpl->mbReadOnly && !rKeyEvent.GetKeyCode().IsShift() &&
591 !rKeyEvent.GetKeyCode().IsMod1() && !rKeyEvent.GetKeyCode().IsMod2() &&
592 ImplCheckTextLen( OUString('x') ) )
594 aCurSel = mpImpl->mpTextEngine->ImpInsertText( aCurSel, '\t', !IsInsertMode() );
595 bModified = true;
597 else
598 bDone = false;
600 break;
601 case KEY_RETURN:
603 // do not swallow Shift-RETURN, as this would disable multi-line entries
604 // in dialogs & property editors
605 if ( !mpImpl->mbReadOnly && !rKeyEvent.GetKeyCode().IsMod1() &&
606 !rKeyEvent.GetKeyCode().IsMod2() && ImplCheckTextLen( OUString('x') ) )
608 mpImpl->mpTextEngine->UndoActionStart();
609 aCurSel = mpImpl->mpTextEngine->ImpInsertParaBreak( aCurSel );
610 if ( mpImpl->mbAutoIndent )
612 TextNode* pPrev = mpImpl->mpTextEngine->mpDoc->GetNodes()[ aCurSel.GetEnd().GetPara() - 1 ].get();
613 sal_Int32 n = 0;
614 while ( ( n < pPrev->GetText().getLength() ) && (
615 ( pPrev->GetText()[ n ] == ' ' ) ||
616 ( pPrev->GetText()[ n ] == '\t' ) ) )
618 n++;
620 if ( n )
621 aCurSel = mpImpl->mpTextEngine->ImpInsertText( aCurSel, pPrev->GetText().copy( 0, n ) );
623 mpImpl->mpTextEngine->UndoActionEnd();
624 bModified = true;
626 else
627 bDone = false;
629 break;
630 case KEY_INSERT:
632 if ( !mpImpl->mbReadOnly )
633 SetInsertMode( !IsInsertMode() );
635 break;
636 default:
638 if ( TextEngine::IsSimpleCharInput( rKeyEvent ) )
640 sal_Unicode nCharCode = rKeyEvent.GetCharCode();
641 if ( !mpImpl->mbReadOnly && ImplCheckTextLen( OUString(nCharCode) ) ) // otherwise swallow the character anyway
643 aCurSel = mpImpl->mpTextEngine->ImpInsertText( nCharCode, aCurSel, !IsInsertMode(), true );
644 bModified = true;
647 else
648 bDone = false;
653 if ( aCurSel != aOldSel ) // Check if changed, maybe other method already changed mpImpl->maSelection, don't overwrite that!
654 ImpSetSelection( aCurSel );
656 if ( ( nCode != KEY_UP ) && ( nCode != KEY_DOWN ) )
657 mpImpl->mnTravelXPos = TRAVEL_X_DONTKNOW;
659 if ( bModified )
661 // Idle-Formatter only if AnyInput
662 if ( bAllowIdle && Application::AnyInput( VclInputFlags::KEYBOARD) )
663 mpImpl->mpTextEngine->IdleFormatAndUpdate( this );
664 else
665 mpImpl->mpTextEngine->FormatAndUpdate( this);
667 else if ( bMoved )
669 // selection is painted now in ImpMoveCursor
670 ImpShowCursor( mpImpl->mbAutoScroll, true, bEndKey );
673 if ( mpImpl->mpTextEngine->IsModified() )
674 mpImpl->mpTextEngine->Broadcast( TextHint( SfxHintId::TextModified ) );
675 else if ( bWasModified )
676 mpImpl->mpTextEngine->SetModified( true );
678 return bDone;
681 void TextView::MouseButtonUp( const MouseEvent& rMouseEvent )
683 mpImpl->mbClickedInSelection = false;
684 mpImpl->mnTravelXPos = TRAVEL_X_DONTKNOW;
685 mpImpl->mpSelEngine->SelMouseButtonUp( rMouseEvent );
686 if ( rMouseEvent.IsMiddle() && !IsReadOnly() &&
687 ( GetWindow()->GetSettings().GetMouseSettings().GetMiddleButtonAction() == MouseMiddleButtonAction::PasteSelection ) )
689 css::uno::Reference<css::datatransfer::clipboard::XClipboard> aSelection(GetWindow()->GetPrimarySelection());
690 Paste( aSelection );
691 if ( mpImpl->mpTextEngine->IsModified() )
692 mpImpl->mpTextEngine->Broadcast( TextHint( SfxHintId::TextModified ) );
694 else if ( rMouseEvent.IsLeft() && GetSelection().HasRange() )
696 css::uno::Reference<css::datatransfer::clipboard::XClipboard> aSelection(GetWindow()->GetPrimarySelection());
697 Copy( aSelection );
701 void TextView::MouseButtonDown( const MouseEvent& rMouseEvent )
703 mpImpl->mpTextEngine->CheckIdleFormatter(); // for fast typing and MouseButtonDown
704 mpImpl->mnTravelXPos = TRAVEL_X_DONTKNOW;
705 mpImpl->mbClickedInSelection = IsSelectionAtPoint( rMouseEvent.GetPosPixel() );
707 mpImpl->mpTextEngine->SetActiveView( this );
709 mpImpl->mpSelEngine->SelMouseButtonDown( rMouseEvent );
711 // mbu 20.01.2005 - SelMouseButtonDown() possibly triggers a 'selection changed'
712 // notification. The appropriate handler could change the current selection,
713 // which is the case in the MailMerge address block control. To enable select'n'drag
714 // we need to reevaluate the selection after the notification has been fired.
715 mpImpl->mbClickedInSelection = IsSelectionAtPoint( rMouseEvent.GetPosPixel() );
717 // special cases
718 if ( rMouseEvent.IsShift() || ( rMouseEvent.GetClicks() < 2 ))
719 return;
721 if ( rMouseEvent.IsMod2() )
723 HideSelection();
724 ImpSetSelection( mpImpl->maSelection.GetEnd() );
725 SetCursorAtPoint( rMouseEvent.GetPosPixel() ); // not set by SelectionEngine for MOD2
728 if ( rMouseEvent.GetClicks() == 2 )
730 // select word
731 if ( mpImpl->maSelection.GetEnd().GetIndex() < mpImpl->mpTextEngine->GetTextLen( mpImpl->maSelection.GetEnd().GetPara() ) )
733 HideSelection();
734 // tdf#57879 - expand selection to include connector punctuations
735 TextSelection aNewSel;
736 mpImpl->mpTextEngine->GetWord( mpImpl->maSelection.GetEnd(), &aNewSel.GetStart(), &aNewSel.GetEnd() );
737 ImpSetSelection( aNewSel );
738 ShowSelection();
739 ShowCursor();
742 else if ( rMouseEvent.GetClicks() == 3 )
744 // select paragraph
745 if ( mpImpl->maSelection.GetStart().GetIndex() || ( mpImpl->maSelection.GetEnd().GetIndex() < mpImpl->mpTextEngine->GetTextLen( mpImpl->maSelection.GetEnd().GetPara() ) ) )
747 HideSelection();
748 TextSelection aNewSel( mpImpl->maSelection );
749 aNewSel.GetStart().GetIndex() = 0;
750 aNewSel.GetEnd().GetIndex() = mpImpl->mpTextEngine->mpDoc->GetNodes()[ mpImpl->maSelection.GetEnd().GetPara() ]->GetText().getLength();
751 ImpSetSelection( aNewSel );
752 ShowSelection();
753 ShowCursor();
758 void TextView::MouseMove( const MouseEvent& rMouseEvent )
760 mpImpl->mnTravelXPos = TRAVEL_X_DONTKNOW;
761 mpImpl->mpSelEngine->SelMouseMove( rMouseEvent );
764 void TextView::Command( const CommandEvent& rCEvt )
766 mpImpl->mpTextEngine->CheckIdleFormatter(); // for fast typing and MouseButtonDown
767 mpImpl->mpTextEngine->SetActiveView( this );
769 if ( rCEvt.GetCommand() == CommandEventId::StartExtTextInput )
771 DeleteSelected();
772 TextNode* pNode = mpImpl->mpTextEngine->mpDoc->GetNodes()[ GetSelection().GetEnd().GetPara() ].get();
773 mpImpl->mpTextEngine->mpIMEInfos = std::make_unique<TEIMEInfos>( GetSelection().GetEnd(), pNode->GetText().copy( GetSelection().GetEnd().GetIndex() ) );
774 mpImpl->mpTextEngine->mpIMEInfos->bWasCursorOverwrite = !IsInsertMode();
776 else if ( rCEvt.GetCommand() == CommandEventId::EndExtTextInput )
778 SAL_WARN_IF( !mpImpl->mpTextEngine->mpIMEInfos, "vcl", "CommandEventId::EndExtTextInput => No Start ?" );
779 if( mpImpl->mpTextEngine->mpIMEInfos )
781 TEParaPortion* pPortion = mpImpl->mpTextEngine->mpTEParaPortions->GetObject( mpImpl->mpTextEngine->mpIMEInfos->aPos.GetPara() );
782 pPortion->MarkSelectionInvalid( mpImpl->mpTextEngine->mpIMEInfos->aPos.GetIndex() );
784 bool bInsertMode = !mpImpl->mpTextEngine->mpIMEInfos->bWasCursorOverwrite;
786 mpImpl->mpTextEngine->mpIMEInfos.reset();
788 mpImpl->mpTextEngine->TextModified();
789 mpImpl->mpTextEngine->FormatAndUpdate( this );
791 SetInsertMode( bInsertMode );
793 if ( mpImpl->mpTextEngine->IsModified() )
794 mpImpl->mpTextEngine->Broadcast( TextHint( SfxHintId::TextModified ) );
797 else if ( rCEvt.GetCommand() == CommandEventId::ExtTextInput )
799 SAL_WARN_IF( !mpImpl->mpTextEngine->mpIMEInfos, "vcl", "CommandEventId::ExtTextInput => No Start ?" );
800 if( mpImpl->mpTextEngine->mpIMEInfos )
802 const CommandExtTextInputData* pData = rCEvt.GetExtTextInputData();
804 if ( !pData->IsOnlyCursorChanged() )
806 TextSelection aSelect( mpImpl->mpTextEngine->mpIMEInfos->aPos );
807 aSelect.GetEnd().GetIndex() += mpImpl->mpTextEngine->mpIMEInfos->nLen;
808 aSelect = mpImpl->mpTextEngine->ImpDeleteText( aSelect );
809 aSelect = mpImpl->mpTextEngine->ImpInsertText( aSelect, pData->GetText() );
811 if ( mpImpl->mpTextEngine->mpIMEInfos->bWasCursorOverwrite )
813 const sal_Int32 nOldIMETextLen = mpImpl->mpTextEngine->mpIMEInfos->nLen;
814 const sal_Int32 nNewIMETextLen = pData->GetText().getLength();
816 if ( ( nOldIMETextLen > nNewIMETextLen ) &&
817 ( nNewIMETextLen < mpImpl->mpTextEngine->mpIMEInfos->aOldTextAfterStartPos.getLength() ) )
819 // restore old characters
820 sal_Int32 nRestore = nOldIMETextLen - nNewIMETextLen;
821 TextPaM aPaM( mpImpl->mpTextEngine->mpIMEInfos->aPos );
822 aPaM.GetIndex() += nNewIMETextLen;
823 mpImpl->mpTextEngine->ImpInsertText( aPaM, mpImpl->mpTextEngine->mpIMEInfos->aOldTextAfterStartPos.copy( nNewIMETextLen, nRestore ) );
825 else if ( ( nOldIMETextLen < nNewIMETextLen ) &&
826 ( nOldIMETextLen < mpImpl->mpTextEngine->mpIMEInfos->aOldTextAfterStartPos.getLength() ) )
828 // overwrite
829 const sal_Int32 nOverwrite = std::min( nNewIMETextLen, mpImpl->mpTextEngine->mpIMEInfos->aOldTextAfterStartPos.getLength() ) - nOldIMETextLen;
830 SAL_WARN_IF( !nOverwrite || (nOverwrite >= 0xFF00), "vcl", "IME Overwrite?!" );
831 TextPaM aPaM( mpImpl->mpTextEngine->mpIMEInfos->aPos );
832 aPaM.GetIndex() += nNewIMETextLen;
833 TextSelection aSel( aPaM );
834 aSel.GetEnd().GetIndex() += nOverwrite;
835 mpImpl->mpTextEngine->ImpDeleteText( aSel );
839 if ( pData->GetTextAttr() )
841 mpImpl->mpTextEngine->mpIMEInfos->CopyAttribs( pData->GetTextAttr(), pData->GetText().getLength() );
843 else
845 mpImpl->mpTextEngine->mpIMEInfos->DestroyAttribs();
848 TEParaPortion* pPPortion = mpImpl->mpTextEngine->mpTEParaPortions->GetObject( mpImpl->mpTextEngine->mpIMEInfos->aPos.GetPara() );
849 pPPortion->MarkSelectionInvalid( mpImpl->mpTextEngine->mpIMEInfos->aPos.GetIndex() );
850 mpImpl->mpTextEngine->FormatAndUpdate( this );
853 TextSelection aNewSel = TextPaM( mpImpl->mpTextEngine->mpIMEInfos->aPos.GetPara(), mpImpl->mpTextEngine->mpIMEInfos->aPos.GetIndex()+pData->GetCursorPos() );
854 SetSelection( aNewSel );
855 SetInsertMode( !pData->IsCursorOverwrite() );
857 if ( pData->IsCursorVisible() )
858 ShowCursor();
859 else
860 HideCursor();
863 else if ( rCEvt.GetCommand() == CommandEventId::CursorPos )
865 if ( mpImpl->mpTextEngine->mpIMEInfos && mpImpl->mpTextEngine->mpIMEInfos->nLen )
867 TextPaM aPaM( GetSelection().GetEnd() );
868 tools::Rectangle aR1 = mpImpl->mpTextEngine->PaMtoEditCursor( aPaM );
870 sal_Int32 nInputEnd = mpImpl->mpTextEngine->mpIMEInfos->aPos.GetIndex() + mpImpl->mpTextEngine->mpIMEInfos->nLen;
872 if ( !mpImpl->mpTextEngine->IsFormatted() )
873 mpImpl->mpTextEngine->FormatDoc();
875 TEParaPortion* pParaPortion = mpImpl->mpTextEngine->mpTEParaPortions->GetObject( aPaM.GetPara() );
876 std::vector<TextLine>::size_type nLine = pParaPortion->GetLineNumber( aPaM.GetIndex(), true );
877 TextLine& rLine = pParaPortion->GetLines()[ nLine ];
878 if ( nInputEnd > rLine.GetEnd() )
879 nInputEnd = rLine.GetEnd();
880 tools::Rectangle aR2 = mpImpl->mpTextEngine->PaMtoEditCursor( TextPaM( aPaM.GetPara(), nInputEnd ) );
882 tools::Long nWidth = aR2.Left()-aR1.Right();
883 aR1.Move( -GetStartDocPos().X(), -GetStartDocPos().Y() );
884 GetWindow()->SetCursorRect( &aR1, nWidth );
886 else
888 GetWindow()->SetCursorRect();
891 else
893 mpImpl->mpSelEngine->Command( rCEvt );
897 void TextView::ShowCursor( bool bGotoCursor, bool bForceVisCursor )
899 // this setting has more weight
900 if ( !mpImpl->mbAutoScroll )
901 bGotoCursor = false;
902 ImpShowCursor( bGotoCursor, bForceVisCursor, false );
905 void TextView::HideCursor()
907 mpImpl->mpCursor->Hide();
910 void TextView::Scroll( tools::Long ndX, tools::Long ndY )
912 SAL_WARN_IF( !mpImpl->mpTextEngine->IsFormatted(), "vcl", "Scroll: Not formatted!" );
914 if ( !ndX && !ndY )
915 return;
917 Point aNewStartPos( mpImpl->maStartDocPos );
919 // Vertical:
920 aNewStartPos.AdjustY( -ndY );
921 if ( aNewStartPos.Y() < 0 )
922 aNewStartPos.setY( 0 );
924 // Horizontal:
925 aNewStartPos.AdjustX( -ndX );
926 if ( aNewStartPos.X() < 0 )
927 aNewStartPos.setX( 0 );
929 tools::Long nDiffX = mpImpl->maStartDocPos.X() - aNewStartPos.X();
930 tools::Long nDiffY = mpImpl->maStartDocPos.Y() - aNewStartPos.Y();
932 if ( nDiffX || nDiffY )
934 bool bVisCursor = mpImpl->mpCursor->IsVisible();
935 mpImpl->mpCursor->Hide();
936 mpImpl->mpWindow->PaintImmediately();
937 mpImpl->maStartDocPos = aNewStartPos;
939 if ( mpImpl->mpTextEngine->IsRightToLeft() )
940 nDiffX = -nDiffX;
941 mpImpl->mpWindow->Scroll( nDiffX, nDiffY );
942 mpImpl->mpWindow->PaintImmediately();
943 mpImpl->mpCursor->SetPos( mpImpl->mpCursor->GetPos() + Point( nDiffX, nDiffY ) );
944 if ( bVisCursor && !mpImpl->mbReadOnly )
945 mpImpl->mpCursor->Show();
948 mpImpl->mpTextEngine->Broadcast( TextHint( SfxHintId::TextViewScrolled ) );
951 void TextView::Undo()
953 mpImpl->mpTextEngine->SetActiveView( this );
954 mpImpl->mpTextEngine->GetUndoManager().Undo();
957 void TextView::Redo()
959 mpImpl->mpTextEngine->SetActiveView( this );
960 mpImpl->mpTextEngine->GetUndoManager().Redo();
963 void TextView::Cut()
965 mpImpl->mpTextEngine->UndoActionStart();
966 Copy();
967 DeleteSelected();
968 mpImpl->mpTextEngine->UndoActionEnd();
971 void TextView::Copy( css::uno::Reference< css::datatransfer::clipboard::XClipboard > const & rxClipboard )
973 if ( !rxClipboard.is() )
974 return;
976 TETextDataObject* pDataObj = new TETextDataObject( GetSelected() );
978 SolarMutexReleaser aReleaser;
982 rxClipboard->setContents( pDataObj, nullptr );
984 css::uno::Reference< css::datatransfer::clipboard::XFlushableClipboard > xFlushableClipboard( rxClipboard, css::uno::UNO_QUERY );
985 if( xFlushableClipboard.is() )
986 xFlushableClipboard->flushClipboard();
988 catch( const css::uno::Exception& )
993 void TextView::Copy()
995 css::uno::Reference<css::datatransfer::clipboard::XClipboard> aClipboard(GetWindow()->GetClipboard());
996 Copy( aClipboard );
999 void TextView::Paste( css::uno::Reference< css::datatransfer::clipboard::XClipboard > const & rxClipboard )
1001 if ( !rxClipboard.is() )
1002 return;
1004 css::uno::Reference< css::datatransfer::XTransferable > xDataObj;
1008 SolarMutexReleaser aReleaser;
1009 xDataObj = rxClipboard->getContents();
1011 catch( const css::uno::Exception& )
1015 if ( !xDataObj.is() )
1016 return;
1018 css::datatransfer::DataFlavor aFlavor;
1019 SotExchange::GetFormatDataFlavor( SotClipboardFormatId::STRING, aFlavor );
1020 if ( !xDataObj->isDataFlavorSupported( aFlavor ) )
1021 return;
1025 css::uno::Any aData = xDataObj->getTransferData( aFlavor );
1026 OUString aText;
1027 aData >>= aText;
1028 bool bWasTruncated = false;
1029 if( mpImpl->mpTextEngine->GetMaxTextLen() != 0 )
1030 bWasTruncated = ImplTruncateNewText( aText );
1031 InsertText( aText );
1032 mpImpl->mpTextEngine->Broadcast( TextHint( SfxHintId::TextModified ) );
1034 if( bWasTruncated )
1035 Edit::ShowTruncationWarning(mpImpl->mpWindow->GetFrameWeld());
1037 catch( const css::datatransfer::UnsupportedFlavorException& )
1042 void TextView::Paste()
1044 css::uno::Reference<css::datatransfer::clipboard::XClipboard> aClipboard(GetWindow()->GetClipboard());
1045 Paste( aClipboard );
1048 OUString TextView::GetSelected()
1050 return GetSelected( GetSystemLineEnd() );
1053 OUString TextView::GetSelected( LineEnd aSeparator )
1055 return mpImpl->mpTextEngine->GetText( mpImpl->maSelection, aSeparator );
1058 void TextView::SetInsertMode( bool bInsert )
1060 if ( mpImpl->mbInsertMode != bInsert )
1062 mpImpl->mbInsertMode = bInsert;
1063 ShowCursor( mpImpl->mbAutoScroll, false );
1067 void TextView::SetReadOnly( bool bReadOnly )
1069 if ( mpImpl->mbReadOnly != bReadOnly )
1071 mpImpl->mbReadOnly = bReadOnly;
1072 if ( !mpImpl->mbReadOnly )
1073 ShowCursor( mpImpl->mbAutoScroll, false );
1074 else
1075 HideCursor();
1077 GetWindow()->SetInputContext( InputContext( mpImpl->mpTextEngine->GetFont(), bReadOnly ? InputContextFlags::Text|InputContextFlags::ExtText : InputContextFlags::NONE ) );
1081 TextSelection const & TextView::ImpMoveCursor( const KeyEvent& rKeyEvent )
1083 // normally only needed for Up/Down; but who cares
1084 mpImpl->mpTextEngine->CheckIdleFormatter();
1086 TextPaM aPaM( mpImpl->maSelection.GetEnd() );
1087 TextPaM aOldEnd( aPaM );
1089 TextDirectionality eTextDirection = TextDirectionality::LeftToRight_TopToBottom;
1090 if ( mpImpl->mpTextEngine->IsRightToLeft() )
1091 eTextDirection = TextDirectionality::RightToLeft_TopToBottom;
1093 KeyEvent aTranslatedKeyEvent = rKeyEvent.LogicalTextDirectionality( eTextDirection );
1095 bool bCtrl = aTranslatedKeyEvent.GetKeyCode().IsMod1();
1096 sal_uInt16 nCode = aTranslatedKeyEvent.GetKeyCode().GetCode();
1098 bool bSelect = aTranslatedKeyEvent.GetKeyCode().IsShift();
1099 switch ( nCode )
1101 case KEY_UP: aPaM = CursorUp( aPaM );
1102 break;
1103 case KEY_DOWN: aPaM = CursorDown( aPaM );
1104 break;
1105 case KEY_HOME: aPaM = bCtrl ? CursorStartOfDoc() : CursorStartOfLine( aPaM );
1106 break;
1107 case KEY_END: aPaM = bCtrl ? CursorEndOfDoc() : CursorEndOfLine( aPaM );
1108 break;
1109 case KEY_PAGEUP: aPaM = bCtrl ? CursorStartOfDoc() : PageUp( aPaM );
1110 break;
1111 case KEY_PAGEDOWN: aPaM = bCtrl ? CursorEndOfDoc() : PageDown( aPaM );
1112 break;
1113 case KEY_LEFT: aPaM = bCtrl ? CursorWordLeft( aPaM ) : CursorLeft( aPaM, aTranslatedKeyEvent.GetKeyCode().IsMod2() ? sal_uInt16(css::i18n::CharacterIteratorMode::SKIPCHARACTER) : sal_uInt16(css::i18n::CharacterIteratorMode::SKIPCELL) );
1114 break;
1115 case KEY_RIGHT: aPaM = bCtrl ? CursorWordRight( aPaM ) : CursorRight( aPaM, aTranslatedKeyEvent.GetKeyCode().IsMod2() ? sal_uInt16(css::i18n::CharacterIteratorMode::SKIPCHARACTER) : sal_uInt16(css::i18n::CharacterIteratorMode::SKIPCELL) );
1116 break;
1117 case css::awt::Key::SELECT_WORD_FORWARD:
1118 bSelect = true;
1119 [[fallthrough]];
1120 case css::awt::Key::MOVE_WORD_FORWARD:
1121 aPaM = CursorWordRight( aPaM );
1122 break;
1123 case css::awt::Key::SELECT_WORD_BACKWARD:
1124 bSelect = true;
1125 [[fallthrough]];
1126 case css::awt::Key::MOVE_WORD_BACKWARD:
1127 aPaM = CursorWordLeft( aPaM );
1128 break;
1129 case css::awt::Key::SELECT_TO_BEGIN_OF_LINE:
1130 bSelect = true;
1131 [[fallthrough]];
1132 case css::awt::Key::MOVE_TO_BEGIN_OF_LINE:
1133 aPaM = CursorStartOfLine( aPaM );
1134 break;
1135 case css::awt::Key::SELECT_TO_END_OF_LINE:
1136 bSelect = true;
1137 [[fallthrough]];
1138 case css::awt::Key::MOVE_TO_END_OF_LINE:
1139 aPaM = CursorEndOfLine( aPaM );
1140 break;
1141 case css::awt::Key::SELECT_TO_BEGIN_OF_PARAGRAPH:
1142 bSelect = true;
1143 [[fallthrough]];
1144 case css::awt::Key::MOVE_TO_BEGIN_OF_PARAGRAPH:
1145 aPaM = CursorStartOfParagraph( aPaM );
1146 break;
1147 case css::awt::Key::SELECT_TO_END_OF_PARAGRAPH:
1148 bSelect = true;
1149 [[fallthrough]];
1150 case css::awt::Key::MOVE_TO_END_OF_PARAGRAPH:
1151 aPaM = CursorEndOfParagraph( aPaM );
1152 break;
1153 case css::awt::Key::SELECT_TO_BEGIN_OF_DOCUMENT:
1154 bSelect = true;
1155 [[fallthrough]];
1156 case css::awt::Key::MOVE_TO_BEGIN_OF_DOCUMENT:
1157 aPaM = CursorStartOfDoc();
1158 break;
1159 case css::awt::Key::SELECT_TO_END_OF_DOCUMENT:
1160 bSelect = true;
1161 [[fallthrough]];
1162 case css::awt::Key::MOVE_TO_END_OF_DOCUMENT:
1163 aPaM = CursorEndOfDoc();
1164 break;
1167 // might cause a CreateAnchor or Deselection all
1168 mpImpl->mpSelEngine->CursorPosChanging( bSelect, aTranslatedKeyEvent.GetKeyCode().IsMod1() );
1170 if ( aOldEnd != aPaM )
1172 mpImpl->mpTextEngine->CursorMoved( aOldEnd.GetPara() );
1174 TextSelection aNewSelection( mpImpl->maSelection );
1175 aNewSelection.GetEnd() = aPaM;
1176 if ( bSelect )
1178 // extend the selection
1179 ImpSetSelection( aNewSelection );
1180 ShowSelection( TextSelection( aOldEnd, aPaM ) );
1182 else
1184 aNewSelection.GetStart() = aPaM;
1185 ImpSetSelection( aNewSelection );
1189 return mpImpl->maSelection;
1192 void TextView::InsertText( const OUString& rStr )
1194 mpImpl->mpTextEngine->UndoActionStart();
1196 TextSelection aNewSel = mpImpl->mpTextEngine->ImpInsertText( mpImpl->maSelection, rStr );
1198 ImpSetSelection( aNewSel );
1200 mpImpl->mpTextEngine->UndoActionEnd();
1202 mpImpl->mpTextEngine->FormatAndUpdate( this );
1205 TextPaM TextView::CursorLeft( const TextPaM& rPaM, sal_uInt16 nCharacterIteratorMode )
1207 TextPaM aPaM( rPaM );
1209 if ( aPaM.GetIndex() )
1211 TextNode* pNode = mpImpl->mpTextEngine->mpDoc->GetNodes()[ aPaM.GetPara() ].get();
1212 css::uno::Reference < css::i18n::XBreakIterator > xBI = mpImpl->mpTextEngine->GetBreakIterator();
1213 sal_Int32 nCount = 1;
1214 aPaM.GetIndex() = xBI->previousCharacters( pNode->GetText(), aPaM.GetIndex(), mpImpl->mpTextEngine->GetLocale(), nCharacterIteratorMode, nCount, nCount );
1216 else if ( aPaM.GetPara() )
1218 aPaM.GetPara()--;
1219 TextNode* pNode = mpImpl->mpTextEngine->mpDoc->GetNodes()[ aPaM.GetPara() ].get();
1220 aPaM.GetIndex() = pNode->GetText().getLength();
1222 return aPaM;
1225 TextPaM TextView::CursorRight( const TextPaM& rPaM, sal_uInt16 nCharacterIteratorMode )
1227 TextPaM aPaM( rPaM );
1229 TextNode* pNode = mpImpl->mpTextEngine->mpDoc->GetNodes()[ aPaM.GetPara() ].get();
1230 if ( aPaM.GetIndex() < pNode->GetText().getLength() )
1232 css::uno::Reference < css::i18n::XBreakIterator > xBI = mpImpl->mpTextEngine->GetBreakIterator();
1233 sal_Int32 nCount = 1;
1234 aPaM.GetIndex() = xBI->nextCharacters( pNode->GetText(), aPaM.GetIndex(), mpImpl->mpTextEngine->GetLocale(), nCharacterIteratorMode, nCount, nCount );
1236 else if ( aPaM.GetPara() < ( mpImpl->mpTextEngine->mpDoc->GetNodes().size()-1) )
1238 aPaM.GetPara()++;
1239 aPaM.GetIndex() = 0;
1242 return aPaM;
1245 TextPaM TextView::CursorWordLeft( const TextPaM& rPaM )
1247 TextPaM aPaM( rPaM );
1249 if ( aPaM.GetIndex() )
1251 // tdf#57879 - expand selection to the left to include connector punctuations
1252 mpImpl->mpTextEngine->GetWord( rPaM, &aPaM );
1253 if ( aPaM.GetIndex() >= rPaM.GetIndex() )
1255 TextNode* pNode = mpImpl->mpTextEngine->mpDoc->GetNodes()[ aPaM.GetPara() ].get();
1256 css::uno::Reference < css::i18n::XBreakIterator > xBI = mpImpl->mpTextEngine->GetBreakIterator();
1257 aPaM.GetIndex() = xBI->previousWord( pNode->GetText(), rPaM.GetIndex(), mpImpl->mpTextEngine->GetLocale(), css::i18n::WordType::ANYWORD_IGNOREWHITESPACES ).startPos;
1258 if ( aPaM.GetIndex() > 0 )
1259 mpImpl->mpTextEngine->GetWord( aPaM, &aPaM );
1260 else
1261 aPaM.GetIndex() = 0;
1264 else if ( aPaM.GetPara() )
1266 aPaM.GetPara()--;
1267 TextNode* pNode = mpImpl->mpTextEngine->mpDoc->GetNodes()[ aPaM.GetPara() ].get();
1268 aPaM.GetIndex() = pNode->GetText().getLength();
1270 return aPaM;
1273 TextPaM TextView::CursorWordRight( const TextPaM& rPaM )
1275 TextPaM aPaM( rPaM );
1277 TextNode* pNode = mpImpl->mpTextEngine->mpDoc->GetNodes()[ aPaM.GetPara() ].get();
1278 if ( aPaM.GetIndex() < pNode->GetText().getLength() )
1280 css::uno::Reference < css::i18n::XBreakIterator > xBI = mpImpl->mpTextEngine->GetBreakIterator();
1281 aPaM.GetIndex() = xBI->nextWord( pNode->GetText(), aPaM.GetIndex(), mpImpl->mpTextEngine->GetLocale(), css::i18n::WordType::ANYWORD_IGNOREWHITESPACES ).endPos;
1282 mpImpl->mpTextEngine->GetWord( aPaM, nullptr, &aPaM );
1284 else if ( aPaM.GetPara() < ( mpImpl->mpTextEngine->mpDoc->GetNodes().size()-1) )
1286 aPaM.GetPara()++;
1287 aPaM.GetIndex() = 0;
1290 return aPaM;
1293 TextPaM TextView::ImpDelete( sal_uInt8 nMode, sal_uInt8 nDelMode )
1295 if ( mpImpl->maSelection.HasRange() ) // only delete selection
1296 return mpImpl->mpTextEngine->ImpDeleteText( mpImpl->maSelection );
1298 TextPaM aStartPaM = mpImpl->maSelection.GetStart();
1299 TextPaM aEndPaM = aStartPaM;
1300 if ( nMode == DEL_LEFT )
1302 if ( nDelMode == DELMODE_SIMPLE )
1304 aEndPaM = CursorLeft( aEndPaM, sal_uInt16(css::i18n::CharacterIteratorMode::SKIPCHARACTER) );
1306 else if ( nDelMode == DELMODE_RESTOFWORD )
1308 TextNode* pNode = mpImpl->mpTextEngine->mpDoc->GetNodes()[ aEndPaM.GetPara() ].get();
1309 css::uno::Reference < css::i18n::XBreakIterator > xBI = mpImpl->mpTextEngine->GetBreakIterator();
1310 css::i18n::Boundary aBoundary = xBI->getWordBoundary( pNode->GetText(), mpImpl->maSelection.GetEnd().GetIndex(), mpImpl->mpTextEngine->GetLocale(), css::i18n::WordType::ANYWORD_IGNOREWHITESPACES, true );
1311 if ( aBoundary.startPos == mpImpl->maSelection.GetEnd().GetIndex() )
1312 aBoundary = xBI->previousWord( pNode->GetText(), mpImpl->maSelection.GetEnd().GetIndex(), mpImpl->mpTextEngine->GetLocale(), css::i18n::WordType::ANYWORD_IGNOREWHITESPACES );
1313 // #i63506# startPos is -1 when the paragraph starts with a tab
1314 aEndPaM.GetIndex() = std::max<sal_Int32>(aBoundary.startPos, 0);
1316 else // DELMODE_RESTOFCONTENT
1318 if ( aEndPaM.GetIndex() != 0 )
1319 aEndPaM.GetIndex() = 0;
1320 else if ( aEndPaM.GetPara() )
1322 // previous paragraph
1323 aEndPaM.GetPara()--;
1324 aEndPaM.GetIndex() = 0;
1328 else
1330 if ( nDelMode == DELMODE_SIMPLE )
1332 aEndPaM = CursorRight( aEndPaM, sal_uInt16(css::i18n::CharacterIteratorMode::SKIPCELL) );
1334 else if ( nDelMode == DELMODE_RESTOFWORD )
1336 TextNode* pNode = mpImpl->mpTextEngine->mpDoc->GetNodes()[ aEndPaM.GetPara() ].get();
1337 css::uno::Reference < css::i18n::XBreakIterator > xBI = mpImpl->mpTextEngine->GetBreakIterator();
1338 css::i18n::Boundary aBoundary = xBI->nextWord( pNode->GetText(), mpImpl->maSelection.GetEnd().GetIndex(), mpImpl->mpTextEngine->GetLocale(), css::i18n::WordType::ANYWORD_IGNOREWHITESPACES );
1339 aEndPaM.GetIndex() = aBoundary.startPos;
1341 else // DELMODE_RESTOFCONTENT
1343 TextNode* pNode = mpImpl->mpTextEngine->mpDoc->GetNodes()[ aEndPaM.GetPara() ].get();
1344 if ( aEndPaM.GetIndex() < pNode->GetText().getLength() )
1345 aEndPaM.GetIndex() = pNode->GetText().getLength();
1346 else if ( aEndPaM.GetPara() < ( mpImpl->mpTextEngine->mpDoc->GetNodes().size() - 1 ) )
1348 // next paragraph
1349 aEndPaM.GetPara()++;
1350 TextNode* pNextNode = mpImpl->mpTextEngine->mpDoc->GetNodes()[ aEndPaM.GetPara() ].get();
1351 aEndPaM.GetIndex() = pNextNode->GetText().getLength();
1356 return mpImpl->mpTextEngine->ImpDeleteText( TextSelection( aStartPaM, aEndPaM ) );
1359 TextPaM TextView::CursorUp( const TextPaM& rPaM )
1361 TextPaM aPaM( rPaM );
1363 tools::Long nX;
1364 if ( mpImpl->mnTravelXPos == TRAVEL_X_DONTKNOW )
1366 nX = mpImpl->mpTextEngine->GetEditCursor( rPaM, false ).Left();
1367 mpImpl->mnTravelXPos = static_cast<sal_uInt16>(nX)+1;
1369 else
1370 nX = mpImpl->mnTravelXPos;
1372 TEParaPortion* pPPortion = mpImpl->mpTextEngine->mpTEParaPortions->GetObject( rPaM.GetPara() );
1373 std::vector<TextLine>::size_type nLine = pPPortion->GetLineNumber( rPaM.GetIndex(), false );
1374 if ( nLine ) // same paragraph
1376 aPaM.GetIndex() = mpImpl->mpTextEngine->GetCharPos( rPaM.GetPara(), nLine-1, nX );
1377 // If we need to go to the end of a line that was wrapped automatically,
1378 // the cursor ends up at the beginning of the 2nd line
1379 // Problem: Last character of an automatically wrapped line = Cursor
1380 TextLine& rLine = pPPortion->GetLines()[ nLine - 1 ];
1381 if ( aPaM.GetIndex() && ( aPaM.GetIndex() == rLine.GetEnd() ) )
1382 --aPaM.GetIndex();
1384 else if ( rPaM.GetPara() ) // previous paragraph
1386 aPaM.GetPara()--;
1387 pPPortion = mpImpl->mpTextEngine->mpTEParaPortions->GetObject( aPaM.GetPara() );
1388 std::vector<TextLine>::size_type nL = pPPortion->GetLines().size() - 1;
1389 aPaM.GetIndex() = mpImpl->mpTextEngine->GetCharPos( aPaM.GetPara(), nL, nX+1 );
1392 return aPaM;
1395 TextPaM TextView::CursorDown( const TextPaM& rPaM )
1397 TextPaM aPaM( rPaM );
1399 tools::Long nX;
1400 if ( mpImpl->mnTravelXPos == TRAVEL_X_DONTKNOW )
1402 nX = mpImpl->mpTextEngine->GetEditCursor( rPaM, false ).Left();
1403 mpImpl->mnTravelXPos = static_cast<sal_uInt16>(nX)+1;
1405 else
1406 nX = mpImpl->mnTravelXPos;
1408 TEParaPortion* pPPortion = mpImpl->mpTextEngine->mpTEParaPortions->GetObject( rPaM.GetPara() );
1409 std::vector<TextLine>::size_type nLine = pPPortion->GetLineNumber( rPaM.GetIndex(), false );
1410 if ( nLine < ( pPPortion->GetLines().size() - 1 ) )
1412 aPaM.GetIndex() = mpImpl->mpTextEngine->GetCharPos( rPaM.GetPara(), nLine+1, nX );
1414 // special case CursorUp
1415 TextLine& rLine = pPPortion->GetLines()[ nLine + 1 ];
1416 if ( ( aPaM.GetIndex() == rLine.GetEnd() ) && ( aPaM.GetIndex() > rLine.GetStart() ) && aPaM.GetIndex() < pPPortion->GetNode()->GetText().getLength() )
1417 --aPaM.GetIndex();
1419 else if ( rPaM.GetPara() < ( mpImpl->mpTextEngine->mpDoc->GetNodes().size() - 1 ) ) // next paragraph
1421 aPaM.GetPara()++;
1422 pPPortion = mpImpl->mpTextEngine->mpTEParaPortions->GetObject( aPaM.GetPara() );
1423 aPaM.GetIndex() = mpImpl->mpTextEngine->GetCharPos( aPaM.GetPara(), 0, nX+1 );
1424 TextLine& rLine = pPPortion->GetLines().front();
1425 if ( ( aPaM.GetIndex() == rLine.GetEnd() ) && ( aPaM.GetIndex() > rLine.GetStart() ) && ( pPPortion->GetLines().size() > 1 ) )
1426 --aPaM.GetIndex();
1429 return aPaM;
1432 TextPaM TextView::CursorStartOfLine( const TextPaM& rPaM )
1434 TextPaM aPaM( rPaM );
1436 TEParaPortion* pPPortion = mpImpl->mpTextEngine->mpTEParaPortions->GetObject( rPaM.GetPara() );
1437 std::vector<TextLine>::size_type nLine = pPPortion->GetLineNumber( aPaM.GetIndex(), false );
1438 TextLine& rLine = pPPortion->GetLines()[ nLine ];
1439 aPaM.GetIndex() = rLine.GetStart();
1441 return aPaM;
1444 TextPaM TextView::CursorEndOfLine( const TextPaM& rPaM )
1446 TextPaM aPaM( rPaM );
1448 TEParaPortion* pPPortion = mpImpl->mpTextEngine->mpTEParaPortions->GetObject( rPaM.GetPara() );
1449 std::vector<TextLine>::size_type nLine = pPPortion->GetLineNumber( aPaM.GetIndex(), false );
1450 TextLine& rLine = pPPortion->GetLines()[ nLine ];
1451 aPaM.GetIndex() = rLine.GetEnd();
1453 if ( rLine.GetEnd() > rLine.GetStart() ) // empty line
1455 sal_Unicode cLastChar = pPPortion->GetNode()->GetText()[ aPaM.GetIndex()-1 ];
1456 if ( ( cLastChar == ' ' ) && ( aPaM.GetIndex() != pPPortion->GetNode()->GetText().getLength() ) )
1458 // for a blank in an automatically-wrapped line it is better to stand before it,
1459 // as the user will intend to stand behind the prior word.
1460 // If there is a change, special case for Pos1 after End!
1461 --aPaM.GetIndex();
1464 return aPaM;
1467 TextPaM TextView::CursorStartOfParagraph( const TextPaM& rPaM )
1469 TextPaM aPaM( rPaM );
1470 aPaM.GetIndex() = 0;
1471 return aPaM;
1474 TextPaM TextView::CursorEndOfParagraph( const TextPaM& rPaM )
1476 TextNode* pNode = mpImpl->mpTextEngine->mpDoc->GetNodes()[ rPaM.GetPara() ].get();
1477 TextPaM aPaM( rPaM );
1478 aPaM.GetIndex() = pNode->GetText().getLength();
1479 return aPaM;
1482 TextPaM TextView::CursorStartOfDoc()
1484 TextPaM aPaM( 0, 0 );
1485 return aPaM;
1488 TextPaM TextView::CursorEndOfDoc()
1490 const sal_uInt32 nNode = static_cast<sal_uInt32>(mpImpl->mpTextEngine->mpDoc->GetNodes().size() - 1);
1491 TextNode* pNode = mpImpl->mpTextEngine->mpDoc->GetNodes()[ nNode ].get();
1492 TextPaM aPaM( nNode, pNode->GetText().getLength() );
1493 return aPaM;
1496 TextPaM TextView::PageUp( const TextPaM& rPaM )
1498 tools::Rectangle aRect = mpImpl->mpTextEngine->PaMtoEditCursor( rPaM );
1499 Point aTopLeft = aRect.TopLeft();
1500 aTopLeft.AdjustY( -(mpImpl->mpWindow->GetOutputSizePixel().Height() * 9/10) );
1501 aTopLeft.AdjustX(1 );
1502 if ( aTopLeft.Y() < 0 )
1503 aTopLeft.setY( 0 );
1505 TextPaM aPaM = mpImpl->mpTextEngine->GetPaM( aTopLeft );
1506 return aPaM;
1509 TextPaM TextView::PageDown( const TextPaM& rPaM )
1511 tools::Rectangle aRect = mpImpl->mpTextEngine->PaMtoEditCursor( rPaM );
1512 Point aBottomRight = aRect.BottomRight();
1513 aBottomRight.AdjustY(mpImpl->mpWindow->GetOutputSizePixel().Height() * 9/10 );
1514 aBottomRight.AdjustX(1 );
1515 tools::Long nHeight = mpImpl->mpTextEngine->GetTextHeight();
1516 if ( aBottomRight.Y() > nHeight )
1517 aBottomRight.setY( nHeight-1 );
1519 TextPaM aPaM = mpImpl->mpTextEngine->GetPaM( aBottomRight );
1520 return aPaM;
1523 void TextView::ImpShowCursor( bool bGotoCursor, bool bForceVisCursor, bool bSpecial )
1525 if ( mpImpl->mpTextEngine->IsFormatting() )
1526 return;
1527 if ( !mpImpl->mpTextEngine->GetUpdateMode() )
1528 return;
1529 if ( mpImpl->mpTextEngine->IsInUndo() )
1530 return;
1532 mpImpl->mpTextEngine->CheckIdleFormatter();
1533 if ( !mpImpl->mpTextEngine->IsFormatted() )
1534 mpImpl->mpTextEngine->FormatAndUpdate( this );
1536 TextPaM aPaM( mpImpl->maSelection.GetEnd() );
1537 tools::Rectangle aEditCursor = mpImpl->mpTextEngine->PaMtoEditCursor( aPaM, bSpecial );
1539 // Remember that we placed the cursor behind the last character of a line
1540 mpImpl->mbCursorAtEndOfLine = false;
1541 if( bSpecial )
1543 TEParaPortion* pParaPortion = mpImpl->mpTextEngine->mpTEParaPortions->GetObject( aPaM.GetPara() );
1544 mpImpl->mbCursorAtEndOfLine =
1545 pParaPortion->GetLineNumber( aPaM.GetIndex(), true ) != pParaPortion->GetLineNumber( aPaM.GetIndex(), false );
1548 if ( !IsInsertMode() && !mpImpl->maSelection.HasRange() )
1550 TextNode* pNode = mpImpl->mpTextEngine->mpDoc->GetNodes()[ aPaM.GetPara() ].get();
1551 if ( !pNode->GetText().isEmpty() && ( aPaM.GetIndex() < pNode->GetText().getLength() ) )
1553 // If we are behind a portion, and the next portion has other direction, we must change position...
1554 aEditCursor.SetLeft( mpImpl->mpTextEngine->GetEditCursor( aPaM, false, true ).Left() );
1555 aEditCursor.SetRight( aEditCursor.Left() );
1557 TEParaPortion* pParaPortion = mpImpl->mpTextEngine->mpTEParaPortions->GetObject( aPaM.GetPara() );
1559 sal_Int32 nTextPortionStart = 0;
1560 std::size_t nTextPortion = pParaPortion->GetTextPortions().FindPortion( aPaM.GetIndex(), nTextPortionStart, true );
1561 TETextPortion* pTextPortion = pParaPortion->GetTextPortions()[ nTextPortion ];
1562 if ( pTextPortion->GetKind() == PORTIONKIND_TAB )
1564 aEditCursor.AdjustRight(pTextPortion->GetWidth() );
1566 else
1568 TextPaM aNext = CursorRight( TextPaM( aPaM.GetPara(), aPaM.GetIndex() ), sal_uInt16(css::i18n::CharacterIteratorMode::SKIPCELL) );
1569 aEditCursor.SetRight( mpImpl->mpTextEngine->GetEditCursor( aNext, true ).Left() );
1574 Size aOutSz = mpImpl->mpWindow->GetOutputSizePixel();
1575 if ( aEditCursor.GetHeight() > aOutSz.Height() )
1576 aEditCursor.SetBottom( aEditCursor.Top() + aOutSz.Height() - 1 );
1578 aEditCursor.AdjustLeft( -1 );
1580 if ( bGotoCursor
1581 // #i81283# protect maStartDocPos against initialization problems
1582 && aOutSz.Width() && aOutSz.Height()
1585 tools::Long nVisStartY = mpImpl->maStartDocPos.Y();
1586 tools::Long nVisEndY = mpImpl->maStartDocPos.Y() + aOutSz.Height();
1587 tools::Long nVisStartX = mpImpl->maStartDocPos.X();
1588 tools::Long nVisEndX = mpImpl->maStartDocPos.X() + aOutSz.Width();
1589 tools::Long nMoreX = aOutSz.Width() / 4;
1591 Point aNewStartPos( mpImpl->maStartDocPos );
1593 if ( aEditCursor.Bottom() > nVisEndY )
1595 aNewStartPos.AdjustY( aEditCursor.Bottom() - nVisEndY);
1597 else if ( aEditCursor.Top() < nVisStartY )
1599 aNewStartPos.AdjustY( -( nVisStartY - aEditCursor.Top() ) );
1602 if ( aEditCursor.Right() >= nVisEndX )
1604 aNewStartPos.AdjustX( aEditCursor.Right() - nVisEndX );
1606 // do you want some more?
1607 aNewStartPos.AdjustX(nMoreX );
1609 else if ( aEditCursor.Left() <= nVisStartX )
1611 aNewStartPos.AdjustX( -( nVisStartX - aEditCursor.Left() ) );
1613 // do you want some more?
1614 aNewStartPos.AdjustX( -nMoreX );
1617 // X can be wrong for the 'some more' above:
1618 // sal_uLong nMaxTextWidth = mpImpl->mpTextEngine->GetMaxTextWidth();
1619 // if ( !nMaxTextWidth || ( nMaxTextWidth > 0x7FFFFFFF ) )
1620 // nMaxTextWidth = 0x7FFFFFFF;
1621 // long nMaxX = (long)nMaxTextWidth - aOutSz.Width();
1622 tools::Long nMaxX = mpImpl->mpTextEngine->CalcTextWidth() - aOutSz.Width();
1623 if ( nMaxX < 0 )
1624 nMaxX = 0;
1626 if ( aNewStartPos.X() < 0 )
1627 aNewStartPos.setX( 0 );
1628 else if ( aNewStartPos.X() > nMaxX )
1629 aNewStartPos.setX( nMaxX );
1631 // Y should not be further down than needed
1632 tools::Long nYMax = mpImpl->mpTextEngine->GetTextHeight() - aOutSz.Height();
1633 if ( nYMax < 0 )
1634 nYMax = 0;
1635 if ( aNewStartPos.Y() > nYMax )
1636 aNewStartPos.setY( nYMax );
1638 if ( aNewStartPos != mpImpl->maStartDocPos )
1639 Scroll( -(aNewStartPos.X() - mpImpl->maStartDocPos.X()), -(aNewStartPos.Y() - mpImpl->maStartDocPos.Y()) );
1642 if ( aEditCursor.Right() < aEditCursor.Left() )
1644 tools::Long n = aEditCursor.Left();
1645 aEditCursor.SetLeft( aEditCursor.Right() );
1646 aEditCursor.SetRight( n );
1649 Point aPoint( GetWindowPos( !mpImpl->mpTextEngine->IsRightToLeft() ? aEditCursor.TopLeft() : aEditCursor.TopRight() ) );
1650 mpImpl->mpCursor->SetPos( aPoint );
1651 mpImpl->mpCursor->SetSize( aEditCursor.GetSize() );
1652 if ( bForceVisCursor && mpImpl->mbCursorEnabled )
1653 mpImpl->mpCursor->Show();
1656 void TextView::SetCursorAtPoint( const Point& rPosPixel )
1658 mpImpl->mpTextEngine->CheckIdleFormatter();
1660 Point aDocPos = GetDocPos( rPosPixel );
1662 TextPaM aPaM = mpImpl->mpTextEngine->GetPaM( aDocPos );
1664 // aTmpNewSel: Diff between old and new; not the new selection
1665 TextSelection aTmpNewSel( mpImpl->maSelection.GetEnd(), aPaM );
1666 TextSelection aNewSel( mpImpl->maSelection );
1667 aNewSel.GetEnd() = aPaM;
1669 if ( !mpImpl->mpSelEngine->HasAnchor() )
1671 if ( mpImpl->maSelection.GetStart() != aPaM )
1672 mpImpl->mpTextEngine->CursorMoved( mpImpl->maSelection.GetStart().GetPara() );
1673 aNewSel.GetStart() = aPaM;
1674 ImpSetSelection( aNewSel );
1676 else
1678 ImpSetSelection( aNewSel );
1679 ShowSelection( aTmpNewSel );
1682 bool bForceCursor = !mpImpl->mpDDInfo; // && !mbInSelection
1683 ImpShowCursor( mpImpl->mbAutoScroll, bForceCursor, false );
1686 bool TextView::IsSelectionAtPoint( const Point& rPosPixel )
1688 Point aDocPos = GetDocPos( rPosPixel );
1689 TextPaM aPaM = mpImpl->mpTextEngine->GetPaM( aDocPos );
1690 // BeginDrag is only called, however, if IsSelectionAtPoint()
1691 // Problem: IsSelectionAtPoint is not called by Command()
1692 // if before MBDown returned false.
1693 return IsInSelection( aPaM );
1696 bool TextView::IsInSelection( const TextPaM& rPaM )
1698 TextSelection aSel = mpImpl->maSelection;
1699 aSel.Justify();
1701 const sal_uInt32 nStartNode = aSel.GetStart().GetPara();
1702 const sal_uInt32 nEndNode = aSel.GetEnd().GetPara();
1703 const sal_uInt32 nCurNode = rPaM.GetPara();
1705 if ( ( nCurNode > nStartNode ) && ( nCurNode < nEndNode ) )
1706 return true;
1708 if ( nStartNode == nEndNode )
1710 if ( nCurNode == nStartNode )
1711 if ( ( rPaM.GetIndex() >= aSel.GetStart().GetIndex() ) && ( rPaM.GetIndex() < aSel.GetEnd().GetIndex() ) )
1712 return true;
1714 else if ( ( nCurNode == nStartNode ) && ( rPaM.GetIndex() >= aSel.GetStart().GetIndex() ) )
1715 return true;
1716 else if ( ( nCurNode == nEndNode ) && ( rPaM.GetIndex() < aSel.GetEnd().GetIndex() ) )
1717 return true;
1719 return false;
1722 void TextView::ImpHideDDCursor()
1724 if ( mpImpl->mpDDInfo && mpImpl->mpDDInfo->mbVisCursor )
1726 mpImpl->mpDDInfo->maCursor.Hide();
1727 mpImpl->mpDDInfo->mbVisCursor = false;
1731 void TextView::ImpShowDDCursor()
1733 if ( !mpImpl->mpDDInfo->mbVisCursor )
1735 tools::Rectangle aCursor = mpImpl->mpTextEngine->PaMtoEditCursor( mpImpl->mpDDInfo->maDropPos, true );
1736 aCursor.AdjustRight( 1 );
1737 aCursor.SetPos( GetWindowPos( aCursor.TopLeft() ) );
1739 mpImpl->mpDDInfo->maCursor.SetWindow( mpImpl->mpWindow );
1740 mpImpl->mpDDInfo->maCursor.SetPos( aCursor.TopLeft() );
1741 mpImpl->mpDDInfo->maCursor.SetSize( aCursor.GetSize() );
1742 mpImpl->mpDDInfo->maCursor.Show();
1743 mpImpl->mpDDInfo->mbVisCursor = true;
1747 void TextView::SetPaintSelection( bool bPaint )
1749 if ( bPaint != mpImpl->mbPaintSelection )
1751 mpImpl->mbPaintSelection = bPaint;
1752 ShowSelection( mpImpl->maSelection );
1756 void TextView::Read( SvStream& rInput )
1758 mpImpl->mpTextEngine->Read( rInput, &mpImpl->maSelection );
1759 ShowCursor();
1762 bool TextView::ImplTruncateNewText( OUString& rNewText ) const
1764 bool bTruncated = false;
1766 const sal_Int32 nMaxLen = mpImpl->mpTextEngine->GetMaxTextLen();
1767 // 0 means unlimited
1768 if( nMaxLen != 0 )
1770 const sal_Int32 nCurLen = mpImpl->mpTextEngine->GetTextLen();
1772 const sal_Int32 nNewLen = rNewText.getLength();
1773 if ( nCurLen + nNewLen > nMaxLen )
1775 // see how much text will be replaced
1776 const sal_Int32 nSelLen = mpImpl->mpTextEngine->GetTextLen( mpImpl->maSelection );
1777 if ( nCurLen + nNewLen - nSelLen > nMaxLen )
1779 const sal_Int32 nTruncatedLen = nMaxLen - (nCurLen - nSelLen);
1780 rNewText = rNewText.copy( 0, nTruncatedLen );
1781 bTruncated = true;
1785 return bTruncated;
1788 bool TextView::ImplCheckTextLen( const OUString& rNewText )
1790 bool bOK = true;
1791 if ( mpImpl->mpTextEngine->GetMaxTextLen() )
1793 sal_Int32 n = mpImpl->mpTextEngine->GetTextLen() + rNewText.getLength();
1794 if ( n > mpImpl->mpTextEngine->GetMaxTextLen() )
1796 // calculate how much text is being deleted
1797 n -= mpImpl->mpTextEngine->GetTextLen( mpImpl->maSelection );
1798 if ( n > mpImpl->mpTextEngine->GetMaxTextLen() )
1799 bOK = false;
1802 return bOK;
1805 void TextView::dragGestureRecognized( const css::datatransfer::dnd::DragGestureEvent& rDGE )
1807 if ( !mpImpl->mbClickedInSelection )
1808 return;
1810 SolarMutexGuard aVclGuard;
1812 SAL_WARN_IF( !mpImpl->maSelection.HasRange(), "vcl", "TextView::dragGestureRecognized: mpImpl->mbClickedInSelection, but no selection?" );
1814 mpImpl->mpDDInfo.reset(new TextDDInfo);
1815 mpImpl->mpDDInfo->mbStarterOfDD = true;
1817 TETextDataObject* pDataObj = new TETextDataObject( GetSelected() );
1819 mpImpl->mpCursor->Hide();
1821 sal_Int8 nActions = css::datatransfer::dnd::DNDConstants::ACTION_COPY;
1822 if ( !IsReadOnly() )
1823 nActions |= css::datatransfer::dnd::DNDConstants::ACTION_MOVE;
1824 rDGE.DragSource->startDrag( rDGE, nActions, 0 /*cursor*/, 0 /*image*/, pDataObj, mpImpl->mxDnDListener );
1827 void TextView::dragDropEnd( const css::datatransfer::dnd::DragSourceDropEvent& )
1829 ImpHideDDCursor();
1830 mpImpl->mpDDInfo.reset();
1833 void TextView::drop( const css::datatransfer::dnd::DropTargetDropEvent& rDTDE )
1835 SolarMutexGuard aVclGuard;
1837 if ( !mpImpl->mbReadOnly && mpImpl->mpDDInfo )
1839 ImpHideDDCursor();
1841 // Data for deleting after DROP_MOVE:
1842 TextSelection aPrevSel( mpImpl->maSelection );
1843 aPrevSel.Justify();
1844 const sal_uInt32 nPrevParaCount = mpImpl->mpTextEngine->GetParagraphCount();
1845 const sal_Int32 nPrevStartParaLen = mpImpl->mpTextEngine->GetTextLen( aPrevSel.GetStart().GetPara() );
1847 bool bStarterOfDD = false;
1848 for ( sal_uInt16 nView = mpImpl->mpTextEngine->GetViewCount(); nView && !bStarterOfDD; )
1849 bStarterOfDD = mpImpl->mpTextEngine->GetView( --nView )->mpImpl->mpDDInfo && mpImpl->mpTextEngine->GetView( nView )->mpImpl->mpDDInfo->mbStarterOfDD;
1851 HideSelection();
1852 ImpSetSelection( mpImpl->mpDDInfo->maDropPos );
1854 mpImpl->mpTextEngine->UndoActionStart();
1856 OUString aText;
1857 css::uno::Reference< css::datatransfer::XTransferable > xDataObj = rDTDE.Transferable;
1858 if ( xDataObj.is() )
1860 css::datatransfer::DataFlavor aFlavor;
1861 SotExchange::GetFormatDataFlavor( SotClipboardFormatId::STRING, aFlavor );
1862 if ( xDataObj->isDataFlavorSupported( aFlavor ) )
1864 css::uno::Any aData = xDataObj->getTransferData( aFlavor );
1865 OUString aOUString;
1866 aData >>= aOUString;
1867 aText = convertLineEnd(aOUString, LINEEND_LF);
1871 if ( !aText.isEmpty() && ( aText[ aText.getLength()-1 ] == LINE_SEP ) )
1872 aText = aText.copy(0, aText.getLength()-1);
1874 if ( ImplCheckTextLen( aText ) )
1875 ImpSetSelection( mpImpl->mpTextEngine->ImpInsertText( mpImpl->mpDDInfo->maDropPos, aText ) );
1877 if ( aPrevSel.HasRange() &&
1878 (( rDTDE.DropAction & css::datatransfer::dnd::DNDConstants::ACTION_MOVE ) || !bStarterOfDD) )
1880 // adjust selection if necessary
1881 if ( ( mpImpl->mpDDInfo->maDropPos.GetPara() < aPrevSel.GetStart().GetPara() ) ||
1882 ( ( mpImpl->mpDDInfo->maDropPos.GetPara() == aPrevSel.GetStart().GetPara() )
1883 && ( mpImpl->mpDDInfo->maDropPos.GetIndex() < aPrevSel.GetStart().GetIndex() ) ) )
1885 const sal_uInt32 nNewParasBeforeSelection =
1886 mpImpl->mpTextEngine->GetParagraphCount() - nPrevParaCount;
1888 aPrevSel.GetStart().GetPara() += nNewParasBeforeSelection;
1889 aPrevSel.GetEnd().GetPara() += nNewParasBeforeSelection;
1891 if ( mpImpl->mpDDInfo->maDropPos.GetPara() == aPrevSel.GetStart().GetPara() )
1893 const sal_Int32 nNewChars =
1894 mpImpl->mpTextEngine->GetTextLen( aPrevSel.GetStart().GetPara() ) - nPrevStartParaLen;
1896 aPrevSel.GetStart().GetIndex() += nNewChars;
1897 if ( aPrevSel.GetStart().GetPara() == aPrevSel.GetEnd().GetPara() )
1898 aPrevSel.GetEnd().GetIndex() += nNewChars;
1901 else
1903 // adjust current selection
1904 TextPaM aPaM = mpImpl->maSelection.GetStart();
1905 aPaM.GetPara() -= ( aPrevSel.GetEnd().GetPara() - aPrevSel.GetStart().GetPara() );
1906 if ( aPrevSel.GetEnd().GetPara() == mpImpl->mpDDInfo->maDropPos.GetPara() )
1908 aPaM.GetIndex() -= aPrevSel.GetEnd().GetIndex();
1909 if ( aPrevSel.GetStart().GetPara() == mpImpl->mpDDInfo->maDropPos.GetPara() )
1910 aPaM.GetIndex() += aPrevSel.GetStart().GetIndex();
1912 ImpSetSelection( aPaM );
1915 mpImpl->mpTextEngine->ImpDeleteText( aPrevSel );
1918 mpImpl->mpTextEngine->UndoActionEnd();
1920 mpImpl->mpDDInfo.reset();
1922 mpImpl->mpTextEngine->FormatAndUpdate( this );
1924 mpImpl->mpTextEngine->Broadcast( TextHint( SfxHintId::TextModified ) );
1926 rDTDE.Context->dropComplete( false/*bChanges*/ );
1929 void TextView::dragEnter( const css::datatransfer::dnd::DropTargetDragEnterEvent& )
1933 void TextView::dragExit( const css::datatransfer::dnd::DropTargetEvent& )
1935 SolarMutexGuard aVclGuard;
1936 ImpHideDDCursor();
1939 void TextView::dragOver( const css::datatransfer::dnd::DropTargetDragEvent& rDTDE )
1941 SolarMutexGuard aVclGuard;
1943 if (!mpImpl->mpDDInfo)
1944 mpImpl->mpDDInfo.reset(new TextDDInfo);
1946 TextPaM aPrevDropPos = mpImpl->mpDDInfo->maDropPos;
1947 Point aMousePos( rDTDE.LocationX, rDTDE.LocationY );
1948 Point aDocPos = GetDocPos( aMousePos );
1949 mpImpl->mpDDInfo->maDropPos = mpImpl->mpTextEngine->GetPaM( aDocPos );
1951 // Don't drop in selection or in read only engine
1952 if ( IsReadOnly() || IsInSelection( mpImpl->mpDDInfo->maDropPos ))
1954 ImpHideDDCursor();
1955 rDTDE.Context->rejectDrag();
1957 else
1959 // delete old Cursor
1960 if ( !mpImpl->mpDDInfo->mbVisCursor || ( aPrevDropPos != mpImpl->mpDDInfo->maDropPos ) )
1962 ImpHideDDCursor();
1963 ImpShowDDCursor();
1965 rDTDE.Context->acceptDrag( rDTDE.DropAction );
1969 Point TextView::ImpGetOutputStartPos( const Point& rStartDocPos ) const
1971 Point aStartPos( -rStartDocPos.X(), -rStartDocPos.Y() );
1972 if ( mpImpl->mpTextEngine->IsRightToLeft() )
1974 Size aSz = mpImpl->mpWindow->GetOutputSizePixel();
1975 aStartPos.setX( rStartDocPos.X() + aSz.Width() - 1 ); // -1: Start is 0
1977 return aStartPos;
1980 Point TextView::GetDocPos( const Point& rWindowPos ) const
1982 // Window Position => Document Position
1984 Point aPoint;
1986 aPoint.setY( rWindowPos.Y() + mpImpl->maStartDocPos.Y() );
1988 if ( !mpImpl->mpTextEngine->IsRightToLeft() )
1990 aPoint.setX( rWindowPos.X() + mpImpl->maStartDocPos.X() );
1992 else
1994 Size aSz = mpImpl->mpWindow->GetOutputSizePixel();
1995 aPoint.setX( ( aSz.Width() - 1 ) - rWindowPos.X() + mpImpl->maStartDocPos.X() );
1998 return aPoint;
2001 Point TextView::GetWindowPos( const Point& rDocPos ) const
2003 // Document Position => Window Position
2005 Point aPoint;
2007 aPoint.setY( rDocPos.Y() - mpImpl->maStartDocPos.Y() );
2009 if ( !mpImpl->mpTextEngine->IsRightToLeft() )
2011 aPoint.setX( rDocPos.X() - mpImpl->maStartDocPos.X() );
2013 else
2015 Size aSz = mpImpl->mpWindow->GetOutputSizePixel();
2016 aPoint.setX( ( aSz.Width() - 1 ) - ( rDocPos.X() - mpImpl->maStartDocPos.X() ) );
2019 return aPoint;
2022 sal_Int32 TextView::GetLineNumberOfCursorInSelection() const
2024 // PROGRESS
2025 sal_Int32 nLineNo = -1;
2026 if( mpImpl->mbCursorEnabled )
2028 TextPaM aPaM = GetSelection().GetEnd();
2029 TEParaPortion* pPPortion = mpImpl->mpTextEngine->mpTEParaPortions->GetObject( aPaM.GetPara() );
2030 nLineNo = pPPortion->GetLineNumber( aPaM.GetIndex(), false );
2031 //TODO: std::vector<TextLine>::size_type -> sal_Int32!
2032 if( mpImpl->mbCursorAtEndOfLine )
2033 --nLineNo;
2035 return nLineNo;
2038 // (+) class TextSelFunctionSet
2040 TextSelFunctionSet::TextSelFunctionSet( TextView* pView )
2042 mpView = pView;
2045 void TextSelFunctionSet::BeginDrag()
2049 void TextSelFunctionSet::CreateAnchor()
2051 // TextSelection aSel( mpView->GetSelection() );
2052 // aSel.GetStart() = aSel.GetEnd();
2053 // mpView->SetSelection( aSel );
2055 // may not be followed by ShowCursor
2056 mpView->HideSelection();
2057 mpView->ImpSetSelection( mpView->mpImpl->maSelection.GetEnd() );
2060 void TextSelFunctionSet::SetCursorAtPoint( const Point& rPointPixel, bool )
2062 mpView->SetCursorAtPoint( rPointPixel );
2065 bool TextSelFunctionSet::IsSelectionAtPoint( const Point& rPointPixel )
2067 return mpView->IsSelectionAtPoint( rPointPixel );
2070 void TextSelFunctionSet::DeselectAll()
2072 CreateAnchor();
2075 void TextSelFunctionSet::DeselectAtPoint( const Point& )
2077 // only for multiple selection
2080 void TextSelFunctionSet::DestroyAnchor()
2082 // only for multiple selection
2084 TextEngine* TextView::GetTextEngine() const
2085 { return mpImpl->mpTextEngine; }
2086 vcl::Window* TextView::GetWindow() const
2087 { return mpImpl->mpWindow; }
2088 void TextView::EnableCursor( bool bEnable )
2089 { mpImpl->mbCursorEnabled = bEnable; }
2090 bool TextView::IsCursorEnabled() const
2091 { return mpImpl->mbCursorEnabled; }
2092 void TextView::SetStartDocPos( const Point& rPos )
2093 { mpImpl->maStartDocPos = rPos; }
2094 const Point& TextView::GetStartDocPos() const
2095 { return mpImpl->maStartDocPos; }
2096 void TextView::SetAutoIndentMode( bool bAutoIndent )
2097 { mpImpl->mbAutoIndent = bAutoIndent; }
2098 bool TextView::IsReadOnly() const
2099 { return mpImpl->mbReadOnly; }
2100 void TextView::SetAutoScroll( bool bAutoScroll )
2101 { mpImpl->mbAutoScroll = bAutoScroll; }
2102 bool TextView::IsAutoScroll() const
2103 { return mpImpl->mbAutoScroll; }
2104 bool TextView::HasSelection() const
2105 { return mpImpl->maSelection.HasRange(); }
2106 bool TextView::IsInsertMode() const
2107 { return mpImpl->mbInsertMode; }
2109 void TextView::MatchGroup()
2111 TextSelection aTmpSel( GetSelection() );
2112 aTmpSel.Justify();
2113 if ( ( aTmpSel.GetStart().GetPara() != aTmpSel.GetEnd().GetPara() ) ||
2114 ( ( aTmpSel.GetEnd().GetIndex() - aTmpSel.GetStart().GetIndex() ) > 1 ) )
2116 return;
2119 TextSelection aMatchSel = static_cast<ExtTextEngine*>(GetTextEngine())->MatchGroup( aTmpSel.GetStart() );
2120 if ( aMatchSel.HasRange() )
2121 SetSelection( aMatchSel );
2124 void TextView::CenterPaM( const TextPaM& rPaM )
2126 // Get textview size and the corresponding y-coordinates
2127 Size aOutSz = mpImpl->mpWindow->GetOutputSizePixel();
2128 tools::Long nVisStartY = mpImpl->maStartDocPos.Y();
2129 tools::Long nVisEndY = mpImpl->maStartDocPos.Y() + aOutSz.Height();
2131 // Retrieve the coordinates of the PaM
2132 tools::Rectangle aRect = mpImpl->mpTextEngine->PaMtoEditCursor(rPaM);
2134 // Recalculate the offset of the center y-coordinates and scroll
2135 Scroll(0, (nVisStartY + nVisEndY) / 2 - aRect.TopLeft().getY());
2138 bool TextView::Search( const i18nutil::SearchOptions& rSearchOptions, bool bForward )
2140 bool bFound = false;
2141 TextSelection aSel( GetSelection() );
2142 if ( static_cast<ExtTextEngine*>(GetTextEngine())->Search( aSel, rSearchOptions, bForward ) )
2144 bFound = true;
2145 // First add the beginning of the word to the selection,
2146 // so that the whole word is in the visible region.
2147 SetSelection( aSel.GetStart() );
2148 ShowCursor( true, false );
2150 else
2152 aSel = GetSelection().GetEnd();
2155 SetSelection( aSel );
2156 // tdf#49482: Move the start of the selection to the center of the textview
2157 if (bFound)
2159 CenterPaM( aSel.GetStart() );
2161 ShowCursor();
2163 return bFound;
2166 sal_uInt16 TextView::Replace( const i18nutil::SearchOptions& rSearchOptions, bool bAll, bool bForward )
2168 sal_uInt16 nFound = 0;
2170 if ( !bAll )
2172 if ( GetSelection().HasRange() )
2174 InsertText( rSearchOptions.replaceString );
2175 nFound = 1;
2176 Search( rSearchOptions, bForward ); // right away to the next
2178 else
2180 if( Search( rSearchOptions, bForward ) )
2181 nFound = 1;
2184 else
2186 // the writer replaces all, from beginning to end
2188 ExtTextEngine* pTextEngine = static_cast<ExtTextEngine*>(GetTextEngine());
2190 // HideSelection();
2191 TextSelection aSel;
2193 bool bSearchInSelection = (0 != (rSearchOptions.searchFlag & css::util::SearchFlags::REG_NOT_BEGINOFLINE) );
2194 if ( bSearchInSelection )
2196 aSel = GetSelection();
2197 aSel.Justify();
2200 TextSelection aSearchSel( aSel );
2202 bool bFound = pTextEngine->Search( aSel, rSearchOptions );
2203 if ( bFound )
2204 pTextEngine->UndoActionStart();
2205 while ( bFound )
2207 nFound++;
2209 TextPaM aNewStart = pTextEngine->ImpInsertText( aSel, rSearchOptions.replaceString );
2210 // tdf#64690 - extend selection to include inserted text portions
2211 if ( aSel.GetEnd().GetPara() == aSearchSel.GetEnd().GetPara() )
2213 aSearchSel.GetEnd().GetIndex() += rSearchOptions.replaceString.getLength() - 1;
2215 aSel = aSearchSel;
2216 aSel.GetStart() = aNewStart;
2217 bFound = pTextEngine->Search( aSel, rSearchOptions );
2219 if ( nFound )
2221 SetSelection( aSel.GetStart() );
2222 pTextEngine->FormatAndUpdate( this );
2223 pTextEngine->UndoActionEnd();
2226 return nFound;
2229 bool TextView::ImpIndentBlock( bool bRight )
2231 bool bDone = false;
2233 TextSelection aSel = GetSelection();
2234 aSel.Justify();
2236 HideSelection();
2237 GetTextEngine()->UndoActionStart();
2239 const sal_uInt32 nStartPara = aSel.GetStart().GetPara();
2240 sal_uInt32 nEndPara = aSel.GetEnd().GetPara();
2241 if ( aSel.HasRange() && !aSel.GetEnd().GetIndex() )
2243 nEndPara--; // do not indent
2246 for ( sal_uInt32 nPara = nStartPara; nPara <= nEndPara; ++nPara )
2248 if ( bRight )
2250 // add tabs
2251 GetTextEngine()->ImpInsertText( TextPaM( nPara, 0 ), '\t' );
2252 bDone = true;
2254 else
2256 // remove Tabs/Blanks
2257 OUString aText = GetTextEngine()->GetText( nPara );
2258 if ( !aText.isEmpty() && (
2259 ( aText[ 0 ] == '\t' ) ||
2260 ( aText[ 0 ] == ' ' ) ) )
2262 GetTextEngine()->ImpDeleteText( TextSelection( TextPaM( nPara, 0 ), TextPaM( nPara, 1 ) ) );
2263 bDone = true;
2268 GetTextEngine()->UndoActionEnd();
2270 bool bRange = aSel.HasRange();
2271 if ( bRight )
2273 ++aSel.GetStart().GetIndex();
2274 if ( bRange && ( aSel.GetEnd().GetPara() == nEndPara ) )
2275 ++aSel.GetEnd().GetIndex();
2277 else
2279 if ( aSel.GetStart().GetIndex() )
2280 --aSel.GetStart().GetIndex();
2281 if ( bRange && aSel.GetEnd().GetIndex() )
2282 --aSel.GetEnd().GetIndex();
2285 ImpSetSelection( aSel );
2286 GetTextEngine()->FormatAndUpdate( this );
2288 return bDone;
2291 bool TextView::IndentBlock()
2293 return ImpIndentBlock( true );
2296 bool TextView::UnindentBlock()
2298 return ImpIndentBlock( false );
2302 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */