Coverage Report

Created: 2025-12-31 10:39

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libreoffice/svx/source/sdr/overlay/overlaymanagerbuffered.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 <sdr/overlay/overlaymanagerbuffered.hxx>
21
#include <svx/sdrpaintwindow.hxx>
22
#include <vcl/outdev.hxx>
23
#include <basegfx/range/b2drange.hxx>
24
#include <vcl/window.hxx>
25
#include <tools/fract.hxx>
26
#include <vcl/cursor.hxx>
27
28
29
namespace sdr::overlay
30
{
31
        void OverlayManagerBuffered::ImpPrepareBufferDevice()
32
0
        {
33
            // compare size of mpBufferDevice with size of visible area
34
0
            if(mpBufferDevice->GetOutputSizePixel() != getOutputDevice().GetOutputSizePixel())
35
0
            {
36
                // set new buffer size, copy as much content as possible (use bool parameter for vcl).
37
                // Newly uncovered regions will be repainted.
38
0
                mpBufferDevice->SetOutputSizePixel(getOutputDevice().GetOutputSizePixel(), false);
39
0
            }
40
41
            // compare the MapModes for zoom/scroll changes
42
0
            if(mpBufferDevice->GetMapMode() != getOutputDevice().GetMapMode())
43
0
            {
44
0
                const bool bZoomed(
45
0
                    mpBufferDevice->GetMapMode().GetScaleX() != getOutputDevice().GetMapMode().GetScaleX()
46
0
                    || mpBufferDevice->GetMapMode().GetScaleY() != getOutputDevice().GetMapMode().GetScaleY());
47
48
0
                if(!bZoomed)
49
0
                {
50
0
                    const Point& rOriginOld = mpBufferDevice->GetMapMode().GetOrigin();
51
0
                    const Point& rOriginNew = getOutputDevice().GetMapMode().GetOrigin();
52
0
                    const bool bScrolled(rOriginOld != rOriginNew);
53
54
55
0
                    if(bScrolled)
56
0
                    {
57
                        // get pixel bounds (tdf#149322 do subtraction in logic units before converting result back to pixel)
58
0
                        const Point aLogicOriginDiff(rOriginNew - rOriginOld);
59
0
                        const Size aPixelOriginDiff(mpBufferDevice->LogicToPixel(Size(aLogicOriginDiff.X(), aLogicOriginDiff.Y())));
60
0
                        const Point aDestinationOffsetPixel(aPixelOriginDiff.Width(), aPixelOriginDiff.Height());
61
0
                        const Size aOutputSizePixel(mpBufferDevice->GetOutputSizePixel());
62
63
                        // remember and switch off MapMode
64
0
                        const bool bMapModeWasEnabled(mpBufferDevice->IsMapModeEnabled());
65
0
                        mpBufferDevice->EnableMapMode(false);
66
67
                        // scroll internally buffered stuff
68
0
                        mpBufferDevice->DrawOutDev(
69
0
                            aDestinationOffsetPixel, aOutputSizePixel, // destination
70
0
                            Point(), aOutputSizePixel); // source
71
72
                        // restore MapMode
73
0
                        mpBufferDevice->EnableMapMode(bMapModeWasEnabled);
74
75
                        // scroll remembered region, too.
76
0
                        if(!maBufferRememberedRangePixel.isEmpty())
77
0
                        {
78
0
                            const basegfx::B2IPoint aIPointDestinationOffsetPixel(aDestinationOffsetPixel.X(), aDestinationOffsetPixel.Y());
79
0
                            const basegfx::B2IPoint aNewMinimum(maBufferRememberedRangePixel.getMinimum() + aIPointDestinationOffsetPixel);
80
0
                            const basegfx::B2IPoint aNewMaximum(maBufferRememberedRangePixel.getMaximum() + aIPointDestinationOffsetPixel);
81
0
                            maBufferRememberedRangePixel = basegfx::B2IRange(aNewMinimum, aNewMaximum);
82
0
                        }
83
0
                    }
84
0
                }
85
86
                // copy new MapMode
87
0
                mpBufferDevice->SetMapMode(getOutputDevice().GetMapMode());
88
0
            }
89
90
            // #i29186#
91
0
            mpBufferDevice->SetDrawMode(getOutputDevice().GetDrawMode());
92
0
            mpBufferDevice->SetSettings(getOutputDevice().GetSettings());
93
0
            mpBufferDevice->SetAntialiasing(getOutputDevice().GetAntialiasing());
94
0
        }
95
96
        void OverlayManagerBuffered::ImpRestoreBackground() const
97
0
        {
98
0
            const tools::Rectangle aRegionRectanglePixel(
99
0
                maBufferRememberedRangePixel.getMinX(), maBufferRememberedRangePixel.getMinY(),
100
0
                maBufferRememberedRangePixel.getMaxX(), maBufferRememberedRangePixel.getMaxY());
101
0
            const vcl::Region aRegionPixel(aRegionRectanglePixel);
102
103
0
            ImpRestoreBackground(aRegionPixel);
104
0
        }
105
106
        void OverlayManagerBuffered::ImpRestoreBackground(const vcl::Region& rRegionPixel) const
107
0
        {
108
            // MapModes off
109
0
            const bool bMapModeWasEnabledDest(getOutputDevice().IsMapModeEnabled());
110
0
            const bool bMapModeWasEnabledSource(mpBufferDevice->IsMapModeEnabled());
111
0
            getOutputDevice().EnableMapMode(false);
112
0
            const_cast<OverlayManagerBuffered*>(this)->mpBufferDevice->EnableMapMode(false);
113
114
            // local region
115
0
            RectangleVector aRectangles;
116
0
            rRegionPixel.GetRegionRectangles(aRectangles);
117
118
0
            for(const auto& rRect : aRectangles)
119
0
            {
120
                // restore the area
121
0
                const Point aTopLeft(rRect.TopLeft());
122
0
                const Size aSize(rRect.GetSize());
123
124
0
                getOutputDevice().DrawOutDev(
125
0
                    aTopLeft, aSize, // destination
126
0
                    aTopLeft, aSize, // source
127
0
                    *mpBufferDevice);
128
0
            }
129
130
            // restore MapModes
131
0
            getOutputDevice().EnableMapMode(bMapModeWasEnabledDest);
132
0
            const_cast<OverlayManagerBuffered*>(this)->mpBufferDevice->EnableMapMode(bMapModeWasEnabledSource);
133
0
        }
134
135
        void OverlayManagerBuffered::ImpSaveBackground(const vcl::Region& rRegion, OutputDevice* pPreRenderDevice)
136
0
        {
137
            // prepare source
138
0
            OutputDevice& rSource = pPreRenderDevice ? *pPreRenderDevice : getOutputDevice();
139
140
            // Ensure buffer is valid
141
0
            ImpPrepareBufferDevice();
142
143
            // build region which needs to be copied
144
0
            vcl::Region aRegion(rSource.LogicToPixel(rRegion));
145
146
            // limit to PaintRegion if it's a window. This will be evtl. the expanded one,
147
            // but always the exact redraw area
148
0
            if(OUTDEV_WINDOW == rSource.GetOutDevType())
149
0
            {
150
0
                vcl::Window& rWindow = *rSource.GetOwnerWindow();
151
0
                vcl::Region aPaintRegionPixel = rWindow.LogicToPixel(rWindow.GetPaintRegion());
152
0
                aRegion.Intersect(aPaintRegionPixel);
153
154
                // #i72754# Make sure content is completely rendered, the window
155
                // will be used as source of a DrawOutDev soon
156
0
                rWindow.GetOutDev()->Flush();
157
0
            }
158
159
            // also limit to buffer size
160
0
            const tools::Rectangle aBufferDeviceRectanglePixel(Point(), mpBufferDevice->GetOutputSizePixel());
161
0
            aRegion.Intersect(aBufferDeviceRectanglePixel);
162
163
            // MapModes off
164
0
            const bool bMapModeWasEnabledDest(rSource.IsMapModeEnabled());
165
0
            const bool bMapModeWasEnabledSource(mpBufferDevice->IsMapModeEnabled());
166
0
            rSource.EnableMapMode(false);
167
0
            mpBufferDevice->EnableMapMode(false);
168
169
            // prepare to iterate over the rectangles from the region in pixels
170
0
            RectangleVector aRectangles;
171
0
            aRegion.GetRegionRectangles(aRectangles);
172
173
0
            for(const auto& rRect : aRectangles)
174
0
            {
175
                // for each rectangle, save the area
176
0
                const Point aTopLeft(rRect.TopLeft());
177
0
                const Size aSize(rRect.GetSize());
178
179
0
                mpBufferDevice->DrawOutDev(
180
0
                    aTopLeft, aSize, // destination
181
0
                    aTopLeft, aSize, // source
182
0
                    rSource);
183
0
            }
184
185
            // restore MapModes
186
0
            rSource.EnableMapMode(bMapModeWasEnabledDest);
187
0
            mpBufferDevice->EnableMapMode(bMapModeWasEnabledSource);
188
0
        }
189
190
        IMPL_LINK_NOARG(OverlayManagerBuffered, ImpBufferTimerHandler, Timer*, void)
191
0
        {
192
            //Resolves: fdo#46728 ensure this exists until end of scope
193
0
            rtl::Reference<OverlayManager> xKeepAlive(this);
194
195
            // stop timer
196
0
            maBufferIdle.Stop();
197
198
0
            if(maBufferRememberedRangePixel.isEmpty())
199
                // nothing to refresh, done
200
0
                return;
201
202
0
            if (maMapModeLastCompleteRedraw == MapMode())
203
                // there was no CompleteRedraw yet, done
204
0
                return;
205
206
            // in calc i stumbled over the situation that MapModes for OutDevs
207
            // were different for ::CompleteRedraw and this timer-based refresh,
208
            // probably due to Calc's massive MapMode changes - as soon as an
209
            // overlay invalidate happened the next reschedule in calc *will*
210
            // trigger this refresh, despite what MapMode may be set at the
211
            // target device currently.
212
            // These updates only make sense in combination with the saved
213
            // background, so this is an error (and happens only in Calc due
214
            // to these always changing MapModes).
215
            // Thus it is plausible to remember the MapMode of the last full
216
            // CompleteRedraw and force local usage to it.
217
0
            const MapMode aPrevMapMode(getOutputDevice().GetMapMode());
218
0
            const bool bPatchMapMode(aPrevMapMode != maMapModeLastCompleteRedraw);
219
0
            if (bPatchMapMode)
220
0
                getOutputDevice().SetMapMode(maMapModeLastCompleteRedraw);
221
222
            // logic size for impDrawMember call
223
0
            basegfx::B2DRange aBufferRememberedRangeLogic(
224
0
                maBufferRememberedRangePixel.getMinX(), maBufferRememberedRangePixel.getMinY(),
225
0
                maBufferRememberedRangePixel.getMaxX(), maBufferRememberedRangePixel.getMaxY());
226
0
            aBufferRememberedRangeLogic.transform(getOutputDevice().GetInverseViewTransformation());
227
228
            // prepare cursor handling
229
0
            const bool bTargetIsWindow(OUTDEV_WINDOW == mrOutputDevice.GetOutDevType());
230
0
            bool bCursorWasEnabled(false);
231
232
            // #i80730# switch off VCL cursor during overlay refresh
233
0
            if(bTargetIsWindow)
234
0
            {
235
0
                vcl::Window& rWindow = *mrOutputDevice.GetOwnerWindow();
236
0
                vcl::Cursor* pCursor = rWindow.GetCursor();
237
238
0
                if(pCursor && pCursor->IsVisible())
239
0
                {
240
0
                    pCursor->Hide();
241
0
                    bCursorWasEnabled = true;
242
0
                }
243
0
            }
244
245
            // refresh with prerendering
246
0
            {
247
                // #i73602# ensure valid and sized mpOutputBufferDevice
248
0
                const Size aDestinationSizePixel(mpBufferDevice->GetOutputSizePixel());
249
0
                const Size aOutputBufferSizePixel(mpOutputBufferDevice->GetOutputSizePixel());
250
251
0
                if(aDestinationSizePixel != aOutputBufferSizePixel)
252
0
                {
253
0
                    mpOutputBufferDevice->SetOutputSizePixel(aDestinationSizePixel);
254
0
                }
255
256
0
                mpOutputBufferDevice->SetMapMode(getOutputDevice().GetMapMode());
257
0
                mpOutputBufferDevice->EnableMapMode(false);
258
0
                mpOutputBufferDevice->SetDrawMode(mpBufferDevice->GetDrawMode());
259
0
                mpOutputBufferDevice->SetSettings(mpBufferDevice->GetSettings());
260
0
                mpOutputBufferDevice->SetAntialiasing(mpBufferDevice->GetAntialiasing());
261
262
                // calculate sizes
263
0
                tools::Rectangle aRegionRectanglePixel(
264
0
                    maBufferRememberedRangePixel.getMinX(), maBufferRememberedRangePixel.getMinY(),
265
0
                    maBufferRememberedRangePixel.getMaxX(), maBufferRememberedRangePixel.getMaxY());
266
267
                // truncate aRegionRectanglePixel to destination pixel size, more does
268
                // not need to be prepared since destination is a buffer for a window. So,
269
                // maximum size indirectly shall be limited to getOutputDevice().GetOutputSizePixel()
270
0
                if(aRegionRectanglePixel.Left() < 0)
271
0
                {
272
0
                    aRegionRectanglePixel.SetLeft( 0 );
273
0
                }
274
275
0
                if(aRegionRectanglePixel.Top() < 0)
276
0
                {
277
0
                    aRegionRectanglePixel.SetTop( 0 );
278
0
                }
279
280
0
                if(aRegionRectanglePixel.Right() > aDestinationSizePixel.getWidth())
281
0
                {
282
0
                    aRegionRectanglePixel.SetRight( aDestinationSizePixel.getWidth() );
283
0
                }
284
285
0
                if(aRegionRectanglePixel.Bottom() > aDestinationSizePixel.getHeight())
286
0
                {
287
0
                    aRegionRectanglePixel.SetBottom( aDestinationSizePixel.getHeight() );
288
0
                }
289
290
                // get sizes
291
0
                const Point aTopLeft(aRegionRectanglePixel.TopLeft());
292
0
                const Size aSize(aRegionRectanglePixel.GetSize());
293
294
0
                {
295
0
                    const bool bMapModeWasEnabledDest(mpBufferDevice->IsMapModeEnabled());
296
0
                    mpBufferDevice->EnableMapMode(false);
297
298
0
                    mpOutputBufferDevice->DrawOutDev(
299
0
                        aTopLeft, aSize, // destination
300
0
                        aTopLeft, aSize, // source
301
0
                        *mpBufferDevice);
302
303
                    // restore MapModes
304
0
                    mpBufferDevice->EnableMapMode(bMapModeWasEnabledDest);
305
0
                }
306
307
                // paint overlay content for remembered region, use
308
                // method from base class directly
309
0
                mpOutputBufferDevice->EnableMapMode();
310
0
                OverlayManager::ImpDrawMembers(aBufferRememberedRangeLogic, *mpOutputBufferDevice);
311
0
                mpOutputBufferDevice->EnableMapMode(false);
312
313
                // copy to output
314
0
                {
315
0
                    const bool bMapModeWasEnabledDest(getOutputDevice().IsMapModeEnabled());
316
0
                    getOutputDevice().EnableMapMode(false);
317
318
0
                    getOutputDevice().DrawOutDev(
319
0
                        aTopLeft, aSize, // destination
320
0
                        aTopLeft, aSize, // source
321
0
                        *mpOutputBufferDevice);
322
323
                    // debug
324
                    /*getOutputDevice().SetLineCOL_RED);
325
                    getOutputDevice().SetFillColor();
326
                    getOutputDevice().DrawRect(Rectangle(aTopLeft, aSize));*/
327
328
                    // restore MapModes
329
0
                    getOutputDevice().EnableMapMode(bMapModeWasEnabledDest);
330
0
                }
331
0
            }
332
333
            // VCL hack for transparent child windows
334
            // Problem is e.g. a radiobutton form control in life mode. The used window
335
            // is a transparence vcl childwindow. This flag only allows the parent window to
336
            // paint into the child windows area, but there is no mechanism which takes
337
            // care for a repaint of the child window. A transparent child window is NOT
338
            // a window which always keeps it's content consistent over the parent, but it's
339
            // more like just a paint flag for the parent.
340
            // To get the update, the windows in question are updated manually here.
341
0
            if(bTargetIsWindow)
342
0
            {
343
0
                vcl::Window& rWindow = *mrOutputDevice.GetOwnerWindow();
344
345
0
                const tools::Rectangle aRegionRectanglePixel(
346
0
                    maBufferRememberedRangePixel.getMinX(),
347
0
                    maBufferRememberedRangePixel.getMinY(),
348
0
                    maBufferRememberedRangePixel.getMaxX(),
349
0
                    maBufferRememberedRangePixel.getMaxY());
350
0
                PaintTransparentChildren(rWindow, aRegionRectanglePixel);
351
0
            }
352
353
            // #i80730# restore visibility of VCL cursor
354
0
            if(bCursorWasEnabled)
355
0
            {
356
0
                vcl::Window& rWindow = *mrOutputDevice.GetOwnerWindow();
357
0
                vcl::Cursor* pCursor = rWindow.GetCursor();
358
359
0
                if(pCursor)
360
0
                {
361
                    // check if cursor still exists. It may have been deleted from someone
362
0
                    pCursor->Show();
363
0
                }
364
0
            }
365
366
            // forget remembered Region
367
0
            maBufferRememberedRangePixel.reset();
368
369
            // restore evtl. temporarily patched MapMode (see explanation
370
            // above)
371
0
            if (bPatchMapMode)
372
0
                getOutputDevice().SetMapMode(aPrevMapMode);
373
0
        }
374
375
        OverlayManagerBuffered::OverlayManagerBuffered(
376
            OutputDevice& rOutputDevice)
377
0
        :   OverlayManager(rOutputDevice),
378
0
            mpBufferDevice(VclPtr<VirtualDevice>::Create()),
379
0
            mpOutputBufferDevice(VclPtr<VirtualDevice>::Create()),
380
0
            maBufferIdle( "sdr::overlay::OverlayManagerBuffered maBufferIdle" ),
381
0
            maMapModeLastCompleteRedraw()
382
0
        {
383
            // Init timer
384
0
            maBufferIdle.SetPriority( TaskPriority::POST_PAINT );
385
0
            maBufferIdle.SetInvokeHandler(LINK(this, OverlayManagerBuffered, ImpBufferTimerHandler));
386
0
        }
387
388
        rtl::Reference<OverlayManager> OverlayManagerBuffered::create(
389
            OutputDevice& rOutputDevice)
390
0
        {
391
0
            return rtl::Reference<OverlayManager>(new OverlayManagerBuffered(rOutputDevice));
392
0
        }
393
394
        OverlayManagerBuffered::~OverlayManagerBuffered()
395
0
        {
396
            // Clear timer
397
0
            maBufferIdle.Stop();
398
399
0
            if(!maBufferRememberedRangePixel.isEmpty())
400
0
            {
401
                // Restore all rectangles for remembered region from buffer
402
0
                ImpRestoreBackground();
403
0
            }
404
0
        }
405
406
        void OverlayManagerBuffered::completeRedraw(const vcl::Region& rRegion, OutputDevice* pPreRenderDevice) const
407
0
        {
408
            // remember MapMode of last full completeRedraw to
409
            // have it available in timer-based updates (see
410
            // ImpBufferTimerHandler)
411
0
            maMapModeLastCompleteRedraw = getOutputDevice().GetMapMode();
412
413
0
            if(!rRegion.IsEmpty())
414
0
            {
415
                // save new background
416
0
                const_cast<OverlayManagerBuffered*>(this)->ImpSaveBackground(rRegion, pPreRenderDevice);
417
0
            }
418
419
            // call parent
420
0
            OverlayManager::completeRedraw(rRegion, pPreRenderDevice);
421
0
        }
422
423
        void OverlayManagerBuffered::invalidateRange(const basegfx::B2DRange& rRange)
424
0
        {
425
0
            if(rRange.isEmpty())
426
0
                return;
427
428
0
            if (maMapModeLastCompleteRedraw == MapMode())
429
                // there was no CompleteRedraw yet, done
430
0
                return;
431
432
            // buffered output, do not invalidate but use the timer
433
            // to trigger a timer event for refresh
434
0
            maBufferIdle.Start();
435
436
            // tdf#166520 need to also use MapModeLastCompleteRedraw for
437
            // the invalidates -> Calc is just completely unstable using
438
            // MapModes at any Window. The one remembered at full
439
            // repaint has to be the DrawingLayer one - overlay works
440
            // in DrawingLayer coordinates/MapMode(s)
441
0
            const MapMode aPrevMapMode(getOutputDevice().GetMapMode());
442
0
            const bool bPatchMapMode(aPrevMapMode != maMapModeLastCompleteRedraw);
443
0
            if (bPatchMapMode)
444
0
                getOutputDevice().SetMapMode(maMapModeLastCompleteRedraw);
445
446
            // add the discrete range to the remembered region
447
            // #i75163# use double precision and floor/ceil rounding to get overlapped pixel region, even
448
            // when the given logic region has a width/height of 0.0. This does NOT work with LogicToPixel
449
            // since it just transforms the top left and bottom right points equally without taking
450
            // discrete pixel coverage into account. An empty B2DRange and thus empty logic Rectangle translated
451
            // to an also empty discrete pixel rectangle - what is wrong.
452
0
            basegfx::B2DRange aDiscreteRange(rRange);
453
0
            aDiscreteRange.transform(getOutputDevice().GetViewTransformation());
454
455
0
            if(getCurrentViewInformation2D().getUseAntiAliasing())
456
0
            {
457
                // assume AA needs one pixel more and invalidate one pixel more
458
0
                const double fDiscreteOne(getDiscreteOne());
459
0
                const basegfx::B2IPoint aTopLeft(
460
0
                    static_cast<sal_Int32>(floor(aDiscreteRange.getMinX() - fDiscreteOne)),
461
0
                    static_cast<sal_Int32>(floor(aDiscreteRange.getMinY() - fDiscreteOne)));
462
0
                const basegfx::B2IPoint aBottomRight(
463
0
                    static_cast<sal_Int32>(ceil(aDiscreteRange.getMaxX() + fDiscreteOne)),
464
0
                    static_cast<sal_Int32>(ceil(aDiscreteRange.getMaxY() + fDiscreteOne)));
465
466
0
                maBufferRememberedRangePixel.expand(aTopLeft);
467
0
                maBufferRememberedRangePixel.expand(aBottomRight);
468
0
            }
469
0
            else
470
0
            {
471
0
                const basegfx::B2IPoint aTopLeft(static_cast<sal_Int32>(floor(aDiscreteRange.getMinX())), static_cast<sal_Int32>(floor(aDiscreteRange.getMinY())));
472
0
                const basegfx::B2IPoint aBottomRight(static_cast<sal_Int32>(ceil(aDiscreteRange.getMaxX())), static_cast<sal_Int32>(ceil(aDiscreteRange.getMaxY())));
473
474
0
                maBufferRememberedRangePixel.expand(aTopLeft);
475
0
                maBufferRememberedRangePixel.expand(aBottomRight);
476
0
            }
477
478
            // restore evtl. temporarily patched MapMode (see explanation
479
            // above)
480
0
            if (bPatchMapMode)
481
0
                getOutputDevice().SetMapMode(aPrevMapMode);
482
0
        }
483
} // end of namespace
484
485
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */