Coverage Report

Created: 2026-09-28 10:59

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