bump product version to 6.4.0.3
[LibreOffice.git] / vcl / source / edit / textview.cxx
blob2eaa1b2f598cbe1a7342cf1a2f71a1f6e851dfb5
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/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 <<= GetText();
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() )
309 mpImpl->mpCursor->Hide();
311 SAL_WARN_IF( mpImpl->mpTextEngine->mpIdleFormatter->IsActive(), "vcl", "ImpHighlight: Not formatted!" );
313 tools::Rectangle aVisArea( mpImpl->maStartDocPos, mpImpl->mpWindow->GetOutputSizePixel() );
314 long nY = 0;
315 const sal_uInt32 nStartPara = aSel.GetStart().GetPara();
316 const sal_uInt32 nEndPara = aSel.GetEnd().GetPara();
317 for ( sal_uInt32 nPara = 0; nPara <= nEndPara; ++nPara )
319 const long nParaHeight = mpImpl->mpTextEngine->CalcParaHeight( nPara );
320 if ( ( nPara >= nStartPara ) && ( ( nY + nParaHeight ) > aVisArea.Top() ) )
322 TEParaPortion* pTEParaPortion = mpImpl->mpTextEngine->mpTEParaPortions->GetObject( nPara );
323 std::vector<TextLine>::size_type nStartLine = 0;
324 std::vector<TextLine>::size_type nEndLine = pTEParaPortion->GetLines().size() -1;
325 if ( nPara == nStartPara )
326 nStartLine = pTEParaPortion->GetLineNumber( aSel.GetStart().GetIndex(), false );
327 if ( nPara == nEndPara )
328 nEndLine = pTEParaPortion->GetLineNumber( aSel.GetEnd().GetIndex(), true );
330 // iterate over all lines
331 for ( std::vector<TextLine>::size_type nLine = nStartLine; nLine <= nEndLine; nLine++ )
333 TextLine& rLine = pTEParaPortion->GetLines()[ nLine ];
334 sal_Int32 nStartIndex = rLine.GetStart();
335 sal_Int32 nEndIndex = rLine.GetEnd();
336 if ( ( nPara == nStartPara ) && ( nLine == nStartLine ) )
337 nStartIndex = aSel.GetStart().GetIndex();
338 if ( ( nPara == nEndPara ) && ( nLine == nEndLine ) )
339 nEndIndex = aSel.GetEnd().GetIndex();
341 // possible if at the beginning of a wrapped line
342 if ( nEndIndex < nStartIndex )
343 nEndIndex = nStartIndex;
345 tools::Rectangle aTmpRect( mpImpl->mpTextEngine->GetEditCursor( TextPaM( nPara, nStartIndex ), false ) );
346 aTmpRect.AdjustTop(nY );
347 aTmpRect.AdjustBottom(nY );
348 Point aTopLeft( aTmpRect.TopLeft() );
350 aTmpRect = mpImpl->mpTextEngine->GetEditCursor( TextPaM( nPara, nEndIndex ), true );
351 aTmpRect.AdjustTop(nY );
352 aTmpRect.AdjustBottom(nY );
353 Point aBottomRight( aTmpRect.BottomRight() );
354 aBottomRight.AdjustX( -1 );
356 // only paint if in the visible region
357 if ( ( aTopLeft.X() < aBottomRight.X() ) && ( aBottomRight.Y() >= aVisArea.Top() ) )
359 Point aPnt1( GetWindowPos( aTopLeft ) );
360 Point aPnt2( GetWindowPos( aBottomRight ) );
362 tools::Rectangle aRect( aPnt1, aPnt2 );
363 mpImpl->mpWindow->Invert( aRect );
367 nY += nParaHeight;
369 if ( nY >= aVisArea.Bottom() )
370 break;
375 void TextView::ImpSetSelection( const TextSelection& rSelection )
377 if (rSelection != mpImpl->maSelection)
379 bool bCaret = false, bSelection = false;
380 const TextPaM &rEnd = rSelection.GetEnd();
381 const TextPaM &rOldEnd = mpImpl->maSelection.GetEnd();
382 bool bGap = rSelection.HasRange(), bOldGap = mpImpl->maSelection.HasRange();
383 if (rEnd != rOldEnd)
384 bCaret = true;
385 if (bGap || bOldGap)
386 bSelection = true;
388 mpImpl->maSelection = rSelection;
390 if (bSelection)
391 mpImpl->mpTextEngine->Broadcast(TextHint(SfxHintId::TextViewSelectionChanged));
393 if (bCaret)
394 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() )
419 if ( mpImpl->mbHighlightSelection )
421 ImpHighlight( *pRangeOrSelection );
423 else
425 if( mpImpl->mpWindow->IsPaintTransparent() )
426 mpImpl->mpWindow->Invalidate();
427 else
429 TextSelection aRange( *pRangeOrSelection );
430 aRange.Justify();
431 bool bVisCursor = mpImpl->mpCursor->IsVisible();
432 mpImpl->mpCursor->Hide();
433 Invalidate();
434 if (bVisCursor)
435 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 ) )
720 if ( rMouseEvent.IsMod2() )
722 HideSelection();
723 ImpSetSelection( mpImpl->maSelection.GetEnd() );
724 SetCursorAtPoint( rMouseEvent.GetPosPixel() ); // not set by SelectionEngine for MOD2
727 if ( rMouseEvent.GetClicks() == 2 )
729 // select word
730 if ( mpImpl->maSelection.GetEnd().GetIndex() < mpImpl->mpTextEngine->GetTextLen( mpImpl->maSelection.GetEnd().GetPara() ) )
732 HideSelection();
733 TextNode* pNode = mpImpl->mpTextEngine->mpDoc->GetNodes()[ mpImpl->maSelection.GetEnd().GetPara() ].get();
734 css::uno::Reference < css::i18n::XBreakIterator > xBI = mpImpl->mpTextEngine->GetBreakIterator();
735 css::i18n::Boundary aBoundary = xBI->getWordBoundary( pNode->GetText(), mpImpl->maSelection.GetEnd().GetIndex(), mpImpl->mpTextEngine->GetLocale(), css::i18n::WordType::ANYWORD_IGNOREWHITESPACES, true );
736 TextSelection aNewSel( mpImpl->maSelection );
737 aNewSel.GetStart().GetIndex() = aBoundary.startPos;
738 aNewSel.GetEnd().GetIndex() = aBoundary.endPos;
739 ImpSetSelection( aNewSel );
740 ShowSelection();
741 ShowCursor();
744 else if ( rMouseEvent.GetClicks() == 3 )
746 // select paragraph
747 if ( mpImpl->maSelection.GetStart().GetIndex() || ( mpImpl->maSelection.GetEnd().GetIndex() < mpImpl->mpTextEngine->GetTextLen( mpImpl->maSelection.GetEnd().GetPara() ) ) )
749 HideSelection();
750 TextSelection aNewSel( mpImpl->maSelection );
751 aNewSel.GetStart().GetIndex() = 0;
752 aNewSel.GetEnd().GetIndex() = mpImpl->mpTextEngine->mpDoc->GetNodes()[ mpImpl->maSelection.GetEnd().GetPara() ]->GetText().getLength();
753 ImpSetSelection( aNewSel );
754 ShowSelection();
755 ShowCursor();
761 void TextView::MouseMove( const MouseEvent& rMouseEvent )
763 mpImpl->mnTravelXPos = TRAVEL_X_DONTKNOW;
764 mpImpl->mpSelEngine->SelMouseMove( rMouseEvent );
767 void TextView::Command( const CommandEvent& rCEvt )
769 mpImpl->mpTextEngine->CheckIdleFormatter(); // for fast typing and MouseButtonDown
770 mpImpl->mpTextEngine->SetActiveView( this );
772 if ( rCEvt.GetCommand() == CommandEventId::StartExtTextInput )
774 DeleteSelected();
775 TextNode* pNode = mpImpl->mpTextEngine->mpDoc->GetNodes()[ GetSelection().GetEnd().GetPara() ].get();
776 mpImpl->mpTextEngine->mpIMEInfos = std::make_unique<TEIMEInfos>( GetSelection().GetEnd(), pNode->GetText().copy( GetSelection().GetEnd().GetIndex() ) );
777 mpImpl->mpTextEngine->mpIMEInfos->bWasCursorOverwrite = !IsInsertMode();
779 else if ( rCEvt.GetCommand() == CommandEventId::EndExtTextInput )
781 SAL_WARN_IF( !mpImpl->mpTextEngine->mpIMEInfos, "vcl", "CommandEventId::EndExtTextInput => No Start ?" );
782 if( mpImpl->mpTextEngine->mpIMEInfos )
784 TEParaPortion* pPortion = mpImpl->mpTextEngine->mpTEParaPortions->GetObject( mpImpl->mpTextEngine->mpIMEInfos->aPos.GetPara() );
785 pPortion->MarkSelectionInvalid( mpImpl->mpTextEngine->mpIMEInfos->aPos.GetIndex() );
787 bool bInsertMode = !mpImpl->mpTextEngine->mpIMEInfos->bWasCursorOverwrite;
789 mpImpl->mpTextEngine->mpIMEInfos.reset();
791 mpImpl->mpTextEngine->TextModified();
792 mpImpl->mpTextEngine->FormatAndUpdate( this );
794 SetInsertMode( bInsertMode );
796 if ( mpImpl->mpTextEngine->IsModified() )
797 mpImpl->mpTextEngine->Broadcast( TextHint( SfxHintId::TextModified ) );
800 else if ( rCEvt.GetCommand() == CommandEventId::ExtTextInput )
802 SAL_WARN_IF( !mpImpl->mpTextEngine->mpIMEInfos, "vcl", "CommandEventId::ExtTextInput => No Start ?" );
803 if( mpImpl->mpTextEngine->mpIMEInfos )
805 const CommandExtTextInputData* pData = rCEvt.GetExtTextInputData();
807 if ( !pData->IsOnlyCursorChanged() )
809 TextSelection aSelect( mpImpl->mpTextEngine->mpIMEInfos->aPos );
810 aSelect.GetEnd().GetIndex() += mpImpl->mpTextEngine->mpIMEInfos->nLen;
811 aSelect = mpImpl->mpTextEngine->ImpDeleteText( aSelect );
812 aSelect = mpImpl->mpTextEngine->ImpInsertText( aSelect, pData->GetText() );
814 if ( mpImpl->mpTextEngine->mpIMEInfos->bWasCursorOverwrite )
816 const sal_Int32 nOldIMETextLen = mpImpl->mpTextEngine->mpIMEInfos->nLen;
817 const sal_Int32 nNewIMETextLen = pData->GetText().getLength();
819 if ( ( nOldIMETextLen > nNewIMETextLen ) &&
820 ( nNewIMETextLen < mpImpl->mpTextEngine->mpIMEInfos->aOldTextAfterStartPos.getLength() ) )
822 // restore old characters
823 sal_Int32 nRestore = nOldIMETextLen - nNewIMETextLen;
824 TextPaM aPaM( mpImpl->mpTextEngine->mpIMEInfos->aPos );
825 aPaM.GetIndex() += nNewIMETextLen;
826 mpImpl->mpTextEngine->ImpInsertText( aPaM, mpImpl->mpTextEngine->mpIMEInfos->aOldTextAfterStartPos.copy( nNewIMETextLen, nRestore ) );
828 else if ( ( nOldIMETextLen < nNewIMETextLen ) &&
829 ( nOldIMETextLen < mpImpl->mpTextEngine->mpIMEInfos->aOldTextAfterStartPos.getLength() ) )
831 // overwrite
832 const sal_Int32 nOverwrite = std::min( nNewIMETextLen, mpImpl->mpTextEngine->mpIMEInfos->aOldTextAfterStartPos.getLength() ) - nOldIMETextLen;
833 SAL_WARN_IF( !nOverwrite || (nOverwrite >= 0xFF00), "vcl", "IME Overwrite?!" );
834 TextPaM aPaM( mpImpl->mpTextEngine->mpIMEInfos->aPos );
835 aPaM.GetIndex() += nNewIMETextLen;
836 TextSelection aSel( aPaM );
837 aSel.GetEnd().GetIndex() += nOverwrite;
838 mpImpl->mpTextEngine->ImpDeleteText( aSel );
842 if ( pData->GetTextAttr() )
844 mpImpl->mpTextEngine->mpIMEInfos->CopyAttribs( pData->GetTextAttr(), pData->GetText().getLength() );
846 else
848 mpImpl->mpTextEngine->mpIMEInfos->DestroyAttribs();
851 TEParaPortion* pPPortion = mpImpl->mpTextEngine->mpTEParaPortions->GetObject( mpImpl->mpTextEngine->mpIMEInfos->aPos.GetPara() );
852 pPPortion->MarkSelectionInvalid( mpImpl->mpTextEngine->mpIMEInfos->aPos.GetIndex() );
853 mpImpl->mpTextEngine->FormatAndUpdate( this );
856 TextSelection aNewSel = TextPaM( mpImpl->mpTextEngine->mpIMEInfos->aPos.GetPara(), mpImpl->mpTextEngine->mpIMEInfos->aPos.GetIndex()+pData->GetCursorPos() );
857 SetSelection( aNewSel );
858 SetInsertMode( !pData->IsCursorOverwrite() );
860 if ( pData->IsCursorVisible() )
861 ShowCursor();
862 else
863 HideCursor();
866 else if ( rCEvt.GetCommand() == CommandEventId::CursorPos )
868 if ( mpImpl->mpTextEngine->mpIMEInfos && mpImpl->mpTextEngine->mpIMEInfos->nLen )
870 TextPaM aPaM( GetSelection().GetEnd() );
871 tools::Rectangle aR1 = mpImpl->mpTextEngine->PaMtoEditCursor( aPaM );
873 sal_Int32 nInputEnd = mpImpl->mpTextEngine->mpIMEInfos->aPos.GetIndex() + mpImpl->mpTextEngine->mpIMEInfos->nLen;
875 if ( !mpImpl->mpTextEngine->IsFormatted() )
876 mpImpl->mpTextEngine->FormatDoc();
878 TEParaPortion* pParaPortion = mpImpl->mpTextEngine->mpTEParaPortions->GetObject( aPaM.GetPara() );
879 std::vector<TextLine>::size_type nLine = pParaPortion->GetLineNumber( aPaM.GetIndex(), true );
880 TextLine& rLine = pParaPortion->GetLines()[ nLine ];
881 if ( nInputEnd > rLine.GetEnd() )
882 nInputEnd = rLine.GetEnd();
883 tools::Rectangle aR2 = mpImpl->mpTextEngine->PaMtoEditCursor( TextPaM( aPaM.GetPara(), nInputEnd ) );
885 long nWidth = aR2.Left()-aR1.Right();
886 aR1.Move( -GetStartDocPos().X(), -GetStartDocPos().Y() );
887 GetWindow()->SetCursorRect( &aR1, nWidth );
889 else
891 GetWindow()->SetCursorRect();
894 else
896 mpImpl->mpSelEngine->Command( rCEvt );
900 void TextView::ShowCursor( bool bGotoCursor, bool bForceVisCursor )
902 // this setting has more weight
903 if ( !mpImpl->mbAutoScroll )
904 bGotoCursor = false;
905 ImpShowCursor( bGotoCursor, bForceVisCursor, false );
908 void TextView::HideCursor()
910 mpImpl->mpCursor->Hide();
913 void TextView::Scroll( long ndX, long ndY )
915 SAL_WARN_IF( !mpImpl->mpTextEngine->IsFormatted(), "vcl", "Scroll: Not formatted!" );
917 if ( !ndX && !ndY )
918 return;
920 Point aNewStartPos( mpImpl->maStartDocPos );
922 // Vertical:
923 aNewStartPos.AdjustY( -ndY );
924 if ( aNewStartPos.Y() < 0 )
925 aNewStartPos.setY( 0 );
927 // Horizontal:
928 aNewStartPos.AdjustX( -ndX );
929 if ( aNewStartPos.X() < 0 )
930 aNewStartPos.setX( 0 );
932 long nDiffX = mpImpl->maStartDocPos.X() - aNewStartPos.X();
933 long nDiffY = mpImpl->maStartDocPos.Y() - aNewStartPos.Y();
935 if ( nDiffX || nDiffY )
937 bool bVisCursor = mpImpl->mpCursor->IsVisible();
938 mpImpl->mpCursor->Hide();
939 mpImpl->mpWindow->Update();
940 mpImpl->maStartDocPos = aNewStartPos;
942 if ( mpImpl->mpTextEngine->IsRightToLeft() )
943 nDiffX = -nDiffX;
944 mpImpl->mpWindow->Scroll( nDiffX, nDiffY );
945 mpImpl->mpWindow->Update();
946 mpImpl->mpCursor->SetPos( mpImpl->mpCursor->GetPos() + Point( nDiffX, nDiffY ) );
947 if ( bVisCursor && !mpImpl->mbReadOnly )
948 mpImpl->mpCursor->Show();
951 mpImpl->mpTextEngine->Broadcast( TextHint( SfxHintId::TextViewScrolled ) );
954 void TextView::Undo()
956 mpImpl->mpTextEngine->SetActiveView( this );
957 mpImpl->mpTextEngine->GetUndoManager().Undo();
960 void TextView::Redo()
962 mpImpl->mpTextEngine->SetActiveView( this );
963 mpImpl->mpTextEngine->GetUndoManager().Redo();
966 void TextView::Cut()
968 mpImpl->mpTextEngine->UndoActionStart();
969 Copy();
970 DeleteSelected();
971 mpImpl->mpTextEngine->UndoActionEnd();
974 void TextView::Copy( css::uno::Reference< css::datatransfer::clipboard::XClipboard > const & rxClipboard )
976 if ( rxClipboard.is() )
978 TETextDataObject* pDataObj = new TETextDataObject( GetSelected() );
980 SolarMutexReleaser aReleaser;
984 rxClipboard->setContents( pDataObj, nullptr );
986 css::uno::Reference< css::datatransfer::clipboard::XFlushableClipboard > xFlushableClipboard( rxClipboard, css::uno::UNO_QUERY );
987 if( xFlushableClipboard.is() )
988 xFlushableClipboard->flushClipboard();
990 catch( const css::uno::Exception& )
996 void TextView::Copy()
998 css::uno::Reference<css::datatransfer::clipboard::XClipboard> aClipboard(GetWindow()->GetClipboard());
999 Copy( aClipboard );
1002 void TextView::Paste( css::uno::Reference< css::datatransfer::clipboard::XClipboard > const & rxClipboard )
1004 if ( rxClipboard.is() )
1006 css::uno::Reference< css::datatransfer::XTransferable > xDataObj;
1010 SolarMutexReleaser aReleaser;
1011 xDataObj = rxClipboard->getContents();
1013 catch( const css::uno::Exception& )
1017 if ( xDataObj.is() )
1019 css::datatransfer::DataFlavor aFlavor;
1020 SotExchange::GetFormatDataFlavor( SotClipboardFormatId::STRING, aFlavor );
1021 if ( xDataObj->isDataFlavorSupported( aFlavor ) )
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& )
1045 void TextView::Paste()
1047 css::uno::Reference<css::datatransfer::clipboard::XClipboard> aClipboard(GetWindow()->GetClipboard());
1048 Paste( aClipboard );
1051 OUString TextView::GetSelected()
1053 return GetSelected( GetSystemLineEnd() );
1056 OUString TextView::GetSelected( LineEnd aSeparator )
1058 return mpImpl->mpTextEngine->GetText( mpImpl->maSelection, aSeparator );
1061 void TextView::SetInsertMode( bool bInsert )
1063 if ( mpImpl->mbInsertMode != bInsert )
1065 mpImpl->mbInsertMode = bInsert;
1066 ShowCursor( mpImpl->mbAutoScroll, false );
1070 void TextView::SetReadOnly( bool bReadOnly )
1072 if ( mpImpl->mbReadOnly != bReadOnly )
1074 mpImpl->mbReadOnly = bReadOnly;
1075 if ( !mpImpl->mbReadOnly )
1076 ShowCursor( mpImpl->mbAutoScroll, false );
1077 else
1078 HideCursor();
1080 GetWindow()->SetInputContext( InputContext( mpImpl->mpTextEngine->GetFont(), bReadOnly ? InputContextFlags::Text|InputContextFlags::ExtText : InputContextFlags::NONE ) );
1084 TextSelection const & TextView::ImpMoveCursor( const KeyEvent& rKeyEvent )
1086 // normally only needed for Up/Down; but who cares
1087 mpImpl->mpTextEngine->CheckIdleFormatter();
1089 TextPaM aPaM( mpImpl->maSelection.GetEnd() );
1090 TextPaM aOldEnd( aPaM );
1092 TextDirectionality eTextDirection = TextDirectionality::LeftToRight_TopToBottom;
1093 if ( mpImpl->mpTextEngine->IsRightToLeft() )
1094 eTextDirection = TextDirectionality::RightToLeft_TopToBottom;
1096 KeyEvent aTranslatedKeyEvent = rKeyEvent.LogicalTextDirectionality( eTextDirection );
1098 bool bCtrl = aTranslatedKeyEvent.GetKeyCode().IsMod1();
1099 sal_uInt16 nCode = aTranslatedKeyEvent.GetKeyCode().GetCode();
1101 bool bSelect = aTranslatedKeyEvent.GetKeyCode().IsShift();
1102 switch ( nCode )
1104 case KEY_UP: aPaM = CursorUp( aPaM );
1105 break;
1106 case KEY_DOWN: aPaM = CursorDown( aPaM );
1107 break;
1108 case KEY_HOME: aPaM = bCtrl ? CursorStartOfDoc() : CursorStartOfLine( aPaM );
1109 break;
1110 case KEY_END: aPaM = bCtrl ? CursorEndOfDoc() : CursorEndOfLine( aPaM );
1111 break;
1112 case KEY_PAGEUP: aPaM = bCtrl ? CursorStartOfDoc() : PageUp( aPaM );
1113 break;
1114 case KEY_PAGEDOWN: aPaM = bCtrl ? CursorEndOfDoc() : PageDown( aPaM );
1115 break;
1116 case KEY_LEFT: aPaM = bCtrl ? CursorWordLeft( aPaM ) : CursorLeft( aPaM, aTranslatedKeyEvent.GetKeyCode().IsMod2() ? sal_uInt16(css::i18n::CharacterIteratorMode::SKIPCHARACTER) : sal_uInt16(css::i18n::CharacterIteratorMode::SKIPCELL) );
1117 break;
1118 case KEY_RIGHT: aPaM = bCtrl ? CursorWordRight( aPaM ) : CursorRight( aPaM, aTranslatedKeyEvent.GetKeyCode().IsMod2() ? sal_uInt16(css::i18n::CharacterIteratorMode::SKIPCHARACTER) : sal_uInt16(css::i18n::CharacterIteratorMode::SKIPCELL) );
1119 break;
1120 case css::awt::Key::SELECT_WORD_FORWARD:
1121 bSelect = true;
1122 [[fallthrough]];
1123 case css::awt::Key::MOVE_WORD_FORWARD:
1124 aPaM = CursorWordRight( aPaM );
1125 break;
1126 case css::awt::Key::SELECT_WORD_BACKWARD:
1127 bSelect = true;
1128 [[fallthrough]];
1129 case css::awt::Key::MOVE_WORD_BACKWARD:
1130 aPaM = CursorWordLeft( aPaM );
1131 break;
1132 case css::awt::Key::SELECT_TO_BEGIN_OF_LINE:
1133 bSelect = true;
1134 [[fallthrough]];
1135 case css::awt::Key::MOVE_TO_BEGIN_OF_LINE:
1136 aPaM = CursorStartOfLine( aPaM );
1137 break;
1138 case css::awt::Key::SELECT_TO_END_OF_LINE:
1139 bSelect = true;
1140 [[fallthrough]];
1141 case css::awt::Key::MOVE_TO_END_OF_LINE:
1142 aPaM = CursorEndOfLine( aPaM );
1143 break;
1144 case css::awt::Key::SELECT_TO_BEGIN_OF_PARAGRAPH:
1145 bSelect = true;
1146 [[fallthrough]];
1147 case css::awt::Key::MOVE_TO_BEGIN_OF_PARAGRAPH:
1148 aPaM = CursorStartOfParagraph( aPaM );
1149 break;
1150 case css::awt::Key::SELECT_TO_END_OF_PARAGRAPH:
1151 bSelect = true;
1152 [[fallthrough]];
1153 case css::awt::Key::MOVE_TO_END_OF_PARAGRAPH:
1154 aPaM = CursorEndOfParagraph( aPaM );
1155 break;
1156 case css::awt::Key::SELECT_TO_BEGIN_OF_DOCUMENT:
1157 bSelect = true;
1158 [[fallthrough]];
1159 case css::awt::Key::MOVE_TO_BEGIN_OF_DOCUMENT:
1160 aPaM = CursorStartOfDoc();
1161 break;
1162 case css::awt::Key::SELECT_TO_END_OF_DOCUMENT:
1163 bSelect = true;
1164 [[fallthrough]];
1165 case css::awt::Key::MOVE_TO_END_OF_DOCUMENT:
1166 aPaM = CursorEndOfDoc();
1167 break;
1170 // might cause a CreateAnchor or Deselection all
1171 mpImpl->mpSelEngine->CursorPosChanging( bSelect, aTranslatedKeyEvent.GetKeyCode().IsMod1() );
1173 if ( aOldEnd != aPaM )
1175 mpImpl->mpTextEngine->CursorMoved( aOldEnd.GetPara() );
1177 TextSelection aNewSelection( mpImpl->maSelection );
1178 aNewSelection.GetEnd() = aPaM;
1179 if ( bSelect )
1181 // extend the selection
1182 ImpSetSelection( aNewSelection );
1183 ShowSelection( TextSelection( aOldEnd, aPaM ) );
1185 else
1187 aNewSelection.GetStart() = aPaM;
1188 ImpSetSelection( aNewSelection );
1192 return mpImpl->maSelection;
1195 void TextView::InsertText( const OUString& rStr )
1197 mpImpl->mpTextEngine->UndoActionStart();
1199 TextSelection aNewSel = mpImpl->mpTextEngine->ImpInsertText( mpImpl->maSelection, rStr );
1201 ImpSetSelection( aNewSel );
1203 mpImpl->mpTextEngine->UndoActionEnd();
1205 mpImpl->mpTextEngine->FormatAndUpdate( this );
1208 TextPaM TextView::CursorLeft( const TextPaM& rPaM, sal_uInt16 nCharacterIteratorMode )
1210 TextPaM aPaM( rPaM );
1212 if ( aPaM.GetIndex() )
1214 TextNode* pNode = mpImpl->mpTextEngine->mpDoc->GetNodes()[ aPaM.GetPara() ].get();
1215 css::uno::Reference < css::i18n::XBreakIterator > xBI = mpImpl->mpTextEngine->GetBreakIterator();
1216 sal_Int32 nCount = 1;
1217 aPaM.GetIndex() = xBI->previousCharacters( pNode->GetText(), aPaM.GetIndex(), mpImpl->mpTextEngine->GetLocale(), nCharacterIteratorMode, nCount, nCount );
1219 else if ( aPaM.GetPara() )
1221 aPaM.GetPara()--;
1222 TextNode* pNode = mpImpl->mpTextEngine->mpDoc->GetNodes()[ aPaM.GetPara() ].get();
1223 aPaM.GetIndex() = pNode->GetText().getLength();
1225 return aPaM;
1228 TextPaM TextView::CursorRight( const TextPaM& rPaM, sal_uInt16 nCharacterIteratorMode )
1230 TextPaM aPaM( rPaM );
1232 TextNode* pNode = mpImpl->mpTextEngine->mpDoc->GetNodes()[ aPaM.GetPara() ].get();
1233 if ( aPaM.GetIndex() < pNode->GetText().getLength() )
1235 css::uno::Reference < css::i18n::XBreakIterator > xBI = mpImpl->mpTextEngine->GetBreakIterator();
1236 sal_Int32 nCount = 1;
1237 aPaM.GetIndex() = xBI->nextCharacters( pNode->GetText(), aPaM.GetIndex(), mpImpl->mpTextEngine->GetLocale(), nCharacterIteratorMode, nCount, nCount );
1239 else if ( aPaM.GetPara() < ( mpImpl->mpTextEngine->mpDoc->GetNodes().size()-1) )
1241 aPaM.GetPara()++;
1242 aPaM.GetIndex() = 0;
1245 return aPaM;
1248 TextPaM TextView::CursorWordLeft( const TextPaM& rPaM )
1250 TextPaM aPaM( rPaM );
1252 if ( aPaM.GetIndex() )
1254 TextNode* pNode = mpImpl->mpTextEngine->mpDoc->GetNodes()[ aPaM.GetPara() ].get();
1255 css::uno::Reference < css::i18n::XBreakIterator > xBI = mpImpl->mpTextEngine->GetBreakIterator();
1256 css::i18n::Boundary aBoundary = xBI->getWordBoundary( pNode->GetText(), rPaM.GetIndex(), mpImpl->mpTextEngine->GetLocale(), css::i18n::WordType::ANYWORD_IGNOREWHITESPACES, true );
1257 if ( aBoundary.startPos >= rPaM.GetIndex() )
1258 aBoundary = xBI->previousWord( pNode->GetText(), rPaM.GetIndex(), mpImpl->mpTextEngine->GetLocale(), css::i18n::WordType::ANYWORD_IGNOREWHITESPACES );
1259 aPaM.GetIndex() = ( aBoundary.startPos != -1 ) ? aBoundary.startPos : 0;
1261 else if ( aPaM.GetPara() )
1263 aPaM.GetPara()--;
1264 TextNode* pNode = mpImpl->mpTextEngine->mpDoc->GetNodes()[ aPaM.GetPara() ].get();
1265 aPaM.GetIndex() = pNode->GetText().getLength();
1267 return aPaM;
1270 TextPaM TextView::CursorWordRight( const TextPaM& rPaM )
1272 TextPaM aPaM( rPaM );
1274 TextNode* pNode = mpImpl->mpTextEngine->mpDoc->GetNodes()[ aPaM.GetPara() ].get();
1275 if ( aPaM.GetIndex() < pNode->GetText().getLength() )
1277 css::uno::Reference < css::i18n::XBreakIterator > xBI = mpImpl->mpTextEngine->GetBreakIterator();
1278 css::i18n::Boundary aBoundary = xBI->nextWord( pNode->GetText(), aPaM.GetIndex(), mpImpl->mpTextEngine->GetLocale(), css::i18n::WordType::ANYWORD_IGNOREWHITESPACES );
1279 aPaM.GetIndex() = aBoundary.startPos;
1281 else if ( aPaM.GetPara() < ( mpImpl->mpTextEngine->mpDoc->GetNodes().size()-1) )
1283 aPaM.GetPara()++;
1284 aPaM.GetIndex() = 0;
1287 return aPaM;
1290 TextPaM TextView::ImpDelete( sal_uInt8 nMode, sal_uInt8 nDelMode )
1292 if ( mpImpl->maSelection.HasRange() ) // only delete selection
1293 return mpImpl->mpTextEngine->ImpDeleteText( mpImpl->maSelection );
1295 TextPaM aStartPaM = mpImpl->maSelection.GetStart();
1296 TextPaM aEndPaM = aStartPaM;
1297 if ( nMode == DEL_LEFT )
1299 if ( nDelMode == DELMODE_SIMPLE )
1301 aEndPaM = CursorLeft( aEndPaM, sal_uInt16(css::i18n::CharacterIteratorMode::SKIPCHARACTER) );
1303 else if ( nDelMode == DELMODE_RESTOFWORD )
1305 TextNode* pNode = mpImpl->mpTextEngine->mpDoc->GetNodes()[ aEndPaM.GetPara() ].get();
1306 css::uno::Reference < css::i18n::XBreakIterator > xBI = mpImpl->mpTextEngine->GetBreakIterator();
1307 css::i18n::Boundary aBoundary = xBI->getWordBoundary( pNode->GetText(), mpImpl->maSelection.GetEnd().GetIndex(), mpImpl->mpTextEngine->GetLocale(), css::i18n::WordType::ANYWORD_IGNOREWHITESPACES, true );
1308 if ( aBoundary.startPos == mpImpl->maSelection.GetEnd().GetIndex() )
1309 aBoundary = xBI->previousWord( pNode->GetText(), mpImpl->maSelection.GetEnd().GetIndex(), mpImpl->mpTextEngine->GetLocale(), css::i18n::WordType::ANYWORD_IGNOREWHITESPACES );
1310 // #i63506# startPos is -1 when the paragraph starts with a tab
1311 aEndPaM.GetIndex() = std::max<sal_Int32>(aBoundary.startPos, 0);
1313 else // DELMODE_RESTOFCONTENT
1315 if ( aEndPaM.GetIndex() != 0 )
1316 aEndPaM.GetIndex() = 0;
1317 else if ( aEndPaM.GetPara() )
1319 // previous paragraph
1320 aEndPaM.GetPara()--;
1321 aEndPaM.GetIndex() = 0;
1325 else
1327 if ( nDelMode == DELMODE_SIMPLE )
1329 aEndPaM = CursorRight( aEndPaM, sal_uInt16(css::i18n::CharacterIteratorMode::SKIPCELL) );
1331 else if ( nDelMode == DELMODE_RESTOFWORD )
1333 TextNode* pNode = mpImpl->mpTextEngine->mpDoc->GetNodes()[ aEndPaM.GetPara() ].get();
1334 css::uno::Reference < css::i18n::XBreakIterator > xBI = mpImpl->mpTextEngine->GetBreakIterator();
1335 css::i18n::Boundary aBoundary = xBI->nextWord( pNode->GetText(), mpImpl->maSelection.GetEnd().GetIndex(), mpImpl->mpTextEngine->GetLocale(), css::i18n::WordType::ANYWORD_IGNOREWHITESPACES );
1336 aEndPaM.GetIndex() = aBoundary.startPos;
1338 else // DELMODE_RESTOFCONTENT
1340 TextNode* pNode = mpImpl->mpTextEngine->mpDoc->GetNodes()[ aEndPaM.GetPara() ].get();
1341 if ( aEndPaM.GetIndex() < pNode->GetText().getLength() )
1342 aEndPaM.GetIndex() = pNode->GetText().getLength();
1343 else if ( aEndPaM.GetPara() < ( mpImpl->mpTextEngine->mpDoc->GetNodes().size() - 1 ) )
1345 // next paragraph
1346 aEndPaM.GetPara()++;
1347 TextNode* pNextNode = mpImpl->mpTextEngine->mpDoc->GetNodes()[ aEndPaM.GetPara() ].get();
1348 aEndPaM.GetIndex() = pNextNode->GetText().getLength();
1353 return mpImpl->mpTextEngine->ImpDeleteText( TextSelection( aStartPaM, aEndPaM ) );
1356 TextPaM TextView::CursorUp( const TextPaM& rPaM )
1358 TextPaM aPaM( rPaM );
1360 long nX;
1361 if ( mpImpl->mnTravelXPos == TRAVEL_X_DONTKNOW )
1363 nX = mpImpl->mpTextEngine->GetEditCursor( rPaM, false ).Left();
1364 mpImpl->mnTravelXPos = static_cast<sal_uInt16>(nX)+1;
1366 else
1367 nX = mpImpl->mnTravelXPos;
1369 TEParaPortion* pPPortion = mpImpl->mpTextEngine->mpTEParaPortions->GetObject( rPaM.GetPara() );
1370 std::vector<TextLine>::size_type nLine = pPPortion->GetLineNumber( rPaM.GetIndex(), false );
1371 if ( nLine ) // same paragraph
1373 aPaM.GetIndex() = mpImpl->mpTextEngine->GetCharPos( rPaM.GetPara(), nLine-1, nX );
1374 // If we need to go to the end of a line that was wrapped automatically,
1375 // the cursor ends up at the beginning of the 2nd line
1376 // Problem: Last character of an automatically wrapped line = Cursor
1377 TextLine& rLine = pPPortion->GetLines()[ nLine - 1 ];
1378 if ( aPaM.GetIndex() && ( aPaM.GetIndex() == rLine.GetEnd() ) )
1379 --aPaM.GetIndex();
1381 else if ( rPaM.GetPara() ) // previous paragraph
1383 aPaM.GetPara()--;
1384 pPPortion = mpImpl->mpTextEngine->mpTEParaPortions->GetObject( aPaM.GetPara() );
1385 std::vector<TextLine>::size_type nL = pPPortion->GetLines().size() - 1;
1386 aPaM.GetIndex() = mpImpl->mpTextEngine->GetCharPos( aPaM.GetPara(), nL, nX+1 );
1389 return aPaM;
1392 TextPaM TextView::CursorDown( const TextPaM& rPaM )
1394 TextPaM aPaM( rPaM );
1396 long nX;
1397 if ( mpImpl->mnTravelXPos == TRAVEL_X_DONTKNOW )
1399 nX = mpImpl->mpTextEngine->GetEditCursor( rPaM, false ).Left();
1400 mpImpl->mnTravelXPos = static_cast<sal_uInt16>(nX)+1;
1402 else
1403 nX = mpImpl->mnTravelXPos;
1405 TEParaPortion* pPPortion = mpImpl->mpTextEngine->mpTEParaPortions->GetObject( rPaM.GetPara() );
1406 std::vector<TextLine>::size_type nLine = pPPortion->GetLineNumber( rPaM.GetIndex(), false );
1407 if ( nLine < ( pPPortion->GetLines().size() - 1 ) )
1409 aPaM.GetIndex() = mpImpl->mpTextEngine->GetCharPos( rPaM.GetPara(), nLine+1, nX );
1411 // special case CursorUp
1412 TextLine& rLine = pPPortion->GetLines()[ nLine + 1 ];
1413 if ( ( aPaM.GetIndex() == rLine.GetEnd() ) && ( aPaM.GetIndex() > rLine.GetStart() ) && aPaM.GetIndex() < pPPortion->GetNode()->GetText().getLength() )
1414 --aPaM.GetIndex();
1416 else if ( rPaM.GetPara() < ( mpImpl->mpTextEngine->mpDoc->GetNodes().size() - 1 ) ) // next paragraph
1418 aPaM.GetPara()++;
1419 pPPortion = mpImpl->mpTextEngine->mpTEParaPortions->GetObject( aPaM.GetPara() );
1420 aPaM.GetIndex() = mpImpl->mpTextEngine->GetCharPos( aPaM.GetPara(), 0, nX+1 );
1421 TextLine& rLine = pPPortion->GetLines().front();
1422 if ( ( aPaM.GetIndex() == rLine.GetEnd() ) && ( aPaM.GetIndex() > rLine.GetStart() ) && ( pPPortion->GetLines().size() > 1 ) )
1423 --aPaM.GetIndex();
1426 return aPaM;
1429 TextPaM TextView::CursorStartOfLine( const TextPaM& rPaM )
1431 TextPaM aPaM( rPaM );
1433 TEParaPortion* pPPortion = mpImpl->mpTextEngine->mpTEParaPortions->GetObject( rPaM.GetPara() );
1434 std::vector<TextLine>::size_type nLine = pPPortion->GetLineNumber( aPaM.GetIndex(), false );
1435 TextLine& rLine = pPPortion->GetLines()[ nLine ];
1436 aPaM.GetIndex() = rLine.GetStart();
1438 return aPaM;
1441 TextPaM TextView::CursorEndOfLine( const TextPaM& rPaM )
1443 TextPaM aPaM( rPaM );
1445 TEParaPortion* pPPortion = mpImpl->mpTextEngine->mpTEParaPortions->GetObject( rPaM.GetPara() );
1446 std::vector<TextLine>::size_type nLine = pPPortion->GetLineNumber( aPaM.GetIndex(), false );
1447 TextLine& rLine = pPPortion->GetLines()[ nLine ];
1448 aPaM.GetIndex() = rLine.GetEnd();
1450 if ( rLine.GetEnd() > rLine.GetStart() ) // empty line
1452 sal_Unicode cLastChar = pPPortion->GetNode()->GetText()[ aPaM.GetIndex()-1 ];
1453 if ( ( cLastChar == ' ' ) && ( aPaM.GetIndex() != pPPortion->GetNode()->GetText().getLength() ) )
1455 // for a blank in an automatically-wrapped line it is better to stand before it,
1456 // as the user will intend to stand behind the prior word.
1457 // If there is a change, special case for Pos1 after End!
1458 --aPaM.GetIndex();
1461 return aPaM;
1464 TextPaM TextView::CursorStartOfParagraph( const TextPaM& rPaM )
1466 TextPaM aPaM( rPaM );
1467 aPaM.GetIndex() = 0;
1468 return aPaM;
1471 TextPaM TextView::CursorEndOfParagraph( const TextPaM& rPaM )
1473 TextNode* pNode = mpImpl->mpTextEngine->mpDoc->GetNodes()[ rPaM.GetPara() ].get();
1474 TextPaM aPaM( rPaM );
1475 aPaM.GetIndex() = pNode->GetText().getLength();
1476 return aPaM;
1479 TextPaM TextView::CursorStartOfDoc()
1481 TextPaM aPaM( 0, 0 );
1482 return aPaM;
1485 TextPaM TextView::CursorEndOfDoc()
1487 const sal_uInt32 nNode = static_cast<sal_uInt32>(mpImpl->mpTextEngine->mpDoc->GetNodes().size() - 1);
1488 TextNode* pNode = mpImpl->mpTextEngine->mpDoc->GetNodes()[ nNode ].get();
1489 TextPaM aPaM( nNode, pNode->GetText().getLength() );
1490 return aPaM;
1493 TextPaM TextView::PageUp( const TextPaM& rPaM )
1495 tools::Rectangle aRect = mpImpl->mpTextEngine->PaMtoEditCursor( rPaM );
1496 Point aTopLeft = aRect.TopLeft();
1497 aTopLeft.AdjustY( -(mpImpl->mpWindow->GetOutputSizePixel().Height() * 9/10) );
1498 aTopLeft.AdjustX(1 );
1499 if ( aTopLeft.Y() < 0 )
1500 aTopLeft.setY( 0 );
1502 TextPaM aPaM = mpImpl->mpTextEngine->GetPaM( aTopLeft );
1503 return aPaM;
1506 TextPaM TextView::PageDown( const TextPaM& rPaM )
1508 tools::Rectangle aRect = mpImpl->mpTextEngine->PaMtoEditCursor( rPaM );
1509 Point aBottomRight = aRect.BottomRight();
1510 aBottomRight.AdjustY(mpImpl->mpWindow->GetOutputSizePixel().Height() * 9/10 );
1511 aBottomRight.AdjustX(1 );
1512 long nHeight = mpImpl->mpTextEngine->GetTextHeight();
1513 if ( aBottomRight.Y() > nHeight )
1514 aBottomRight.setY( nHeight-1 );
1516 TextPaM aPaM = mpImpl->mpTextEngine->GetPaM( aBottomRight );
1517 return aPaM;
1520 void TextView::ImpShowCursor( bool bGotoCursor, bool bForceVisCursor, bool bSpecial )
1522 if ( mpImpl->mpTextEngine->IsFormatting() )
1523 return;
1524 if ( !mpImpl->mpTextEngine->GetUpdateMode() )
1525 return;
1526 if ( mpImpl->mpTextEngine->IsInUndo() )
1527 return;
1529 mpImpl->mpTextEngine->CheckIdleFormatter();
1530 if ( !mpImpl->mpTextEngine->IsFormatted() )
1531 mpImpl->mpTextEngine->FormatAndUpdate( this );
1533 TextPaM aPaM( mpImpl->maSelection.GetEnd() );
1534 tools::Rectangle aEditCursor = mpImpl->mpTextEngine->PaMtoEditCursor( aPaM, bSpecial );
1536 // Remember that we placed the cursor behind the last character of a line
1537 mpImpl->mbCursorAtEndOfLine = false;
1538 if( bSpecial )
1540 TEParaPortion* pParaPortion = mpImpl->mpTextEngine->mpTEParaPortions->GetObject( aPaM.GetPara() );
1541 mpImpl->mbCursorAtEndOfLine =
1542 pParaPortion->GetLineNumber( aPaM.GetIndex(), true ) != pParaPortion->GetLineNumber( aPaM.GetIndex(), false );
1545 if ( !IsInsertMode() && !mpImpl->maSelection.HasRange() )
1547 TextNode* pNode = mpImpl->mpTextEngine->mpDoc->GetNodes()[ aPaM.GetPara() ].get();
1548 if ( !pNode->GetText().isEmpty() && ( aPaM.GetIndex() < pNode->GetText().getLength() ) )
1550 // If we are behind a portion, and the next portion has other direction, we must change position...
1551 aEditCursor.SetLeft( mpImpl->mpTextEngine->GetEditCursor( aPaM, false, true ).Left() );
1552 aEditCursor.SetRight( aEditCursor.Left() );
1554 TEParaPortion* pParaPortion = mpImpl->mpTextEngine->mpTEParaPortions->GetObject( aPaM.GetPara() );
1556 sal_Int32 nTextPortionStart = 0;
1557 std::size_t nTextPortion = pParaPortion->GetTextPortions().FindPortion( aPaM.GetIndex(), nTextPortionStart, true );
1558 TETextPortion* pTextPortion = pParaPortion->GetTextPortions()[ nTextPortion ];
1559 if ( pTextPortion->GetKind() == PORTIONKIND_TAB )
1561 aEditCursor.AdjustRight(pTextPortion->GetWidth() );
1563 else
1565 TextPaM aNext = CursorRight( TextPaM( aPaM.GetPara(), aPaM.GetIndex() ), sal_uInt16(css::i18n::CharacterIteratorMode::SKIPCELL) );
1566 aEditCursor.SetRight( mpImpl->mpTextEngine->GetEditCursor( aNext, true ).Left() );
1571 Size aOutSz = mpImpl->mpWindow->GetOutputSizePixel();
1572 if ( aEditCursor.GetHeight() > aOutSz.Height() )
1573 aEditCursor.SetBottom( aEditCursor.Top() + aOutSz.Height() - 1 );
1575 aEditCursor.AdjustLeft( -1 );
1577 if ( bGotoCursor
1578 // #i81283# protect maStartDocPos against initialization problems
1579 && aOutSz.Width() && aOutSz.Height()
1582 long nVisStartY = mpImpl->maStartDocPos.Y();
1583 long nVisEndY = mpImpl->maStartDocPos.Y() + aOutSz.Height();
1584 long nVisStartX = mpImpl->maStartDocPos.X();
1585 long nVisEndX = mpImpl->maStartDocPos.X() + aOutSz.Width();
1586 long nMoreX = aOutSz.Width() / 4;
1588 Point aNewStartPos( mpImpl->maStartDocPos );
1590 if ( aEditCursor.Bottom() > nVisEndY )
1592 aNewStartPos.AdjustY( aEditCursor.Bottom() - nVisEndY);
1594 else if ( aEditCursor.Top() < nVisStartY )
1596 aNewStartPos.AdjustY( -( nVisStartY - aEditCursor.Top() ) );
1599 if ( aEditCursor.Right() >= nVisEndX )
1601 aNewStartPos.AdjustX( aEditCursor.Right() - nVisEndX );
1603 // do you want some more?
1604 aNewStartPos.AdjustX(nMoreX );
1606 else if ( aEditCursor.Left() <= nVisStartX )
1608 aNewStartPos.AdjustX( -( nVisStartX - aEditCursor.Left() ) );
1610 // do you want some more?
1611 aNewStartPos.AdjustX( -nMoreX );
1614 // X can be wrong for the 'some more' above:
1615 // sal_uLong nMaxTextWidth = mpImpl->mpTextEngine->GetMaxTextWidth();
1616 // if ( !nMaxTextWidth || ( nMaxTextWidth > 0x7FFFFFFF ) )
1617 // nMaxTextWidth = 0x7FFFFFFF;
1618 // long nMaxX = (long)nMaxTextWidth - aOutSz.Width();
1619 long nMaxX = mpImpl->mpTextEngine->CalcTextWidth() - aOutSz.Width();
1620 if ( nMaxX < 0 )
1621 nMaxX = 0;
1623 if ( aNewStartPos.X() < 0 )
1624 aNewStartPos.setX( 0 );
1625 else if ( aNewStartPos.X() > nMaxX )
1626 aNewStartPos.setX( nMaxX );
1628 // Y should not be further down than needed
1629 long nYMax = mpImpl->mpTextEngine->GetTextHeight() - aOutSz.Height();
1630 if ( nYMax < 0 )
1631 nYMax = 0;
1632 if ( aNewStartPos.Y() > nYMax )
1633 aNewStartPos.setY( nYMax );
1635 if ( aNewStartPos != mpImpl->maStartDocPos )
1636 Scroll( -(aNewStartPos.X() - mpImpl->maStartDocPos.X()), -(aNewStartPos.Y() - mpImpl->maStartDocPos.Y()) );
1639 if ( aEditCursor.Right() < aEditCursor.Left() )
1641 long n = aEditCursor.Left();
1642 aEditCursor.SetLeft( aEditCursor.Right() );
1643 aEditCursor.SetRight( n );
1646 Point aPoint( GetWindowPos( !mpImpl->mpTextEngine->IsRightToLeft() ? aEditCursor.TopLeft() : aEditCursor.TopRight() ) );
1647 mpImpl->mpCursor->SetPos( aPoint );
1648 mpImpl->mpCursor->SetSize( aEditCursor.GetSize() );
1649 if ( bForceVisCursor && mpImpl->mbCursorEnabled )
1650 mpImpl->mpCursor->Show();
1653 void TextView::SetCursorAtPoint( const Point& rPosPixel )
1655 mpImpl->mpTextEngine->CheckIdleFormatter();
1657 Point aDocPos = GetDocPos( rPosPixel );
1659 TextPaM aPaM = mpImpl->mpTextEngine->GetPaM( aDocPos );
1661 // aTmpNewSel: Diff between old and new; not the new selection
1662 TextSelection aTmpNewSel( mpImpl->maSelection.GetEnd(), aPaM );
1663 TextSelection aNewSel( mpImpl->maSelection );
1664 aNewSel.GetEnd() = aPaM;
1666 if ( !mpImpl->mpSelEngine->HasAnchor() )
1668 if ( mpImpl->maSelection.GetStart() != aPaM )
1669 mpImpl->mpTextEngine->CursorMoved( mpImpl->maSelection.GetStart().GetPara() );
1670 aNewSel.GetStart() = aPaM;
1671 ImpSetSelection( aNewSel );
1673 else
1675 ImpSetSelection( aNewSel );
1676 ShowSelection( aTmpNewSel );
1679 bool bForceCursor = !mpImpl->mpDDInfo; // && !mbInSelection
1680 ImpShowCursor( mpImpl->mbAutoScroll, bForceCursor, false );
1683 bool TextView::IsSelectionAtPoint( const Point& rPosPixel )
1685 Point aDocPos = GetDocPos( rPosPixel );
1686 TextPaM aPaM = mpImpl->mpTextEngine->GetPaM( aDocPos );
1687 // BeginDrag is only called, however, if IsSelectionAtPoint()
1688 // Problem: IsSelectionAtPoint is not called by Command()
1689 // if before MBDown returned false.
1690 return IsInSelection( aPaM );
1693 bool TextView::IsInSelection( const TextPaM& rPaM )
1695 TextSelection aSel = mpImpl->maSelection;
1696 aSel.Justify();
1698 const sal_uInt32 nStartNode = aSel.GetStart().GetPara();
1699 const sal_uInt32 nEndNode = aSel.GetEnd().GetPara();
1700 const sal_uInt32 nCurNode = rPaM.GetPara();
1702 if ( ( nCurNode > nStartNode ) && ( nCurNode < nEndNode ) )
1703 return true;
1705 if ( nStartNode == nEndNode )
1707 if ( nCurNode == nStartNode )
1708 if ( ( rPaM.GetIndex() >= aSel.GetStart().GetIndex() ) && ( rPaM.GetIndex() < aSel.GetEnd().GetIndex() ) )
1709 return true;
1711 else if ( ( nCurNode == nStartNode ) && ( rPaM.GetIndex() >= aSel.GetStart().GetIndex() ) )
1712 return true;
1713 else if ( ( nCurNode == nEndNode ) && ( rPaM.GetIndex() < aSel.GetEnd().GetIndex() ) )
1714 return true;
1716 return false;
1719 void TextView::ImpHideDDCursor()
1721 if ( mpImpl->mpDDInfo && mpImpl->mpDDInfo->mbVisCursor )
1723 mpImpl->mpDDInfo->maCursor.Hide();
1724 mpImpl->mpDDInfo->mbVisCursor = false;
1728 void TextView::ImpShowDDCursor()
1730 if ( !mpImpl->mpDDInfo->mbVisCursor )
1732 tools::Rectangle aCursor = mpImpl->mpTextEngine->PaMtoEditCursor( mpImpl->mpDDInfo->maDropPos, true );
1733 aCursor.AdjustRight( 1 );
1734 aCursor.SetPos( GetWindowPos( aCursor.TopLeft() ) );
1736 mpImpl->mpDDInfo->maCursor.SetWindow( mpImpl->mpWindow );
1737 mpImpl->mpDDInfo->maCursor.SetPos( aCursor.TopLeft() );
1738 mpImpl->mpDDInfo->maCursor.SetSize( aCursor.GetSize() );
1739 mpImpl->mpDDInfo->maCursor.Show();
1740 mpImpl->mpDDInfo->mbVisCursor = true;
1744 void TextView::SetPaintSelection( bool bPaint )
1746 if ( bPaint != mpImpl->mbPaintSelection )
1748 mpImpl->mbPaintSelection = bPaint;
1749 ShowSelection( mpImpl->maSelection );
1753 void TextView::Read( SvStream& rInput )
1755 mpImpl->mpTextEngine->Read( rInput, &mpImpl->maSelection );
1756 ShowCursor();
1759 bool TextView::ImplTruncateNewText( OUString& rNewText ) const
1761 bool bTruncated = false;
1763 const sal_Int32 nMaxLen = mpImpl->mpTextEngine->GetMaxTextLen();
1764 // 0 means unlimited
1765 if( nMaxLen != 0 )
1767 const sal_Int32 nCurLen = mpImpl->mpTextEngine->GetTextLen();
1769 const sal_Int32 nNewLen = rNewText.getLength();
1770 if ( nCurLen + nNewLen > nMaxLen )
1772 // see how much text will be replaced
1773 const sal_Int32 nSelLen = mpImpl->mpTextEngine->GetTextLen( mpImpl->maSelection );
1774 if ( nCurLen + nNewLen - nSelLen > nMaxLen )
1776 const sal_Int32 nTruncatedLen = nMaxLen - (nCurLen - nSelLen);
1777 rNewText = rNewText.copy( 0, nTruncatedLen );
1778 bTruncated = true;
1782 return bTruncated;
1785 bool TextView::ImplCheckTextLen( const OUString& rNewText )
1787 bool bOK = true;
1788 if ( mpImpl->mpTextEngine->GetMaxTextLen() )
1790 sal_Int32 n = mpImpl->mpTextEngine->GetTextLen() + rNewText.getLength();
1791 if ( n > mpImpl->mpTextEngine->GetMaxTextLen() )
1793 // calculate how much text is being deleted
1794 n -= mpImpl->mpTextEngine->GetTextLen( mpImpl->maSelection );
1795 if ( n > mpImpl->mpTextEngine->GetMaxTextLen() )
1796 bOK = false;
1799 return bOK;
1802 void TextView::dragGestureRecognized( const css::datatransfer::dnd::DragGestureEvent& rDGE )
1804 if ( mpImpl->mbClickedInSelection )
1806 SolarMutexGuard aVclGuard;
1808 SAL_WARN_IF( !mpImpl->maSelection.HasRange(), "vcl", "TextView::dragGestureRecognized: mpImpl->mbClickedInSelection, but no selection?" );
1810 mpImpl->mpDDInfo.reset(new TextDDInfo);
1811 mpImpl->mpDDInfo->mbStarterOfDD = true;
1813 TETextDataObject* pDataObj = new TETextDataObject( GetSelected() );
1815 mpImpl->mpCursor->Hide();
1817 sal_Int8 nActions = css::datatransfer::dnd::DNDConstants::ACTION_COPY;
1818 if ( !IsReadOnly() )
1819 nActions |= css::datatransfer::dnd::DNDConstants::ACTION_MOVE;
1820 rDGE.DragSource->startDrag( rDGE, nActions, 0 /*cursor*/, 0 /*image*/, pDataObj, mpImpl->mxDnDListener );
1824 void TextView::dragDropEnd( const css::datatransfer::dnd::DragSourceDropEvent& )
1826 ImpHideDDCursor();
1827 mpImpl->mpDDInfo.reset();
1830 void TextView::drop( const css::datatransfer::dnd::DropTargetDropEvent& rDTDE )
1832 SolarMutexGuard aVclGuard;
1834 if ( !mpImpl->mbReadOnly && mpImpl->mpDDInfo )
1836 ImpHideDDCursor();
1838 // Data for deleting after DROP_MOVE:
1839 TextSelection aPrevSel( mpImpl->maSelection );
1840 aPrevSel.Justify();
1841 const sal_uInt32 nPrevParaCount = mpImpl->mpTextEngine->GetParagraphCount();
1842 const sal_Int32 nPrevStartParaLen = mpImpl->mpTextEngine->GetTextLen( aPrevSel.GetStart().GetPara() );
1844 bool bStarterOfDD = false;
1845 for ( sal_uInt16 nView = mpImpl->mpTextEngine->GetViewCount(); nView && !bStarterOfDD; )
1846 bStarterOfDD = mpImpl->mpTextEngine->GetView( --nView )->mpImpl->mpDDInfo && mpImpl->mpTextEngine->GetView( nView )->mpImpl->mpDDInfo->mbStarterOfDD;
1848 HideSelection();
1849 ImpSetSelection( mpImpl->mpDDInfo->maDropPos );
1851 mpImpl->mpTextEngine->UndoActionStart();
1853 OUString aText;
1854 css::uno::Reference< css::datatransfer::XTransferable > xDataObj = rDTDE.Transferable;
1855 if ( xDataObj.is() )
1857 css::datatransfer::DataFlavor aFlavor;
1858 SotExchange::GetFormatDataFlavor( SotClipboardFormatId::STRING, aFlavor );
1859 if ( xDataObj->isDataFlavorSupported( aFlavor ) )
1861 css::uno::Any aData = xDataObj->getTransferData( aFlavor );
1862 OUString aOUString;
1863 aData >>= aOUString;
1864 aText = convertLineEnd(aOUString, LINEEND_LF);
1868 if ( !aText.isEmpty() && ( aText[ aText.getLength()-1 ] == LINE_SEP ) )
1869 aText = aText.copy(0, aText.getLength()-1);
1871 if ( ImplCheckTextLen( aText ) )
1872 ImpSetSelection( mpImpl->mpTextEngine->ImpInsertText( mpImpl->mpDDInfo->maDropPos, aText ) );
1874 if ( aPrevSel.HasRange() &&
1875 (( rDTDE.DropAction & css::datatransfer::dnd::DNDConstants::ACTION_MOVE ) || !bStarterOfDD) )
1877 // adjust selection if necessary
1878 if ( ( mpImpl->mpDDInfo->maDropPos.GetPara() < aPrevSel.GetStart().GetPara() ) ||
1879 ( ( mpImpl->mpDDInfo->maDropPos.GetPara() == aPrevSel.GetStart().GetPara() )
1880 && ( mpImpl->mpDDInfo->maDropPos.GetIndex() < aPrevSel.GetStart().GetIndex() ) ) )
1882 const sal_uInt32 nNewParasBeforeSelection =
1883 mpImpl->mpTextEngine->GetParagraphCount() - nPrevParaCount;
1885 aPrevSel.GetStart().GetPara() += nNewParasBeforeSelection;
1886 aPrevSel.GetEnd().GetPara() += nNewParasBeforeSelection;
1888 if ( mpImpl->mpDDInfo->maDropPos.GetPara() == aPrevSel.GetStart().GetPara() )
1890 const sal_Int32 nNewChars =
1891 mpImpl->mpTextEngine->GetTextLen( aPrevSel.GetStart().GetPara() ) - nPrevStartParaLen;
1893 aPrevSel.GetStart().GetIndex() += nNewChars;
1894 if ( aPrevSel.GetStart().GetPara() == aPrevSel.GetEnd().GetPara() )
1895 aPrevSel.GetEnd().GetIndex() += nNewChars;
1898 else
1900 // adjust current selection
1901 TextPaM aPaM = mpImpl->maSelection.GetStart();
1902 aPaM.GetPara() -= ( aPrevSel.GetEnd().GetPara() - aPrevSel.GetStart().GetPara() );
1903 if ( aPrevSel.GetEnd().GetPara() == mpImpl->mpDDInfo->maDropPos.GetPara() )
1905 aPaM.GetIndex() -= aPrevSel.GetEnd().GetIndex();
1906 if ( aPrevSel.GetStart().GetPara() == mpImpl->mpDDInfo->maDropPos.GetPara() )
1907 aPaM.GetIndex() += aPrevSel.GetStart().GetIndex();
1909 ImpSetSelection( aPaM );
1912 mpImpl->mpTextEngine->ImpDeleteText( aPrevSel );
1915 mpImpl->mpTextEngine->UndoActionEnd();
1917 mpImpl->mpDDInfo.reset();
1919 mpImpl->mpTextEngine->FormatAndUpdate( this );
1921 mpImpl->mpTextEngine->Broadcast( TextHint( SfxHintId::TextModified ) );
1923 rDTDE.Context->dropComplete( false/*bChanges*/ );
1926 void TextView::dragEnter( const css::datatransfer::dnd::DropTargetDragEnterEvent& )
1930 void TextView::dragExit( const css::datatransfer::dnd::DropTargetEvent& )
1932 SolarMutexGuard aVclGuard;
1933 ImpHideDDCursor();
1936 void TextView::dragOver( const css::datatransfer::dnd::DropTargetDragEvent& rDTDE )
1938 SolarMutexGuard aVclGuard;
1940 if (!mpImpl->mpDDInfo)
1941 mpImpl->mpDDInfo.reset(new TextDDInfo);
1943 TextPaM aPrevDropPos = mpImpl->mpDDInfo->maDropPos;
1944 Point aMousePos( rDTDE.LocationX, rDTDE.LocationY );
1945 Point aDocPos = GetDocPos( aMousePos );
1946 mpImpl->mpDDInfo->maDropPos = mpImpl->mpTextEngine->GetPaM( aDocPos );
1948 // Don't drop in selection or in read only engine
1949 if ( IsReadOnly() || IsInSelection( mpImpl->mpDDInfo->maDropPos ))
1951 ImpHideDDCursor();
1952 rDTDE.Context->rejectDrag();
1954 else
1956 // delete old Cursor
1957 if ( !mpImpl->mpDDInfo->mbVisCursor || ( aPrevDropPos != mpImpl->mpDDInfo->maDropPos ) )
1959 ImpHideDDCursor();
1960 ImpShowDDCursor();
1962 rDTDE.Context->acceptDrag( rDTDE.DropAction );
1966 Point TextView::ImpGetOutputStartPos( const Point& rStartDocPos ) const
1968 Point aStartPos( -rStartDocPos.X(), -rStartDocPos.Y() );
1969 if ( mpImpl->mpTextEngine->IsRightToLeft() )
1971 Size aSz = mpImpl->mpWindow->GetOutputSizePixel();
1972 aStartPos.setX( rStartDocPos.X() + aSz.Width() - 1 ); // -1: Start is 0
1974 return aStartPos;
1977 Point TextView::GetDocPos( const Point& rWindowPos ) const
1979 // Window Position => Document Position
1981 Point aPoint;
1983 aPoint.setY( rWindowPos.Y() + mpImpl->maStartDocPos.Y() );
1985 if ( !mpImpl->mpTextEngine->IsRightToLeft() )
1987 aPoint.setX( rWindowPos.X() + mpImpl->maStartDocPos.X() );
1989 else
1991 Size aSz = mpImpl->mpWindow->GetOutputSizePixel();
1992 aPoint.setX( ( aSz.Width() - 1 ) - rWindowPos.X() + mpImpl->maStartDocPos.X() );
1995 return aPoint;
1998 Point TextView::GetWindowPos( const Point& rDocPos ) const
2000 // Document Position => Window Position
2002 Point aPoint;
2004 aPoint.setY( rDocPos.Y() - mpImpl->maStartDocPos.Y() );
2006 if ( !mpImpl->mpTextEngine->IsRightToLeft() )
2008 aPoint.setX( rDocPos.X() - mpImpl->maStartDocPos.X() );
2010 else
2012 Size aSz = mpImpl->mpWindow->GetOutputSizePixel();
2013 aPoint.setX( ( aSz.Width() - 1 ) - ( rDocPos.X() - mpImpl->maStartDocPos.X() ) );
2016 return aPoint;
2019 sal_Int32 TextView::GetLineNumberOfCursorInSelection() const
2021 // PROGRESS
2022 sal_Int32 nLineNo = -1;
2023 if( mpImpl->mbCursorEnabled )
2025 TextPaM aPaM = GetSelection().GetEnd();
2026 TEParaPortion* pPPortion = mpImpl->mpTextEngine->mpTEParaPortions->GetObject( aPaM.GetPara() );
2027 nLineNo = pPPortion->GetLineNumber( aPaM.GetIndex(), false );
2028 //TODO: std::vector<TextLine>::size_type -> sal_Int32!
2029 if( mpImpl->mbCursorAtEndOfLine )
2030 --nLineNo;
2032 return nLineNo;
2035 // (+) class TextSelFunctionSet
2037 TextSelFunctionSet::TextSelFunctionSet( TextView* pView )
2039 mpView = pView;
2042 void TextSelFunctionSet::BeginDrag()
2046 void TextSelFunctionSet::CreateAnchor()
2048 // TextSelection aSel( mpView->GetSelection() );
2049 // aSel.GetStart() = aSel.GetEnd();
2050 // mpView->SetSelection( aSel );
2052 // may not be followed by ShowCursor
2053 mpView->HideSelection();
2054 mpView->ImpSetSelection( mpView->mpImpl->maSelection.GetEnd() );
2057 void TextSelFunctionSet::SetCursorAtPoint( const Point& rPointPixel, bool )
2059 mpView->SetCursorAtPoint( rPointPixel );
2062 bool TextSelFunctionSet::IsSelectionAtPoint( const Point& rPointPixel )
2064 return mpView->IsSelectionAtPoint( rPointPixel );
2067 void TextSelFunctionSet::DeselectAll()
2069 CreateAnchor();
2072 void TextSelFunctionSet::DeselectAtPoint( const Point& )
2074 // only for multiple selection
2077 void TextSelFunctionSet::DestroyAnchor()
2079 // only for multiple selection
2081 TextEngine* TextView::GetTextEngine() const
2082 { return mpImpl->mpTextEngine; }
2083 vcl::Window* TextView::GetWindow() const
2084 { return mpImpl->mpWindow; }
2085 void TextView::EnableCursor( bool bEnable )
2086 { mpImpl->mbCursorEnabled = bEnable; }
2087 bool TextView::IsCursorEnabled() const
2088 { return mpImpl->mbCursorEnabled; }
2089 void TextView::SetStartDocPos( const Point& rPos )
2090 { mpImpl->maStartDocPos = rPos; }
2091 const Point& TextView::GetStartDocPos() const
2092 { return mpImpl->maStartDocPos; }
2093 void TextView::SetAutoIndentMode( bool bAutoIndent )
2094 { mpImpl->mbAutoIndent = bAutoIndent; }
2095 bool TextView::IsReadOnly() const
2096 { return mpImpl->mbReadOnly; }
2097 void TextView::SetAutoScroll( bool bAutoScroll )
2098 { mpImpl->mbAutoScroll = bAutoScroll; }
2099 bool TextView::IsAutoScroll() const
2100 { return mpImpl->mbAutoScroll; }
2101 bool TextView::HasSelection() const
2102 { return mpImpl->maSelection.HasRange(); }
2103 bool TextView::IsInsertMode() const
2104 { return mpImpl->mbInsertMode; }
2106 void TextView::MatchGroup()
2108 TextSelection aTmpSel( GetSelection() );
2109 aTmpSel.Justify();
2110 if ( ( aTmpSel.GetStart().GetPara() != aTmpSel.GetEnd().GetPara() ) ||
2111 ( ( aTmpSel.GetEnd().GetIndex() - aTmpSel.GetStart().GetIndex() ) > 1 ) )
2113 return;
2116 TextSelection aMatchSel = static_cast<ExtTextEngine*>(GetTextEngine())->MatchGroup( aTmpSel.GetStart() );
2117 if ( aMatchSel.HasRange() )
2118 SetSelection( aMatchSel );
2121 void TextView::CenterPaM( const TextPaM& rPaM )
2123 // Get textview size and the corresponding y-coordinates
2124 Size aOutSz = mpImpl->mpWindow->GetOutputSizePixel();
2125 long nVisStartY = mpImpl->maStartDocPos.Y();
2126 long nVisEndY = mpImpl->maStartDocPos.Y() + aOutSz.Height();
2128 // Retrieve the coordinates of the PaM
2129 tools::Rectangle aRect = mpImpl->mpTextEngine->PaMtoEditCursor(rPaM);
2131 // Recalculate the offset of the center y-coordinates and scroll
2132 Scroll(0, (nVisStartY + nVisEndY) / 2 - aRect.TopLeft().getY());
2135 bool TextView::Search( const i18nutil::SearchOptions& rSearchOptions, bool bForward )
2137 bool bFound = false;
2138 TextSelection aSel( GetSelection() );
2139 if ( static_cast<ExtTextEngine*>(GetTextEngine())->Search( aSel, rSearchOptions, bForward ) )
2141 bFound = true;
2142 // First add the beginning of the word to the selection,
2143 // so that the whole word is in the visible region.
2144 SetSelection( aSel.GetStart() );
2145 ShowCursor( true, false );
2147 else
2149 aSel = GetSelection().GetEnd();
2152 SetSelection( aSel );
2153 // tdf#49482: Move the start of the selection to the center of the textview
2154 if (bFound)
2156 CenterPaM( aSel.GetStart() );
2158 ShowCursor();
2160 return bFound;
2163 sal_uInt16 TextView::Replace( const i18nutil::SearchOptions& rSearchOptions, bool bAll, bool bForward )
2165 sal_uInt16 nFound = 0;
2167 if ( !bAll )
2169 if ( GetSelection().HasRange() )
2171 InsertText( rSearchOptions.replaceString );
2172 nFound = 1;
2173 Search( rSearchOptions, bForward ); // right away to the next
2175 else
2177 if( Search( rSearchOptions, bForward ) )
2178 nFound = 1;
2181 else
2183 // the writer replaces all, from beginning to end
2185 ExtTextEngine* pTextEngine = static_cast<ExtTextEngine*>(GetTextEngine());
2187 // HideSelection();
2188 TextSelection aSel;
2190 bool bSearchInSelection = (0 != (rSearchOptions.searchFlag & css::util::SearchFlags::REG_NOT_BEGINOFLINE) );
2191 if ( bSearchInSelection )
2193 aSel = GetSelection();
2194 aSel.Justify();
2197 TextSelection aSearchSel( aSel );
2199 bool bFound = pTextEngine->Search( aSel, rSearchOptions );
2200 if ( bFound )
2201 pTextEngine->UndoActionStart();
2202 while ( bFound )
2204 nFound++;
2206 TextPaM aNewStart = pTextEngine->ImpInsertText( aSel, rSearchOptions.replaceString );
2207 aSel = aSearchSel;
2208 aSel.GetStart() = aNewStart;
2209 bFound = pTextEngine->Search( aSel, rSearchOptions );
2211 if ( nFound )
2213 SetSelection( aSel.GetStart() );
2214 pTextEngine->FormatAndUpdate( this );
2215 pTextEngine->UndoActionEnd();
2218 return nFound;
2221 bool TextView::ImpIndentBlock( bool bRight )
2223 bool bDone = false;
2225 TextSelection aSel = GetSelection();
2226 aSel.Justify();
2228 HideSelection();
2229 GetTextEngine()->UndoActionStart();
2231 const sal_uInt32 nStartPara = aSel.GetStart().GetPara();
2232 sal_uInt32 nEndPara = aSel.GetEnd().GetPara();
2233 if ( aSel.HasRange() && !aSel.GetEnd().GetIndex() )
2235 nEndPara--; // do not indent
2238 for ( sal_uInt32 nPara = nStartPara; nPara <= nEndPara; ++nPara )
2240 if ( bRight )
2242 // add tabs
2243 GetTextEngine()->ImpInsertText( TextPaM( nPara, 0 ), '\t' );
2244 bDone = true;
2246 else
2248 // remove Tabs/Blanks
2249 OUString aText = GetTextEngine()->GetText( nPara );
2250 if ( !aText.isEmpty() && (
2251 ( aText[ 0 ] == '\t' ) ||
2252 ( aText[ 0 ] == ' ' ) ) )
2254 GetTextEngine()->ImpDeleteText( TextSelection( TextPaM( nPara, 0 ), TextPaM( nPara, 1 ) ) );
2255 bDone = true;
2260 GetTextEngine()->UndoActionEnd();
2262 bool bRange = aSel.HasRange();
2263 if ( bRight )
2265 ++aSel.GetStart().GetIndex();
2266 if ( bRange && ( aSel.GetEnd().GetPara() == nEndPara ) )
2267 ++aSel.GetEnd().GetIndex();
2269 else
2271 if ( aSel.GetStart().GetIndex() )
2272 --aSel.GetStart().GetIndex();
2273 if ( bRange && aSel.GetEnd().GetIndex() )
2274 --aSel.GetEnd().GetIndex();
2277 ImpSetSelection( aSel );
2278 GetTextEngine()->FormatAndUpdate( this );
2280 return bDone;
2283 bool TextView::IndentBlock()
2285 return ImpIndentBlock( true );
2288 bool TextView::UnindentBlock()
2290 return ImpIndentBlock( false );
2294 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */