Coverage Report

Created: 2026-10-14 09:33

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libreoffice/vcl/source/accessibility/vclxaccessibleedit.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 <accessibility/vclxaccessibleedit.hxx>
21
22
#include <com/sun/star/accessibility/AccessibleStateType.hpp>
23
#include <com/sun/star/accessibility/AccessibleEventId.hpp>
24
#include <com/sun/star/accessibility/AccessibleRole.hpp>
25
#include <com/sun/star/datatransfer/clipboard/XClipboard.hpp>
26
#include <com/sun/star/accessibility/AccessibleTextType.hpp>
27
#include <com/sun/star/lang/IndexOutOfBoundsException.hpp>
28
#include <comphelper/accessiblecontexthelper.hxx>
29
#include <comphelper/string.hxx>
30
#include <vcl/svapp.hxx>
31
#include <vcl/window.hxx>
32
#include <vcl/mnemonic.hxx>
33
#include <vcl/settings.hxx>
34
#include <vcl/toolkit/vclmedit.hxx>
35
#include <vcl/textdata.hxx>
36
#include <vcl/txtattr.hxx>
37
#include <vcl/unohelp.hxx>
38
#include <vcl/vclevent.hxx>
39
#include <vcl/xtextedt.hxx>
40
#include <sot/exchange.hxx>
41
#include <sot/formats.hxx>
42
43
#include <algorithm>
44
45
using namespace ::com::sun::star;
46
using namespace ::com::sun::star::uno;
47
using namespace ::com::sun::star::lang;
48
using namespace ::com::sun::star::beans;
49
using namespace ::com::sun::star::accessibility;
50
using namespace ::comphelper;
51
52
53
// VCLXAccessibleEdit
54
55
56
VCLXAccessibleEdit::VCLXAccessibleEdit(Edit* pEdit)
57
0
    :ImplInheritanceHelper(pEdit)
58
0
{
59
0
    m_nCaretPosition = getCaretPosition();
60
0
}
61
62
63
void VCLXAccessibleEdit::ProcessWindowEvent( const VclWindowEvent& rVclWindowEvent )
64
0
{
65
0
    switch ( rVclWindowEvent.GetId() )
66
0
    {
67
0
        case VclEventId::EditModify:
68
0
        {
69
0
            SetText( implGetText() );
70
0
        }
71
0
        break;
72
0
        case VclEventId::EditCaretChanged:
73
0
        {
74
0
            sal_Int32 nOldCaretPosition = m_nCaretPosition;
75
0
            m_nCaretPosition = getCaretPosition();
76
77
0
            VclPtr<vcl::Window> pWindow = GetWindow();
78
0
            if (pWindow && pWindow->HasChildPathFocus())
79
0
            {
80
0
                if (m_nCaretPosition != nOldCaretPosition)
81
0
                {
82
0
                    Any aOldValue, aNewValue;
83
0
                    aOldValue <<= nOldCaretPosition;
84
0
                    aNewValue <<= m_nCaretPosition;
85
0
                    NotifyAccessibleEvent( AccessibleEventId::CARET_CHANGED, aOldValue, aNewValue );
86
0
                }
87
0
            }
88
0
        }
89
0
        break;
90
0
        case VclEventId::EditSelectionChanged:
91
0
        {
92
0
            VclPtr<vcl::Window> pWindow = GetWindow();
93
0
            if (pWindow && pWindow->HasChildPathFocus())
94
0
            {
95
0
                NotifyAccessibleEvent( AccessibleEventId::TEXT_SELECTION_CHANGED, Any(), Any() );
96
0
            }
97
0
        }
98
0
        break;
99
0
        default:
100
0
            VCLXAccessibleTextComponent::ProcessWindowEvent( rVclWindowEvent );
101
0
   }
102
0
}
103
104
105
void VCLXAccessibleEdit::FillAccessibleStateSet( sal_Int64& rStateSet )
106
0
{
107
0
    VCLXAccessibleTextComponent::FillAccessibleStateSet( rStateSet );
108
109
0
    vcl::Window* pWindow = GetWindow();
110
0
    if (pWindow)
111
0
    {
112
0
        rStateSet |= AccessibleStateType::FOCUSABLE;
113
114
0
        if (pWindow->GetType() == WindowType::MULTILINEEDIT)
115
0
            rStateSet |= AccessibleStateType::MULTI_LINE;
116
0
        else
117
0
            rStateSet |= AccessibleStateType::SINGLE_LINE;
118
119
0
        if (isEditable())
120
0
            rStateSet |= AccessibleStateType::EDITABLE;
121
0
    }
122
0
}
123
124
125
// OCommonAccessibleText
126
127
128
OUString VCLXAccessibleEdit::implGetText()
129
0
{
130
0
    OUString aText;
131
132
0
    VclPtr< Edit > pEdit = GetAs< Edit >();
133
0
    if ( pEdit )
134
0
    {
135
0
        aText = removeMnemonicFromString( pEdit->GetText() );
136
137
0
        if ( implGetAccessibleRole() == AccessibleRole::PASSWORD_TEXT )
138
0
        {
139
0
            sal_Unicode cEchoChar = pEdit->GetEchoChar();
140
0
            if ( !cEchoChar )
141
0
                cEchoChar = '*';
142
0
            aText = OUString::Concat(RepeatedUChar(cEchoChar, aText.getLength()));
143
0
        }
144
0
    }
145
146
0
    return aText;
147
0
}
148
149
150
void VCLXAccessibleEdit::implGetSelection( sal_Int32& nStartIndex, sal_Int32& nEndIndex )
151
0
{
152
0
    Selection aSelection;
153
0
    VclPtr<Edit> pEdit = GetAs<Edit>();
154
0
    if (pEdit)
155
0
        aSelection = pEdit->GetSelection();
156
157
0
    nStartIndex = aSelection.Min();
158
0
    nEndIndex = aSelection.Max();
159
0
}
160
161
// VCLXAccessibleTextComponent
162
bool VCLXAccessibleEdit::PreferFullTextInTextChangedEvent()
163
0
{
164
    // for a combobox subedit, the Orca screen reader announces the new/added text
165
    // so always send the whole old and new text and not just
166
    // the changed characters, so the whole entry text gets announced
167
0
    return isComboBoxChild();
168
0
}
169
170
// XAccessibleContext
171
172
sal_Int64 VCLXAccessibleEdit::getAccessibleChildCount()
173
0
{
174
0
    OExternalLockGuard aGuard( this );
175
176
0
    return 0;
177
0
}
178
179
180
Reference< XAccessible > VCLXAccessibleEdit::getAccessibleChild( sal_Int64 )
181
0
{
182
0
    throw IndexOutOfBoundsException();
183
0
}
184
185
OUString VCLXAccessibleEdit::getAccessibleName()
186
0
{
187
0
    OExternalLockGuard aGuard(this);
188
189
    // for combobox edit, return name of the parent
190
0
    if (isComboBoxChild())
191
0
        return getAccessibleParent()->getAccessibleContext()->getAccessibleName();
192
193
0
    return VCLXAccessibleTextComponent::getAccessibleName();
194
0
}
195
196
sal_Int16 VCLXAccessibleEdit::getAccessibleRole(  )
197
0
{
198
0
    OExternalLockGuard aGuard( this );
199
200
0
    return implGetAccessibleRole();
201
0
}
202
203
sal_Int16 VCLXAccessibleEdit::implGetAccessibleRole(  )
204
0
{
205
0
    sal_Int16 nRole;
206
0
    VclPtr< Edit > pEdit = GetAs< Edit >();
207
0
    if ( pEdit && ( pEdit->IsPassword() || pEdit->GetEchoChar() ) )
208
0
        nRole = AccessibleRole::PASSWORD_TEXT;
209
0
    else
210
0
        nRole = AccessibleRole::TEXT;
211
212
0
    return nRole;
213
0
}
214
215
216
// XAccessibleAction
217
218
219
sal_Int32 VCLXAccessibleEdit::getAccessibleActionCount( )
220
0
{
221
0
    OExternalLockGuard aGuard( this );
222
223
    // There is one action: activate
224
0
    return 1;
225
0
}
226
227
228
sal_Bool VCLXAccessibleEdit::doAccessibleAction ( sal_Int32 nIndex )
229
0
{
230
0
    OExternalLockGuard aGuard( this );
231
232
0
    if ( nIndex != 0 )
233
0
        throw IndexOutOfBoundsException();
234
235
0
    bool bDoAction = false;
236
0
    VclPtr<vcl::Window> pWindow = GetWindow();
237
0
    if ( pWindow )
238
0
    {
239
0
        pWindow->GrabFocus();
240
0
        bDoAction = true;
241
0
    }
242
243
0
    return bDoAction;
244
0
}
245
246
247
OUString VCLXAccessibleEdit::getAccessibleActionDescription ( sal_Int32 nIndex )
248
0
{
249
0
    OExternalLockGuard aGuard( this );
250
251
0
    if ( nIndex != 0)
252
0
        throw IndexOutOfBoundsException();
253
254
0
    return u"activate"_ustr;
255
0
}
256
257
258
Reference< XAccessibleKeyBinding > VCLXAccessibleEdit::getAccessibleActionKeyBinding( sal_Int32 nIndex )
259
0
{
260
0
    OExternalLockGuard aGuard( this );
261
262
0
    if ( nIndex != 0 )
263
0
        throw IndexOutOfBoundsException();
264
265
0
    return Reference< XAccessibleKeyBinding >();
266
0
}
267
268
269
// XAccessibleText
270
271
272
sal_Int32 VCLXAccessibleEdit::getCaretPosition(  )
273
0
{
274
0
    return getSelectionEnd();
275
0
}
276
277
278
sal_Bool VCLXAccessibleEdit::setCaretPosition( sal_Int32 nIndex )
279
0
{
280
0
    return setSelection( nIndex, nIndex );
281
0
}
282
283
284
sal_Unicode VCLXAccessibleEdit::getCharacter( sal_Int32 nIndex )
285
0
{
286
0
    return VCLXAccessibleTextComponent::getCharacter( nIndex );
287
0
}
288
289
290
Sequence< PropertyValue > VCLXAccessibleEdit::getCharacterAttributes( sal_Int32 nIndex, const Sequence< OUString >& aRequestedAttributes )
291
0
{
292
0
    OExternalLockGuard aGuard( this );
293
0
    Sequence< PropertyValue > aProperties = VCLXAccessibleTextComponent::getCharacterAttributes( nIndex, aRequestedAttributes );
294
0
    auto aNonConstRange = asNonConstRange(aProperties);
295
296
    // Handle multiline edit character properties
297
0
    VclPtr<VclMultiLineEdit> pMultiLineEdit = GetAsDynamic< VclMultiLineEdit >();
298
0
    if ( pMultiLineEdit )
299
0
    {
300
0
        ExtTextEngine* pTextEngine = pMultiLineEdit->GetTextEngine();
301
0
        TextPaM aCursor( 0, nIndex );
302
0
        const TextAttribFontColor* pFontColor = static_cast<const TextAttribFontColor* >(pTextEngine->FindAttrib( aCursor, TEXTATTR_FONTCOLOR ));
303
0
        if ( pFontColor )
304
0
        {
305
0
            for (PropertyValue& aValue : aNonConstRange )
306
0
            {
307
0
                if (aValue.Name == "CharColor")
308
0
                {
309
0
                    aValue.Value <<= pFontColor->GetColor().GetRGBColor();
310
0
                    break;
311
0
                }
312
0
            }
313
0
        }
314
0
    }
315
316
    // Set default character color if it is not set yet to a valid value
317
0
    for (PropertyValue& aValue : aNonConstRange )
318
0
    {
319
0
        if (aValue.Name == "CharColor")
320
0
        {
321
0
            if ( aValue.Value == sal_Int32(-1) )
322
0
            {
323
0
                OutputDevice* pDev = Application::GetDefaultDevice();
324
0
                if ( pDev )
325
0
                {
326
0
                    aValue.Value <<= pDev->GetSettings().GetStyleSettings().GetFieldTextColor();
327
0
                }
328
0
            }
329
0
            break;
330
0
        }
331
0
    }
332
333
0
    return aProperties;
334
0
}
335
336
337
awt::Rectangle VCLXAccessibleEdit::getCharacterBounds( sal_Int32 nIndex )
338
0
{
339
0
    OExternalLockGuard aGuard( this );
340
341
0
    awt::Rectangle aBounds( 0, 0, 0, 0 );
342
0
    sal_Int32 nLength = implGetText().getLength();
343
344
0
    if ( !implIsValidRange( nIndex, nIndex, nLength ) )
345
0
        throw IndexOutOfBoundsException();
346
347
0
    VclPtr< Control > pControl = GetAs< Control >();
348
0
    if ( pControl )
349
0
    {
350
0
        if ( nIndex == nLength )
351
0
        {
352
            // #108914# calculate virtual bounding rectangle
353
0
            for ( sal_Int32 i = 0; i < nLength; ++i )
354
0
            {
355
0
                tools::Rectangle aRect = pControl->GetCharacterBounds( i );
356
0
                sal_Int32 nHeight = aRect.GetHeight();
357
0
                if ( aBounds.Height < nHeight )
358
0
                {
359
0
                    aBounds.Y = aRect.Top();
360
0
                    aBounds.Height = nHeight;
361
0
                }
362
0
                if ( i == nLength - 1 )
363
0
                {
364
0
                    aBounds.X = aRect.Right() + 1;
365
0
                    aBounds.Width = 1;
366
0
                }
367
0
            }
368
0
        }
369
0
        else
370
0
        {
371
0
            aBounds = vcl::unohelper::ConvertToAWTRect(pControl->GetCharacterBounds(nIndex));
372
0
        }
373
0
    }
374
375
0
    return aBounds;
376
0
}
377
378
379
sal_Int32 VCLXAccessibleEdit::getCharacterCount(  )
380
0
{
381
0
    return VCLXAccessibleTextComponent::getCharacterCount();
382
0
}
383
384
385
sal_Int32 VCLXAccessibleEdit::getIndexAtPoint( const awt::Point& aPoint )
386
0
{
387
0
    return VCLXAccessibleTextComponent::getIndexAtPoint( aPoint );
388
0
}
389
390
391
OUString VCLXAccessibleEdit::getSelectedText(  )
392
0
{
393
0
    return VCLXAccessibleTextComponent::getSelectedText();
394
0
}
395
396
397
sal_Int32 VCLXAccessibleEdit::getSelectionStart(  )
398
0
{
399
0
    return VCLXAccessibleTextComponent::getSelectionStart();
400
0
}
401
402
403
sal_Int32 VCLXAccessibleEdit::getSelectionEnd(  )
404
0
{
405
0
    return VCLXAccessibleTextComponent::getSelectionEnd();
406
0
}
407
408
409
sal_Bool VCLXAccessibleEdit::setSelection( sal_Int32 nStartIndex, sal_Int32 nEndIndex )
410
0
{
411
0
    OExternalLockGuard aGuard( this );
412
413
0
    bool bReturn = false;
414
0
    OUString sText( implGetText() );
415
416
0
    if ( !implIsValidRange( nStartIndex, nEndIndex, sText.getLength() ) )
417
0
        throw IndexOutOfBoundsException();
418
419
0
    VclPtr< Edit > pEdit = GetAs< Edit >();
420
0
    if (pEdit && pEdit->IsEnabled())
421
0
    {
422
0
        pEdit->SetSelection(Selection(nStartIndex, nEndIndex));
423
0
        bReturn = true;
424
0
    }
425
426
0
    return bReturn;
427
0
}
428
429
430
OUString VCLXAccessibleEdit::getText(  )
431
0
{
432
0
    return VCLXAccessibleTextComponent::getText();
433
0
}
434
435
436
OUString VCLXAccessibleEdit::getTextRange( sal_Int32 nStartIndex, sal_Int32 nEndIndex )
437
0
{
438
0
    return VCLXAccessibleTextComponent::getTextRange( nStartIndex, nEndIndex );
439
0
}
440
441
442
css::accessibility::TextSegment VCLXAccessibleEdit::getTextAtIndex( sal_Int32 nIndex, sal_Int16 aTextType )
443
0
{
444
0
    OExternalLockGuard aGuard( this );
445
    // Override general text component behavior: MultiLineEdit can have more text portions
446
0
    if ( aTextType == AccessibleTextType::ATTRIBUTE_RUN )
447
0
    {
448
0
        VclPtr<VclMultiLineEdit> pMultiLineEdit = GetAsDynamic< VclMultiLineEdit >();
449
0
        if ( pMultiLineEdit )
450
0
        {
451
0
            ExtTextEngine* pTextEngine = pMultiLineEdit->GetTextEngine();
452
0
            TextPaM aCursor( 0, nIndex );
453
0
            TextSegment aResult;
454
0
            pTextEngine->GetTextPortionRange( aCursor, aResult.SegmentStart, aResult.SegmentEnd );
455
0
            return aResult;
456
0
        }
457
0
    }
458
459
0
    return VCLXAccessibleTextComponent::getTextAtIndex( nIndex, aTextType );
460
0
}
461
462
463
css::accessibility::TextSegment VCLXAccessibleEdit::getTextBeforeIndex( sal_Int32 nIndex, sal_Int16 aTextType )
464
0
{
465
0
    return VCLXAccessibleTextComponent::getTextBeforeIndex( nIndex, aTextType );
466
0
}
467
468
469
css::accessibility::TextSegment VCLXAccessibleEdit::getTextBehindIndex( sal_Int32 nIndex, sal_Int16 aTextType )
470
0
{
471
0
    return VCLXAccessibleTextComponent::getTextBehindIndex( nIndex, aTextType );
472
0
}
473
474
475
sal_Bool VCLXAccessibleEdit::copyText( sal_Int32 nStartIndex, sal_Int32 nEndIndex )
476
0
{
477
0
    return VCLXAccessibleTextComponent::copyText( nStartIndex, nEndIndex );
478
0
}
479
480
sal_Bool VCLXAccessibleEdit::scrollSubstringTo( sal_Int32, sal_Int32, AccessibleScrollType )
481
0
{
482
0
    return false;
483
0
}
484
485
// XAccessibleEditableText
486
487
488
sal_Bool VCLXAccessibleEdit::cutText( sal_Int32 nStartIndex, sal_Int32 nEndIndex )
489
0
{
490
0
    return copyText( nStartIndex, nEndIndex ) && deleteText( nStartIndex, nEndIndex );
491
0
}
492
493
494
sal_Bool VCLXAccessibleEdit::pasteText( sal_Int32 nIndex )
495
0
{
496
0
    OExternalLockGuard aGuard( this );
497
498
0
    bool bReturn = false;
499
500
0
    if ( GetWindow() )
501
0
    {
502
0
        Reference< datatransfer::clipboard::XClipboard > xClipboard = GetWindow()->GetClipboard();
503
0
        if ( xClipboard.is() )
504
0
        {
505
0
            Reference< datatransfer::XTransferable > xDataObj;
506
0
            {
507
0
                SolarMutexReleaser aReleaser;
508
0
                xDataObj = xClipboard->getContents();
509
0
            }
510
0
            if ( xDataObj.is() )
511
0
            {
512
0
                datatransfer::DataFlavor aFlavor;
513
0
                SotExchange::GetFormatDataFlavor( SotClipboardFormatId::STRING, aFlavor );
514
0
                if ( xDataObj->isDataFlavorSupported( aFlavor ) )
515
0
                {
516
0
                    Any aData = xDataObj->getTransferData( aFlavor );
517
0
                    OUString sText;
518
0
                    aData >>= sText;
519
0
                    bReturn = replaceText( nIndex, nIndex, sText );
520
0
                }
521
0
            }
522
0
        }
523
0
    }
524
525
0
    return bReturn;
526
0
}
527
528
529
sal_Bool VCLXAccessibleEdit::deleteText( sal_Int32 nStartIndex, sal_Int32 nEndIndex )
530
0
{
531
0
    return replaceText( nStartIndex, nEndIndex, OUString() );
532
0
}
533
534
535
sal_Bool VCLXAccessibleEdit::insertText( const OUString& sText, sal_Int32 nIndex )
536
0
{
537
0
    return replaceText( nIndex, nIndex, sText );
538
0
}
539
540
541
sal_Bool VCLXAccessibleEdit::replaceText( sal_Int32 nStartIndex, sal_Int32 nEndIndex, const OUString& sReplacement )
542
0
{
543
0
    OExternalLockGuard aGuard( this );
544
545
0
    bool bReturn = false;
546
0
    OUString sText( implGetText() );
547
548
0
    if ( !implIsValidRange( nStartIndex, nEndIndex, sText.getLength() ) )
549
0
        throw IndexOutOfBoundsException();
550
551
0
    sal_Int32 nMinIndex = std::min( nStartIndex, nEndIndex );
552
0
    sal_Int32 nMaxIndex = std::max( nStartIndex, nEndIndex );
553
554
555
0
    if (isEditable())
556
0
    {
557
0
        VclPtr<Edit> pEdit = GetAs<Edit>();
558
0
        assert(pEdit);
559
0
        pEdit->SetText(sText.replaceAt(nMinIndex, nMaxIndex - nMinIndex, sReplacement));
560
0
        sal_Int32 nIndex = nMinIndex + sReplacement.getLength();
561
0
        setSelection( nIndex, nIndex );
562
0
        bReturn = true;
563
0
    }
564
565
0
    return bReturn;
566
0
}
567
568
569
sal_Bool VCLXAccessibleEdit::setAttributes( sal_Int32 nStartIndex, sal_Int32 nEndIndex, const Sequence<PropertyValue>& )
570
0
{
571
0
    OExternalLockGuard aGuard( this );
572
573
0
    if ( !implIsValidRange( nStartIndex, nEndIndex, implGetText().getLength() ) )
574
0
        throw IndexOutOfBoundsException();
575
576
0
    return false;   // attributes cannot be set for an edit
577
0
}
578
579
580
sal_Bool VCLXAccessibleEdit::setText( const OUString& sText )
581
0
{
582
0
    OExternalLockGuard aGuard( this );
583
584
0
    bool bReturn = false;
585
586
0
    if (isEditable())
587
0
    {
588
0
        VclPtr<Edit> pEdit = GetAs<Edit>();
589
0
        assert(pEdit);
590
0
        pEdit->SetText(sText);
591
0
        sal_Int32 nSize = sText.getLength();
592
0
        pEdit->SetSelection(Selection(nSize, nSize) );
593
0
        bReturn = true;
594
0
    }
595
596
0
    return bReturn;
597
0
}
598
599
bool VCLXAccessibleEdit::isComboBoxChild()
600
0
{
601
0
    Reference<XAccessible> xParent = getAccessibleParent();
602
0
    if (xParent.is())
603
0
    {
604
0
        Reference<XAccessibleContext> xParentContext = xParent->getAccessibleContext();
605
0
        if (xParentContext.is() && xParentContext->getAccessibleRole() == AccessibleRole::COMBO_BOX)
606
0
            return true;
607
0
    }
608
0
    return false;
609
0
}
610
611
bool VCLXAccessibleEdit::isEditable()
612
0
{
613
0
    VclPtr<Edit> pEdit = GetAs<Edit>();
614
0
    return pEdit && !pEdit->IsReadOnly() && pEdit->IsEnabled();
615
0
}
616
617
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */