Coverage Report

Created: 2026-04-09 11:41

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libreoffice/vcl/source/control/ctrl.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 <comphelper/lok.hxx>
21
#include <tools/mapunit.hxx>
22
23
#include <vcl/DocWindow.hxx>
24
#include <vcl/ctrl.hxx>
25
#include <vcl/decoview.hxx>
26
#include <vcl/event.hxx>
27
#include <vcl/mnemonic.hxx>
28
29
#include <svdata.hxx>
30
#include <textlayout.hxx>
31
32
using namespace vcl;
33
34
void Control::ImplInitControlData()
35
0
{
36
0
    mbHasControlFocus       = false;
37
0
    mbShowAccelerator       = false;
38
0
}
39
40
Control::Control(WindowType eType)
41
0
    : Window(eType)
42
0
{
43
0
    ImplInitControlData();
44
0
}
Unexecuted instantiation: Control::Control(WindowType)
Unexecuted instantiation: Control::Control(WindowType)
45
46
Control::Control(vcl::Window* pParent, WinBits eStyle)
47
0
    : Window(WindowType::CONTROL)
48
0
{
49
0
    ImplInitControlData();
50
0
    ImplInit(pParent, eStyle, nullptr);
51
0
}
Unexecuted instantiation: Control::Control(vcl::Window*, long)
Unexecuted instantiation: Control::Control(vcl::Window*, long)
52
53
Control::~Control()
54
0
{
55
0
    disposeOnce();
56
0
}
57
58
void Control::dispose()
59
0
{
60
0
    mxLayoutData.reset();
61
0
    mpReferenceDevice.reset();
62
0
    Window::dispose();
63
0
}
64
65
void Control::EnableRTL( bool bEnable )
66
0
{
67
    // convenience: for controls also switch layout mode
68
0
    GetOutDev()->SetLayoutMode( bEnable ? vcl::text::ComplexTextLayoutFlags::BiDiRtl | vcl::text::ComplexTextLayoutFlags::TextOriginLeft :
69
0
                                vcl::text::ComplexTextLayoutFlags::TextOriginLeft );
70
0
    CompatStateChanged( StateChangedType::Mirroring );
71
0
    Window::EnableRTL(bEnable);
72
0
}
73
74
void Control::Resize()
75
0
{
76
0
    ImplClearLayoutData();
77
0
    Window::Resize();
78
0
}
79
80
void Control::FillLayoutData() const
81
0
{
82
0
}
83
84
void Control::CreateLayoutData() const
85
0
{
86
0
    SAL_WARN_IF( mxLayoutData, "vcl", "Control::CreateLayoutData: should be called with non-existent layout data only!" );
87
0
    mxLayoutData.emplace();
88
0
}
89
90
bool Control::HasLayoutData() const
91
0
{
92
0
    return bool(mxLayoutData);
93
0
}
94
95
void Control::SetText( const OUString& rStr )
96
0
{
97
0
    ImplClearLayoutData();
98
0
    Window::SetText( rStr );
99
0
}
100
101
0
ControlLayoutData::ControlLayoutData() : m_pParent( nullptr )
102
0
{
103
0
}
104
105
tools::Rectangle ControlLayoutData::GetCharacterBounds( tools::Long nIndex ) const
106
0
{
107
0
    return (nIndex >= 0 && o3tl::make_unsigned(nIndex) < m_aUnicodeBoundRects.size()) ? m_aUnicodeBoundRects[ nIndex ] : tools::Rectangle();
108
0
}
109
110
tools::Rectangle Control::GetCharacterBounds( tools::Long nIndex ) const
111
0
{
112
0
    if( !HasLayoutData() )
113
0
        FillLayoutData();
114
0
    return mxLayoutData ? mxLayoutData->GetCharacterBounds( nIndex ) : tools::Rectangle();
115
0
}
116
117
tools::Long ControlLayoutData::GetIndexForPoint( const Point& rPoint ) const
118
0
{
119
0
    tools::Long nIndex = -1;
120
0
    for( tools::Long i = m_aUnicodeBoundRects.size()-1; i >= 0; i-- )
121
0
    {
122
0
        Point aTopLeft = m_aUnicodeBoundRects[i].TopLeft();
123
0
        Point aBottomRight = m_aUnicodeBoundRects[i].BottomRight();
124
0
        if (rPoint.X() >= aTopLeft.X() && rPoint.Y() >= aTopLeft.Y() &&
125
0
            rPoint.X() <= aBottomRight.X() && rPoint.Y() <= aBottomRight.Y())
126
0
        {
127
0
            nIndex = i;
128
0
            break;
129
0
        }
130
0
    }
131
0
    return nIndex;
132
0
}
133
134
tools::Long Control::GetIndexForPoint( const Point& rPoint ) const
135
0
{
136
0
    if( ! HasLayoutData() )
137
0
        FillLayoutData();
138
0
    return mxLayoutData ? mxLayoutData->GetIndexForPoint( rPoint ) : -1;
139
0
}
140
141
Pair ControlLayoutData::GetLineStartEnd( tools::Long nLine ) const
142
0
{
143
0
    Pair aPair( -1, -1 );
144
145
0
    int nDisplayLines = m_aLineIndices.size();
146
0
    if( nLine >= 0 && nLine < nDisplayLines )
147
0
    {
148
0
        aPair.A() = m_aLineIndices[nLine];
149
0
        if( nLine+1 < nDisplayLines )
150
0
            aPair.B() = m_aLineIndices[nLine+1]-1;
151
0
        else
152
0
            aPair.B() = m_aDisplayText.getLength()-1;
153
0
    }
154
0
    else if( nLine == 0 && nDisplayLines == 0 && !m_aDisplayText.isEmpty() )
155
0
    {
156
        // special case for single line controls so the implementations
157
        // in that case do not have to fill in the line indices
158
0
        aPair.A() = 0;
159
0
        aPair.B() = m_aDisplayText.getLength()-1;
160
0
    }
161
0
    return aPair;
162
0
}
163
164
Pair Control::GetLineStartEnd( tools::Long nLine ) const
165
0
{
166
0
    if( !HasLayoutData() )
167
0
        FillLayoutData();
168
0
    return mxLayoutData ? mxLayoutData->GetLineStartEnd( nLine ) : Pair( -1, -1 );
169
0
}
170
171
tools::Long ControlLayoutData::ToRelativeLineIndex( tools::Long nIndex ) const
172
0
{
173
    // is the index sensible at all ?
174
0
    if( nIndex >= 0 && nIndex < m_aDisplayText.getLength() )
175
0
    {
176
0
        int nDisplayLines = m_aLineIndices.size();
177
        // if only 1 line exists, then absolute and relative index are
178
        // identical -> do nothing
179
0
        if( nDisplayLines > 1 )
180
0
        {
181
0
            int nLine;
182
0
            for( nLine = nDisplayLines-1; nLine >= 0; nLine-- )
183
0
            {
184
0
                if( m_aLineIndices[nLine] <= nIndex )
185
0
                {
186
0
                    nIndex -= m_aLineIndices[nLine];
187
0
                    break;
188
0
                }
189
0
            }
190
0
            if( nLine < 0 )
191
0
            {
192
0
                SAL_WARN_IF( nLine < 0, "vcl", "ToRelativeLineIndex failed" );
193
0
                nIndex = -1;
194
0
            }
195
0
        }
196
0
    }
197
0
    else
198
0
        nIndex = -1;
199
200
0
    return nIndex;
201
0
}
202
203
tools::Long Control::ToRelativeLineIndex( tools::Long nIndex ) const
204
0
{
205
0
    if( !HasLayoutData() )
206
0
        FillLayoutData();
207
0
    return mxLayoutData ? mxLayoutData->ToRelativeLineIndex( nIndex ) : -1;
208
0
}
209
210
OUString Control::GetDisplayText() const
211
0
{
212
0
    if( !HasLayoutData() )
213
0
        FillLayoutData();
214
0
    return mxLayoutData ? mxLayoutData->m_aDisplayText : GetText();
215
0
}
216
217
bool Control::FocusWindowBelongsToControl(const vcl::Window* pFocusWin) const
218
0
{
219
0
    return ImplIsWindowOrChild(pFocusWin);
220
0
}
221
222
bool Control::EventNotify( NotifyEvent& rNEvt )
223
0
{
224
0
    if ( rNEvt.GetType() == NotifyEventType::GETFOCUS )
225
0
    {
226
0
        if ( !mbHasControlFocus )
227
0
        {
228
0
            mbHasControlFocus = true;
229
0
            CompatStateChanged( StateChangedType::ControlFocus );
230
0
            if ( ImplCallEventListenersAndHandler( VclEventId::ControlGetFocus, {} ) )
231
                // been destroyed within the handler
232
0
                return true;
233
0
        }
234
0
    }
235
0
    else
236
0
    {
237
0
        if ( rNEvt.GetType() == NotifyEventType::LOSEFOCUS )
238
0
        {
239
0
            vcl::Window* pFocusWin = Application::GetFocusWindow();
240
0
            if ( !pFocusWin || !FocusWindowBelongsToControl(pFocusWin) )
241
0
            {
242
0
                mbHasControlFocus = false;
243
0
                CompatStateChanged( StateChangedType::ControlFocus );
244
0
                if ( ImplCallEventListenersAndHandler( VclEventId::ControlLoseFocus, [this] () { maLoseFocusHdl.Call(*this); } ) )
245
                    // been destroyed within the handler
246
0
                    return true;
247
0
            }
248
0
        }
249
0
    }
250
0
    return Window::EventNotify( rNEvt );
251
0
}
252
253
void Control::StateChanged( StateChangedType nStateChange )
254
0
{
255
0
    if( nStateChange == StateChangedType::InitShow   ||
256
0
        nStateChange == StateChangedType::Visible    ||
257
0
        nStateChange == StateChangedType::Zoom       ||
258
0
        nStateChange == StateChangedType::ControlFont
259
0
        )
260
0
    {
261
0
        ImplClearLayoutData();
262
0
    }
263
0
    Window::StateChanged( nStateChange );
264
0
}
265
266
void Control::AppendLayoutData( const Control& rSubControl ) const
267
0
{
268
0
    if( !rSubControl.HasLayoutData() )
269
0
        rSubControl.FillLayoutData();
270
0
    if( !rSubControl.HasLayoutData() || rSubControl.mxLayoutData->m_aDisplayText.isEmpty() )
271
0
        return;
272
273
0
    tools::Long nCurrentIndex = mxLayoutData->m_aDisplayText.getLength();
274
0
    mxLayoutData->m_aDisplayText += rSubControl.mxLayoutData->m_aDisplayText;
275
0
    int nLines = rSubControl.mxLayoutData->m_aLineIndices.size();
276
0
    int n;
277
0
    mxLayoutData->m_aLineIndices.push_back( nCurrentIndex );
278
0
    for( n = 1; n < nLines; n++ )
279
0
        mxLayoutData->m_aLineIndices.push_back( rSubControl.mxLayoutData->m_aLineIndices[n] + nCurrentIndex );
280
0
    int nRectangles = rSubControl.mxLayoutData->m_aUnicodeBoundRects.size();
281
0
    tools::Rectangle aRel = rSubControl.GetWindowExtentsRelative(*this);
282
0
    for( n = 0; n < nRectangles; n++ )
283
0
    {
284
0
        tools::Rectangle aRect = rSubControl.mxLayoutData->m_aUnicodeBoundRects[n];
285
0
        aRect.Move( aRel.Left(), aRel.Top() );
286
0
        mxLayoutData->m_aUnicodeBoundRects.push_back( aRect );
287
0
    }
288
0
}
289
290
void Control::CallEventListeners( VclEventId nEvent, void* pData)
291
0
{
292
0
    VclPtr<Control> xThis(this);
293
0
    UITestLogger::getInstance().logAction(xThis, nEvent);
294
295
0
    vcl::Window::CallEventListeners(nEvent, pData);
296
0
}
297
298
bool Control::ImplCallEventListenersAndHandler( VclEventId nEvent, std::function<void()> const & callHandler )
299
0
{
300
0
    VclPtr<Control> xThis(this);
301
302
0
    Control::CallEventListeners( nEvent );
303
304
0
    if ( !xThis->isDisposed() )
305
0
    {
306
0
        if (callHandler)
307
0
        {
308
0
            callHandler();
309
0
        }
310
311
0
        if ( !xThis->isDisposed() )
312
0
            return false;
313
0
    }
314
0
    return true;
315
0
}
316
317
void Control::SetLayoutDataParent( const Control* pParent ) const
318
0
{
319
0
    if( HasLayoutData() )
320
0
        mxLayoutData->m_pParent = pParent;
321
0
}
322
323
void Control::ImplClearLayoutData() const
324
0
{
325
0
    mxLayoutData.reset();
326
0
}
327
328
void Control::ImplDrawFrame( OutputDevice* pDev, tools::Rectangle& rRect )
329
0
{
330
    // use a deco view to draw the frame
331
    // However, since there happens a lot of magic there, we need to fake some (style) settings
332
    // on the device
333
0
    AllSettings aOriginalSettings( pDev->GetSettings() );
334
335
0
    AllSettings aNewSettings( aOriginalSettings );
336
0
    StyleSettings aStyle( aNewSettings.GetStyleSettings() );
337
338
    // The *only known* clients of the Draw methods of the various VCL-controls are form controls:
339
    // During print preview, and during printing, Draw is called. Thus, drawing always happens with a
340
    // mono (colored) border
341
0
    aStyle.SetOptions( aStyle.GetOptions() | StyleSettingsOptions::Mono );
342
0
    aStyle.SetMonoColor( GetSettings().GetStyleSettings().GetMonoColor() );
343
344
0
    aNewSettings.SetStyleSettings( aStyle );
345
    // #i67023# do not call data changed listeners for this fake
346
    // since they may understandably invalidate on settings changed
347
0
    pDev->OutputDevice::SetSettings( aNewSettings );
348
349
0
    DecorationView aDecoView( pDev );
350
0
    rRect = aDecoView.DrawFrame( rRect, DrawFrameStyle::Out, DrawFrameFlags::WindowBorder );
351
352
0
    pDev->OutputDevice::SetSettings( aOriginalSettings );
353
0
}
354
355
void Control::SetShowAccelerator(bool bVal)
356
0
{
357
0
    mbShowAccelerator = bVal;
358
0
};
359
360
ControlLayoutData::~ControlLayoutData()
361
0
{
362
0
    if( m_pParent )
363
0
        m_pParent->ImplClearLayoutData();
364
0
}
365
366
Size Control::GetOptimalSize() const
367
0
{
368
0
    return Size( GetTextWidth( GetText() ) + 2 * 12,
369
0
                 GetTextHeight() + 2 * 6 );
370
0
}
371
372
void Control::SetReferenceDevice( OutputDevice* _referenceDevice )
373
0
{
374
0
    if ( mpReferenceDevice == _referenceDevice )
375
0
        return;
376
377
0
    mpReferenceDevice = _referenceDevice;
378
0
    Invalidate();
379
0
}
380
381
OutputDevice* Control::GetReferenceDevice() const
382
0
{
383
    // tdf#118377 It can happen that mpReferenceDevice is already disposed and
384
    // stays disposed (see task, even when Dialog is closed). I have no idea if
385
    // this may be very bad - someone who knows more about lifetime of OutputDevice's
386
    // will have to decide.
387
    // To secure this, I changed all accesses to mpControlData->mpReferenceDevice to
388
    // use Control::GetReferenceDevice() - only use mpControlData->mpReferenceDevice
389
    // inside Control::SetReferenceDevice and Control::GetReferenceDevice().
390
    // Control::GetReferenceDevice() will now reset mpReferenceDevice if it is already
391
    // disposed. This way all usages will do a kind of 'test-and-get' call.
392
0
    if(nullptr != mpReferenceDevice && mpReferenceDevice->isDisposed())
393
0
    {
394
0
        const_cast<Control*>(this)->SetReferenceDevice(nullptr);
395
0
    }
396
397
0
    return mpReferenceDevice;
398
0
}
399
400
const vcl::Font& Control::GetCanonicalFont( const StyleSettings& _rStyle ) const
401
0
{
402
0
    return _rStyle.GetLabelFont();
403
0
}
404
405
const Color& Control::GetCanonicalTextColor( const StyleSettings& _rStyle ) const
406
0
{
407
0
    return _rStyle.GetLabelTextColor();
408
0
}
409
410
void Control::ApplySettings(vcl::RenderContext& rRenderContext)
411
0
{
412
0
    const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings();
413
414
0
    ApplyControlFont(rRenderContext, GetCanonicalFont(rStyleSettings));
415
416
0
    ApplyControlForeground(rRenderContext, GetCanonicalTextColor(rStyleSettings));
417
0
    rRenderContext.SetTextFillColor();
418
0
}
419
420
void Control::ImplInitSettings()
421
0
{
422
0
    ApplySettings(*GetOutDev());
423
0
}
424
425
tools::Rectangle Control::DrawControlText( OutputDevice& _rTargetDevice, const tools::Rectangle& rRect, const OUString& _rStr,
426
    DrawTextFlags _nStyle, std::vector< tools::Rectangle >* _pVector, OUString* _pDisplayText, const Size* i_pDeviceSize ) const
427
0
{
428
0
    OUString rPStr = _rStr;
429
0
    DrawTextFlags nPStyle = _nStyle;
430
431
0
    bool autoacc = ImplGetSVData()->maNWFData.mbAutoAccel;
432
433
0
    if (autoacc && !mbShowAccelerator)
434
0
        rPStr = removeMnemonicFromString( _rStr );
435
436
0
    if( !GetReferenceDevice() || ( GetReferenceDevice() == &_rTargetDevice ) )
437
0
    {
438
0
        const tools::Rectangle aRet = _rTargetDevice.GetTextRect(rRect, rPStr, nPStyle);
439
0
        _rTargetDevice.DrawText(aRet, rPStr, nPStyle, _pVector, _pDisplayText);
440
0
        return aRet;
441
0
    }
442
443
0
    ControlTextRenderer aRenderer( *this, _rTargetDevice, *GetReferenceDevice() );
444
0
    return aRenderer.DrawText(rRect, rPStr, nPStyle, _pVector, _pDisplayText, i_pDeviceSize);
445
0
}
446
447
tools::Rectangle Control::GetControlTextRect( OutputDevice& _rTargetDevice, const tools::Rectangle & rRect,
448
                                       const OUString& _rStr, DrawTextFlags _nStyle, Size* o_pDeviceSize ) const
449
0
{
450
0
    OUString rPStr = _rStr;
451
0
    DrawTextFlags nPStyle = _nStyle;
452
453
0
    bool autoacc = ImplGetSVData()->maNWFData.mbAutoAccel;
454
455
0
    if (autoacc && !mbShowAccelerator)
456
0
        rPStr = removeMnemonicFromString( _rStr );
457
458
0
    if ( !GetReferenceDevice() || ( GetReferenceDevice() == &_rTargetDevice ) )
459
0
    {
460
0
        tools::Rectangle aRet = _rTargetDevice.GetTextRect( rRect, rPStr, nPStyle );
461
0
        if (o_pDeviceSize)
462
0
        {
463
0
            *o_pDeviceSize = aRet.GetSize();
464
0
        }
465
0
        return aRet;
466
0
    }
467
468
0
    ControlTextRenderer aRenderer( *this, _rTargetDevice, *GetReferenceDevice() );
469
0
    return aRenderer.GetTextRect(rRect, rPStr, nPStyle, o_pDeviceSize);
470
0
}
471
472
Font
473
Control::GetUnzoomedControlPointFont() const
474
0
{
475
0
    Font aFont(GetCanonicalFont(GetSettings().GetStyleSettings()));
476
0
    if (IsControlFont())
477
0
        aFont.Merge(GetControlFont());
478
0
    return aFont;
479
0
}
480
481
void Control::LogicInvalidate(const tools::Rectangle* pRectangle)
482
0
{
483
0
    VclPtr<vcl::Window> pParent = GetParentWithLOKNotifier();
484
0
    if (!pParent || !dynamic_cast<vcl::DocWindow*>(GetParent()))
485
0
    {
486
        // if control doesn't belong to a DocWindow, the overridden base class
487
        // method has to be invoked
488
0
        Window::LogicInvalidate(pRectangle);
489
0
        return;
490
0
    }
491
492
    // avoid endless paint/invalidate loop in Impress
493
0
    if (comphelper::LibreOfficeKit::isTiledPainting())
494
0
        return;
495
496
0
    tools::Rectangle aResultRectangle;
497
0
    if (!pRectangle)
498
0
    {
499
        // we have to invalidate the whole control area not the whole document
500
0
        aResultRectangle = PixelToLogic(tools::Rectangle(GetPosPixel(), GetSizePixel()), MapMode(MapUnit::MapTwip));
501
0
    }
502
0
    else
503
0
    {
504
0
        aResultRectangle = OutputDevice::LogicToLogic(*pRectangle, GetMapMode(), MapMode(MapUnit::MapTwip));
505
0
    }
506
507
0
    pParent->GetLOKNotifier()->notifyInvalidation(&aResultRectangle);
508
0
}
509
510
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */