Coverage Report

Created: 2026-03-31 11:00

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libreoffice/vcl/source/window/cursor.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
22
#include <comphelper/lok.hxx>
23
#include <tools/mapunit.hxx>
24
#include <vcl/svapp.hxx>
25
#include <vcl/timer.hxx>
26
#include <vcl/settings.hxx>
27
#include <vcl/window.hxx>
28
#include <vcl/cursor.hxx>
29
30
#include <window.h>
31
32
#include <tools/poly.hxx>
33
34
struct ImplCursorData
35
{
36
    AutoTimer       maTimer { "vcl ImplCursorData maTimer" };            // Timer
37
    Point           maPixPos;           // Pixel-Position
38
    Point           maPixRotOff;        // Pixel-Offset-Position
39
    Size            maPixSize;          // Pixel-Size
40
    Degree10        mnOrientation;      // Pixel-Orientation
41
    CursorDirection mnDirection = CursorDirection::NONE; // indicates writing direction
42
    sal_uInt16      mnStyle = 0;        // Cursor-Style
43
    bool            mbCurVisible = false; // Is cursor currently visible
44
    VclPtr<vcl::Window> mpWindow;           // assigned window
45
};
46
47
namespace
48
{
49
const char* pDisableCursorIndicator(getenv("SAL_DISABLE_CURSOR_INDICATOR"));
50
bool bDisableCursorIndicator(nullptr != pDisableCursorIndicator);
51
52
// Build the cursor shape polygon accounting for direction indicators
53
// and orientation, or return empty polygon for simple rectangular cursors
54
tools::Polygon ImplCursorPoly(ImplCursorData const* pData)
55
0
{
56
0
    tools::Rectangle aRect(pData->maPixPos, pData->maPixSize);
57
0
    if (pData->mnDirection == CursorDirection::NONE && !pData->mnOrientation)
58
0
        return {};
59
60
0
    tools::Polygon aPoly(aRect);
61
0
    if (aPoly.GetSize() != 5)
62
0
        return {};
63
64
0
    aPoly[1].AdjustX(1);  // include the right border
65
0
    aPoly[2].AdjustX(1);
66
67
    // apply direction flag after slant to use the correct shape
68
0
    if (!bDisableCursorIndicator && pData->mnDirection != CursorDirection::NONE)
69
0
    {
70
0
        Point pAry[7];
71
        // Related system settings for "delta" could be:
72
        // gtk cursor-aspect-ratio and  windows SPI_GETCARETWIDTH
73
0
        int delta = (aRect.getOpenHeight() * 4 / 100) + 1;
74
0
        if (pData->mnDirection == CursorDirection::LTR)
75
0
        {
76
            // left-to-right
77
0
            pAry[0] = aPoly.GetPoint(0);
78
0
            pAry[1] = aPoly.GetPoint(1);
79
0
            pAry[2] = pAry[1];
80
0
            pAry[2].AdjustX(delta);
81
0
            pAry[2].AdjustY(delta);
82
0
            pAry[3] = pAry[1];
83
0
            pAry[3].AdjustY(delta * 2);
84
0
            pAry[4] = aPoly.GetPoint(2);
85
0
            pAry[5] = aPoly.GetPoint(3);
86
0
            pAry[6] = aPoly.GetPoint(4);
87
0
        }
88
0
        else if (pData->mnDirection == CursorDirection::RTL)
89
0
        {
90
            // right-to-left
91
0
            pAry[0] = aPoly.GetPoint(0);
92
0
            pAry[1] = aPoly.GetPoint(1);
93
0
            pAry[2] = aPoly.GetPoint(2);
94
0
            pAry[3] = aPoly.GetPoint(3);
95
0
            pAry[4] = pAry[0];
96
0
            pAry[4].AdjustY(delta * 2);
97
0
            pAry[5] = pAry[0];
98
0
            pAry[5].AdjustX(-delta);
99
0
            pAry[5].AdjustY(delta);
100
0
            pAry[6] = aPoly.GetPoint(4);
101
0
        }
102
0
        aPoly = tools::Polygon(7, pAry);
103
0
    }
104
105
0
    if (pData->mnOrientation)
106
0
        aPoly.Rotate(pData->maPixRotOff, pData->mnOrientation);
107
0
    return aPoly;
108
0
}
109
110
// Calculate the pixel bounding rect of the cursor
111
tools::Rectangle ImplCursorBoundRect(ImplCursorData const* pData)
112
0
{
113
0
    tools::Polygon aPoly = ImplCursorPoly(pData);
114
0
    if (aPoly.GetSize())
115
0
        return aPoly.GetBoundRect();
116
0
    return tools::Rectangle(pData->maPixPos, pData->maPixSize);
117
0
}
118
}
119
120
static tools::Rectangle ImplCursorInvert(vcl::RenderContext* pRenderContext, ImplCursorData const * pData)
121
0
{
122
0
    bool bMapMode = pRenderContext->IsMapModeEnabled();
123
0
    pRenderContext->EnableMapMode( false );
124
0
    InvertFlags nInvertStyle;
125
0
    if ( pData->mnStyle & CURSOR_SHADOW )
126
0
        nInvertStyle = InvertFlags::N50;
127
0
    else
128
0
        nInvertStyle = InvertFlags::NONE;
129
130
0
    tools::Rectangle aRect;
131
0
    tools::Polygon aPoly = ImplCursorPoly(pData);
132
0
    if (aPoly.GetSize())
133
0
    {
134
0
        pRenderContext->Invert(aPoly, nInvertStyle);
135
0
        aRect = aPoly.GetBoundRect();
136
0
    }
137
0
    else
138
0
    {
139
0
        aRect = tools::Rectangle(pData->maPixPos, pData->maPixSize);
140
0
        pRenderContext->Invert(aRect, nInvertStyle);
141
0
    }
142
143
0
    pRenderContext->EnableMapMode(bMapMode);
144
0
    return aRect;
145
0
}
146
147
static void ImplCursorInvert(vcl::Window* pWindow, ImplCursorData const * pData)
148
0
{
149
0
    if (!pWindow || pWindow->isDisposed())
150
0
        return;
151
152
0
    vcl::PaintBufferGuardPtr pGuard;
153
0
    const bool bDoubleBuffering = pWindow->SupportsDoubleBuffering();
154
0
    if (bDoubleBuffering)
155
0
        pGuard.reset(new vcl::PaintBufferGuard(pWindow->ImplGetWindowImpl()->mpFrameData, pWindow));
156
157
0
    vcl::RenderContext* pRenderContext = bDoubleBuffering ? pGuard->GetRenderContext() : pWindow->GetOutDev();
158
159
0
    tools::Rectangle aPaintRect = ImplCursorInvert(pRenderContext, pData);
160
0
    if (bDoubleBuffering)
161
0
        pGuard->SetPaintRect(pRenderContext->PixelToLogic(aPaintRect));
162
0
}
163
164
bool vcl::Cursor::ImplPrepForDraw(const OutputDevice* pDevice, ImplCursorData& rData) const
165
0
{
166
0
    if (pDevice && !rData.mbCurVisible)
167
0
    {
168
0
        rData.maPixPos        = pDevice->LogicToPixel( maPos );
169
0
        rData.maPixSize       = pDevice->LogicToPixel( maSize );
170
0
        rData.mnOrientation   = mnOrientation;
171
0
        rData.mnDirection     = mnDirection;
172
173
        // correct the position with the offset
174
0
        rData.maPixRotOff = rData.maPixPos;
175
176
        // use width (as set in Settings) if size is 0,
177
0
        if (!rData.maPixSize.Width())
178
0
            rData.maPixSize.setWidth(pDevice->GetSettings().GetStyleSettings().GetCursorSize());
179
0
        return true;
180
0
    }
181
0
    return false;
182
0
}
183
184
void vcl::Cursor::ImplDraw()
185
0
{
186
0
    if (mpData && mpData->mpWindow)
187
0
    {
188
        // calculate output area
189
0
        if (ImplPrepForDraw(mpData->mpWindow->GetOutDev(), *mpData))
190
0
        {
191
            // display
192
0
            ImplCursorInvert(mpData->mpWindow, mpData.get());
193
0
            mpData->mbCurVisible = true;
194
0
        }
195
0
    }
196
0
}
197
198
void vcl::Cursor::DrawToDevice(OutputDevice& rRenderContext)
199
0
{
200
0
    ImplCursorData aData;
201
    // calculate output area
202
0
    if (ImplPrepForDraw(&rRenderContext, aData))
203
0
    {
204
        // display
205
0
        ImplCursorInvert(&rRenderContext, &aData);
206
0
    }
207
0
}
208
209
tools::Rectangle vcl::Cursor::GetBoundRect(OutputDevice const& rRenderContext) const
210
0
{
211
0
    ImplCursorData aData;
212
0
    if (ImplPrepForDraw(&rRenderContext, aData))
213
0
        return ImplCursorBoundRect(&aData);
214
0
    return {};
215
0
}
216
217
void vcl::Cursor::ImplRestore()
218
0
{
219
0
    assert( mpData && mpData->mbCurVisible );
220
221
0
    ImplCursorInvert(mpData->mpWindow, mpData.get());
222
0
    mpData->mbCurVisible = false;
223
0
}
224
225
void vcl::Cursor::ImplDoShow( bool bDrawDirect, bool bRestore )
226
4.08k
{
227
4.08k
    if ( !mbVisible )
228
4.08k
        return;
229
230
0
    vcl::Window* pWindow;
231
0
    if ( mpWindow )
232
0
        pWindow = mpWindow;
233
0
    else
234
0
    {
235
        // show the cursor, if there is an active window and the cursor
236
        // has been selected in this window
237
0
        pWindow = Application::GetFocusWindow();
238
0
        if (!pWindow || !pWindow->mpWindowImpl || (pWindow->mpWindowImpl->mpCursor != this)
239
0
            || pWindow->mpWindowImpl->mbInPaint
240
0
            || !pWindow->mpWindowImpl->mpFrameData->mbHasFocus)
241
0
            pWindow = nullptr;
242
0
    }
243
244
0
    if ( !pWindow )
245
0
        return;
246
247
0
    if ( !mpData )
248
0
    {
249
0
        mpData.reset( new ImplCursorData );
250
0
        mpData->maTimer.SetInvokeHandler( LINK( this, Cursor, ImplTimerHdl ) );
251
0
    }
252
253
0
    mpData->mpWindow    = pWindow;
254
0
    mpData->mnStyle     = mnStyle;
255
0
    if ( bDrawDirect || bRestore )
256
0
        ImplDraw();
257
258
0
    if ( !mpWindow && (bDrawDirect || !mpData->maTimer.IsActive()) )
259
0
    {
260
0
        mpData->maTimer.SetTimeout( pWindow->GetSettings().GetStyleSettings().GetCursorBlinkTime() );
261
0
        if ( mpData->maTimer.GetTimeout() != STYLE_CURSOR_NOBLINKTIME )
262
0
            mpData->maTimer.Start();
263
0
        else if ( !mpData->mbCurVisible )
264
0
            ImplDraw();
265
0
        LOKNotify( pWindow, u"cursor_invalidate"_ustr );
266
0
        LOKNotify( pWindow, u"cursor_visible"_ustr );
267
0
    }
268
0
}
269
270
namespace
271
{
272
273
tools::Rectangle calcualteCursorRect(Point const& rPosition, Size const rSize, vcl::Window* pWindow, vcl::Window* pParent)
274
0
{
275
0
    Point aPositionPixel = pWindow->LogicToPixel(rPosition);
276
0
    const tools::Long nX = pWindow->GetOutOffXPixel() + aPositionPixel.X() - pParent->GetOutOffXPixel();
277
0
    const tools::Long nY = pWindow->GetOutOffYPixel() + aPositionPixel.Y() - pParent->GetOutOffYPixel();
278
279
0
    Size aSizePixel = pWindow->LogicToPixel(rSize);
280
0
    if (!aSizePixel.Width())
281
0
        aSizePixel.setWidth( pWindow->GetSettings().GetStyleSettings().GetCursorSize() );
282
283
0
    Point aPosition(nX, nY);
284
285
0
    if (pWindow->IsRTLEnabled() && pWindow->GetOutDev() && pParent->GetOutDev()
286
0
        && !pWindow->GetOutDev()->ImplIsAntiparallel())
287
0
        pParent->GetOutDev()->ReMirror(aPosition);
288
289
0
    if (!pWindow->IsRTLEnabled() && pWindow->GetOutDev() && pParent->GetOutDev()
290
0
        && pWindow->GetOutDev()->ImplIsAntiparallel())
291
0
    {
292
0
        pWindow->GetOutDev()->ReMirror(aPosition);
293
0
        pParent->GetOutDev()->ReMirror(aPosition);
294
0
    }
295
296
0
    return tools::Rectangle(aPosition, aSizePixel);
297
0
}
298
299
} // end anonymous namespace
300
301
void vcl::Cursor::LOKNotify(vcl::Window* pWindow, const OUString& rAction)
302
0
{
303
0
    VclPtr<vcl::Window> pParent = pWindow->GetParentWithLOKNotifier();
304
0
    if (!pParent)
305
0
        return;
306
307
0
    assert(pWindow && "Cannot notify without a window");
308
0
    assert(mpData && "Require ImplCursorData");
309
0
    assert(comphelper::LibreOfficeKit::isActive());
310
311
0
    const vcl::ILibreOfficeKitNotifier* pNotifier = pParent->GetLOKNotifier();
312
313
0
    if (pWindow->IsFormControl() || (pWindow->GetParent() && pWindow->GetParent()->IsFormControl()))
314
0
    {
315
0
        if (rAction == "cursor_invalidate")
316
0
        {
317
0
            tools::Rectangle aRect;
318
0
            if (pWindow->IsFormControl())
319
0
                aRect = calcualteCursorRect(GetPos(), GetSize(), pWindow, pWindow->GetParent());
320
0
            else
321
0
                aRect = calcualteCursorRect(GetPos(), GetSize(), pWindow, pWindow->GetParent()->GetParent());
322
323
0
            OutputDevice* pDevice = mpData->mpWindow->GetOutDev();
324
0
            const tools::Rectangle aRectTwip = pDevice->PixelToLogic(aRect, MapMode(MapUnit::MapTwip));
325
326
0
            if (pWindow->IsFormControl())
327
0
                pNotifier->notifyCursorInvalidation(&aRectTwip, true, pWindow->GetLOKWindowId());
328
0
            else
329
0
                pNotifier->notifyCursorInvalidation(&aRectTwip, true, pWindow->GetParent()->GetLOKWindowId());
330
0
        }
331
0
    }
332
0
    else
333
0
    {
334
0
        if (comphelper::LibreOfficeKit::isDialogPainting())
335
0
            return;
336
337
0
        std::vector<vcl::LOKPayloadItem> aItems;
338
0
        if (rAction == "cursor_visible")
339
0
        {
340
0
            aItems.emplace_back("visible", mpData->mbCurVisible ? "true" : "false");
341
0
        }
342
0
        else if (rAction == "cursor_invalidate")
343
0
        {
344
0
            const tools::Rectangle aRect = calcualteCursorRect(GetPos(), GetSize(), pWindow, pParent);
345
0
            aItems.emplace_back("rectangle", aRect.toString());
346
0
        }
347
0
        pNotifier->notifyWindow(pParent->GetLOKWindowId(), rAction, aItems);
348
0
    }
349
0
}
350
351
bool vcl::Cursor::ImplDoHide( bool bSuspend )
352
4.08k
{
353
4.08k
    bool bWasCurVisible = false;
354
4.08k
    if ( mpData && mpData->mpWindow )
355
0
    {
356
0
        bWasCurVisible = mpData->mbCurVisible;
357
0
        if ( mpData->mbCurVisible )
358
0
            ImplRestore();
359
360
0
        if ( !bSuspend )
361
0
        {
362
0
            LOKNotify( mpData->mpWindow, u"cursor_visible"_ustr );
363
0
            mpData->maTimer.Stop();
364
0
            mpData->mpWindow = nullptr;
365
0
        }
366
0
    }
367
4.08k
    return bWasCurVisible;
368
4.08k
}
369
370
void vcl::Cursor::ImplShow()
371
4.08k
{
372
4.08k
    ImplDoShow( true/*bDrawDirect*/, false );
373
4.08k
}
374
375
void vcl::Cursor::ImplHide()
376
4.08k
{
377
4.08k
    ImplDoHide( false );
378
4.08k
}
379
380
void vcl::Cursor::ImplResume( bool bRestore )
381
0
{
382
0
    ImplDoShow( false, bRestore );
383
0
}
384
385
bool vcl::Cursor::ImplSuspend()
386
0
{
387
0
    return ImplDoHide( true );
388
0
}
389
390
void vcl::Cursor::ImplNew()
391
79.8k
{
392
79.8k
    if ( !(mbVisible && mpData && mpData->mpWindow) )
393
79.8k
        return;
394
395
0
    if ( mpData->mbCurVisible )
396
0
        ImplRestore();
397
398
0
    ImplDraw();
399
0
    if ( !mpWindow )
400
0
    {
401
0
        LOKNotify( mpData->mpWindow, u"cursor_invalidate"_ustr );
402
0
        if ( mpData->maTimer.GetTimeout() != STYLE_CURSOR_NOBLINKTIME )
403
0
            mpData->maTimer.Start();
404
0
    }
405
0
}
406
407
IMPL_LINK_NOARG(vcl::Cursor, ImplTimerHdl, Timer *, void)
408
0
{
409
0
    if ( mpData->mbCurVisible )
410
0
        ImplRestore();
411
0
    else
412
0
        ImplDraw();
413
0
}
414
415
vcl::Cursor::Cursor()
416
4.08k
{
417
4.08k
    mpData          = nullptr;
418
4.08k
    mpWindow        = nullptr;
419
4.08k
    mnOrientation   = 0_deg10;
420
4.08k
    mnDirection     = CursorDirection::NONE;
421
4.08k
    mnStyle         = 0;
422
4.08k
    mbVisible       = false;
423
4.08k
}
424
425
vcl::Cursor::Cursor( const Cursor& rCursor ) :
426
0
    maSize( rCursor.maSize ),
427
0
    maPos( rCursor.maPos )
428
0
{
429
0
    mpData          = nullptr;
430
0
    mpWindow        = nullptr;
431
0
    mnOrientation   = rCursor.mnOrientation;
432
0
    mnDirection     = rCursor.mnDirection;
433
0
    mnStyle         = 0;
434
0
    mbVisible       = rCursor.mbVisible;
435
0
}
436
437
vcl::Cursor::~Cursor()
438
4.08k
{
439
4.08k
    if (mpData && mpData->mbCurVisible)
440
0
        ImplRestore();
441
4.08k
}
442
443
void vcl::Cursor::SetStyle( sal_uInt16 nStyle )
444
0
{
445
0
    if ( mnStyle != nStyle )
446
0
    {
447
0
        mnStyle = nStyle;
448
0
        ImplNew();
449
0
    }
450
0
}
451
452
void vcl::Cursor::Show()
453
0
{
454
0
    if ( !mbVisible )
455
0
    {
456
0
        mbVisible = true;
457
0
        ImplShow();
458
0
    }
459
0
}
460
461
void vcl::Cursor::Hide()
462
0
{
463
0
    if ( mbVisible )
464
0
    {
465
0
        mbVisible = false;
466
0
        ImplHide();
467
0
    }
468
0
}
469
470
void vcl::Cursor::SetWindow( vcl::Window* pWindow )
471
0
{
472
0
    if ( mpWindow.get() != pWindow )
473
0
    {
474
0
        mpWindow = pWindow;
475
0
        ImplNew();
476
0
    }
477
0
}
478
479
void vcl::Cursor::SetPos( const Point& rPoint )
480
0
{
481
0
    if ( maPos != rPoint )
482
0
    {
483
0
        maPos = rPoint;
484
0
        ImplNew();
485
0
    }
486
0
}
487
488
void vcl::Cursor::SetSize( const Size& rSize )
489
0
{
490
0
    if ( maSize != rSize )
491
0
    {
492
0
        maSize = rSize;
493
0
        ImplNew();
494
0
    }
495
0
}
496
497
void vcl::Cursor::SetWidth( tools::Long nNewWidth )
498
4.08k
{
499
4.08k
    if ( maSize.Width() != nNewWidth )
500
0
    {
501
0
        maSize.setWidth( nNewWidth );
502
0
        ImplNew();
503
0
    }
504
4.08k
}
505
506
void vcl::Cursor::SetOrientation( Degree10 nNewOrientation )
507
0
{
508
0
    if ( mnOrientation != nNewOrientation )
509
0
    {
510
0
        mnOrientation = nNewOrientation;
511
0
        ImplNew();
512
0
    }
513
0
}
514
515
void vcl::Cursor::SetDirection( CursorDirection nNewDirection )
516
0
{
517
0
    if ( mnDirection != nNewDirection )
518
0
    {
519
0
        mnDirection = nNewDirection;
520
0
        ImplNew();
521
0
    }
522
0
}
523
524
vcl::Cursor& vcl::Cursor::operator=( const vcl::Cursor& rCursor )
525
0
{
526
0
    maPos           = rCursor.maPos;
527
0
    maSize          = rCursor.maSize;
528
0
    mnOrientation   = rCursor.mnOrientation;
529
0
    mnDirection     = rCursor.mnDirection;
530
0
    mbVisible       = rCursor.mbVisible;
531
0
    ImplNew();
532
533
0
    return *this;
534
0
}
535
536
bool vcl::Cursor::operator==( const vcl::Cursor& rCursor ) const
537
0
{
538
0
    return
539
0
        ((maPos         == rCursor.maPos)           &&
540
0
         (maSize        == rCursor.maSize)          &&
541
0
         (mnOrientation == rCursor.mnOrientation)   &&
542
0
         (mnDirection   == rCursor.mnDirection)     &&
543
0
         (mbVisible     == rCursor.mbVisible))
544
0
        ;
545
0
}
546
547
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */