1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
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/.
9 * This file incorporates work covered by the following license notice:
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 .
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>
29 namespace sdr::overlay
31 void OverlayManagerBuffered::ImpPrepareBufferDevice()
33 // compare size of mpBufferDevice with size of visible area
34 if(mpBufferDevice
->GetOutputSizePixel() != getOutputDevice().GetOutputSizePixel())
36 // set new buffer size, copy as much content as possible (use bool parameter for vcl).
37 // Newly uncovered regions will be repainted.
38 mpBufferDevice
->SetOutputSizePixel(getOutputDevice().GetOutputSizePixel(), false);
41 // compare the MapModes for zoom/scroll changes
42 if(mpBufferDevice
->GetMapMode() != getOutputDevice().GetMapMode())
45 mpBufferDevice
->GetMapMode().GetScaleX() != getOutputDevice().GetMapMode().GetScaleX()
46 || mpBufferDevice
->GetMapMode().GetScaleY() != getOutputDevice().GetMapMode().GetScaleY());
50 const Point
& rOriginOld
= mpBufferDevice
->GetMapMode().GetOrigin();
51 const Point
& rOriginNew
= getOutputDevice().GetMapMode().GetOrigin();
52 const bool bScrolled(rOriginOld
!= rOriginNew
);
57 // get pixel bounds (tdf#149322 do subtraction in logic units before converting result back to pixel)
58 const Point
aLogicOriginDiff(rOriginNew
- rOriginOld
);
59 const Size
aPixelOriginDiff(mpBufferDevice
->LogicToPixel(Size(aLogicOriginDiff
.X(), aLogicOriginDiff
.Y())));
60 const Point
aDestinationOffsetPixel(aPixelOriginDiff
.Width(), aPixelOriginDiff
.Height());
61 const Size
aOutputSizePixel(mpBufferDevice
->GetOutputSizePixel());
63 // remember and switch off MapMode
64 const bool bMapModeWasEnabled(mpBufferDevice
->IsMapModeEnabled());
65 mpBufferDevice
->EnableMapMode(false);
67 // scroll internally buffered stuff
68 mpBufferDevice
->DrawOutDev(
69 aDestinationOffsetPixel
, aOutputSizePixel
, // destination
70 Point(), aOutputSizePixel
); // source
73 mpBufferDevice
->EnableMapMode(bMapModeWasEnabled
);
75 // scroll remembered region, too.
76 if(!maBufferRememberedRangePixel
.isEmpty())
78 const basegfx::B2IPoint
aIPointDestinationOffsetPixel(aDestinationOffsetPixel
.X(), aDestinationOffsetPixel
.Y());
79 const basegfx::B2IPoint
aNewMinimum(maBufferRememberedRangePixel
.getMinimum() + aIPointDestinationOffsetPixel
);
80 const basegfx::B2IPoint
aNewMaximum(maBufferRememberedRangePixel
.getMaximum() + aIPointDestinationOffsetPixel
);
81 maBufferRememberedRangePixel
= basegfx::B2IRange(aNewMinimum
, aNewMaximum
);
87 mpBufferDevice
->SetMapMode(getOutputDevice().GetMapMode());
91 mpBufferDevice
->SetDrawMode(getOutputDevice().GetDrawMode());
92 mpBufferDevice
->SetSettings(getOutputDevice().GetSettings());
93 mpBufferDevice
->SetAntialiasing(getOutputDevice().GetAntialiasing());
96 void OverlayManagerBuffered::ImpRestoreBackground() const
98 const tools::Rectangle
aRegionRectanglePixel(
99 maBufferRememberedRangePixel
.getMinX(), maBufferRememberedRangePixel
.getMinY(),
100 maBufferRememberedRangePixel
.getMaxX(), maBufferRememberedRangePixel
.getMaxY());
101 const vcl::Region
aRegionPixel(aRegionRectanglePixel
);
103 ImpRestoreBackground(aRegionPixel
);
106 void OverlayManagerBuffered::ImpRestoreBackground(const vcl::Region
& rRegionPixel
) const
109 const bool bMapModeWasEnabledDest(getOutputDevice().IsMapModeEnabled());
110 const bool bMapModeWasEnabledSource(mpBufferDevice
->IsMapModeEnabled());
111 getOutputDevice().EnableMapMode(false);
112 const_cast<OverlayManagerBuffered
*>(this)->mpBufferDevice
->EnableMapMode(false);
115 RectangleVector aRectangles
;
116 rRegionPixel
.GetRegionRectangles(aRectangles
);
118 for(const auto& rRect
: aRectangles
)
121 const Point
aTopLeft(rRect
.TopLeft());
122 const Size
aSize(rRect
.GetSize());
124 getOutputDevice().DrawOutDev(
125 aTopLeft
, aSize
, // destination
126 aTopLeft
, aSize
, // source
131 getOutputDevice().EnableMapMode(bMapModeWasEnabledDest
);
132 const_cast<OverlayManagerBuffered
*>(this)->mpBufferDevice
->EnableMapMode(bMapModeWasEnabledSource
);
135 void OverlayManagerBuffered::ImpSaveBackground(const vcl::Region
& rRegion
, OutputDevice
* pPreRenderDevice
)
138 OutputDevice
& rSource
= pPreRenderDevice
? *pPreRenderDevice
: getOutputDevice();
140 // Ensure buffer is valid
141 ImpPrepareBufferDevice();
143 // build region which needs to be copied
144 vcl::Region
aRegion(rSource
.LogicToPixel(rRegion
));
146 // limit to PaintRegion if it's a window. This will be evtl. the expanded one,
147 // but always the exact redraw area
148 if(OUTDEV_WINDOW
== rSource
.GetOutDevType())
150 vcl::Window
& rWindow
= *rSource
.GetOwnerWindow();
151 vcl::Region aPaintRegionPixel
= rWindow
.LogicToPixel(rWindow
.GetPaintRegion());
152 aRegion
.Intersect(aPaintRegionPixel
);
154 // #i72754# Make sure content is completely rendered, the window
155 // will be used as source of a DrawOutDev soon
156 rWindow
.GetOutDev()->Flush();
159 // also limit to buffer size
160 const tools::Rectangle
aBufferDeviceRectanglePixel(Point(), mpBufferDevice
->GetOutputSizePixel());
161 aRegion
.Intersect(aBufferDeviceRectanglePixel
);
164 const bool bMapModeWasEnabledDest(rSource
.IsMapModeEnabled());
165 const bool bMapModeWasEnabledSource(mpBufferDevice
->IsMapModeEnabled());
166 rSource
.EnableMapMode(false);
167 mpBufferDevice
->EnableMapMode(false);
169 // prepare to iterate over the rectangles from the region in pixels
170 RectangleVector aRectangles
;
171 aRegion
.GetRegionRectangles(aRectangles
);
173 for(const auto& rRect
: aRectangles
)
175 // for each rectangle, save the area
176 const Point
aTopLeft(rRect
.TopLeft());
177 const Size
aSize(rRect
.GetSize());
179 mpBufferDevice
->DrawOutDev(
180 aTopLeft
, aSize
, // destination
181 aTopLeft
, aSize
, // source
186 rSource
.EnableMapMode(bMapModeWasEnabledDest
);
187 mpBufferDevice
->EnableMapMode(bMapModeWasEnabledSource
);
190 IMPL_LINK_NOARG(OverlayManagerBuffered
, ImpBufferTimerHandler
, Timer
*, void)
192 //Resolves: fdo#46728 ensure this exists until end of scope
193 rtl::Reference
<OverlayManager
> xKeepAlive(this);
198 if(maBufferRememberedRangePixel
.isEmpty())
201 // logic size for impDrawMember call
202 basegfx::B2DRange
aBufferRememberedRangeLogic(
203 maBufferRememberedRangePixel
.getMinX(), maBufferRememberedRangePixel
.getMinY(),
204 maBufferRememberedRangePixel
.getMaxX(), maBufferRememberedRangePixel
.getMaxY());
205 aBufferRememberedRangeLogic
.transform(getOutputDevice().GetInverseViewTransformation());
207 // prepare cursor handling
208 const bool bTargetIsWindow(OUTDEV_WINDOW
== mrOutputDevice
.GetOutDevType());
209 bool bCursorWasEnabled(false);
211 // #i80730# switch off VCL cursor during overlay refresh
214 vcl::Window
& rWindow
= *mrOutputDevice
.GetOwnerWindow();
215 vcl::Cursor
* pCursor
= rWindow
.GetCursor();
217 if(pCursor
&& pCursor
->IsVisible())
220 bCursorWasEnabled
= true;
224 // refresh with prerendering
226 // #i73602# ensure valid and sized mpOutputBufferDevice
227 const Size
aDestinationSizePixel(mpBufferDevice
->GetOutputSizePixel());
228 const Size
aOutputBufferSizePixel(mpOutputBufferDevice
->GetOutputSizePixel());
230 if(aDestinationSizePixel
!= aOutputBufferSizePixel
)
232 mpOutputBufferDevice
->SetOutputSizePixel(aDestinationSizePixel
);
235 mpOutputBufferDevice
->SetMapMode(getOutputDevice().GetMapMode());
236 mpOutputBufferDevice
->EnableMapMode(false);
237 mpOutputBufferDevice
->SetDrawMode(mpBufferDevice
->GetDrawMode());
238 mpOutputBufferDevice
->SetSettings(mpBufferDevice
->GetSettings());
239 mpOutputBufferDevice
->SetAntialiasing(mpBufferDevice
->GetAntialiasing());
242 tools::Rectangle
aRegionRectanglePixel(
243 maBufferRememberedRangePixel
.getMinX(), maBufferRememberedRangePixel
.getMinY(),
244 maBufferRememberedRangePixel
.getMaxX(), maBufferRememberedRangePixel
.getMaxY());
246 // truncate aRegionRectanglePixel to destination pixel size, more does
247 // not need to be prepared since destination is a buffer for a window. So,
248 // maximum size indirectly shall be limited to getOutputDevice().GetOutputSizePixel()
249 if(aRegionRectanglePixel
.Left() < 0)
251 aRegionRectanglePixel
.SetLeft( 0 );
254 if(aRegionRectanglePixel
.Top() < 0)
256 aRegionRectanglePixel
.SetTop( 0 );
259 if(aRegionRectanglePixel
.Right() > aDestinationSizePixel
.getWidth())
261 aRegionRectanglePixel
.SetRight( aDestinationSizePixel
.getWidth() );
264 if(aRegionRectanglePixel
.Bottom() > aDestinationSizePixel
.getHeight())
266 aRegionRectanglePixel
.SetBottom( aDestinationSizePixel
.getHeight() );
270 const Point
aTopLeft(aRegionRectanglePixel
.TopLeft());
271 const Size
aSize(aRegionRectanglePixel
.GetSize());
274 const bool bMapModeWasEnabledDest(mpBufferDevice
->IsMapModeEnabled());
275 mpBufferDevice
->EnableMapMode(false);
277 mpOutputBufferDevice
->DrawOutDev(
278 aTopLeft
, aSize
, // destination
279 aTopLeft
, aSize
, // source
283 mpBufferDevice
->EnableMapMode(bMapModeWasEnabledDest
);
286 // paint overlay content for remembered region, use
287 // method from base class directly
288 mpOutputBufferDevice
->EnableMapMode();
289 OverlayManager::ImpDrawMembers(aBufferRememberedRangeLogic
, *mpOutputBufferDevice
);
290 mpOutputBufferDevice
->EnableMapMode(false);
294 const bool bMapModeWasEnabledDest(getOutputDevice().IsMapModeEnabled());
295 getOutputDevice().EnableMapMode(false);
297 getOutputDevice().DrawOutDev(
298 aTopLeft
, aSize
, // destination
299 aTopLeft
, aSize
, // source
300 *mpOutputBufferDevice
);
303 /*getOutputDevice().SetLineCOL_RED);
304 getOutputDevice().SetFillColor();
305 getOutputDevice().DrawRect(Rectangle(aTopLeft, aSize));*/
308 getOutputDevice().EnableMapMode(bMapModeWasEnabledDest
);
312 // VCL hack for transparent child windows
313 // Problem is e.g. a radiobutton form control in life mode. The used window
314 // is a transparence vcl childwindow. This flag only allows the parent window to
315 // paint into the child windows area, but there is no mechanism which takes
316 // care for a repaint of the child window. A transparent child window is NOT
317 // a window which always keeps it's content consistent over the parent, but it's
318 // more like just a paint flag for the parent.
319 // To get the update, the windows in question are updated manually here.
322 vcl::Window
& rWindow
= *mrOutputDevice
.GetOwnerWindow();
324 const tools::Rectangle
aRegionRectanglePixel(
325 maBufferRememberedRangePixel
.getMinX(),
326 maBufferRememberedRangePixel
.getMinY(),
327 maBufferRememberedRangePixel
.getMaxX(),
328 maBufferRememberedRangePixel
.getMaxY());
329 PaintTransparentChildren(rWindow
, aRegionRectanglePixel
);
332 // #i80730# restore visibility of VCL cursor
333 if(bCursorWasEnabled
)
335 vcl::Window
& rWindow
= *mrOutputDevice
.GetOwnerWindow();
336 vcl::Cursor
* pCursor
= rWindow
.GetCursor();
340 // check if cursor still exists. It may have been deleted from someone
345 // forget remembered Region
346 maBufferRememberedRangePixel
.reset();
349 OverlayManagerBuffered::OverlayManagerBuffered(
350 OutputDevice
& rOutputDevice
)
351 : OverlayManager(rOutputDevice
),
352 mpBufferDevice(VclPtr
<VirtualDevice
>::Create()),
353 mpOutputBufferDevice(VclPtr
<VirtualDevice
>::Create()),
354 maBufferIdle( "sdr::overlay::OverlayManagerBuffered maBufferIdle" )
357 maBufferIdle
.SetPriority( TaskPriority::POST_PAINT
);
358 maBufferIdle
.SetInvokeHandler(LINK(this, OverlayManagerBuffered
, ImpBufferTimerHandler
));
361 rtl::Reference
<OverlayManager
> OverlayManagerBuffered::create(
362 OutputDevice
& rOutputDevice
)
364 return rtl::Reference
<OverlayManager
>(new OverlayManagerBuffered(rOutputDevice
));
367 OverlayManagerBuffered::~OverlayManagerBuffered()
372 if(!maBufferRememberedRangePixel
.isEmpty())
374 // Restore all rectangles for remembered region from buffer
375 ImpRestoreBackground();
379 void OverlayManagerBuffered::completeRedraw(const vcl::Region
& rRegion
, OutputDevice
* pPreRenderDevice
) const
381 if(!rRegion
.IsEmpty())
383 // save new background
384 const_cast<OverlayManagerBuffered
*>(this)->ImpSaveBackground(rRegion
, pPreRenderDevice
);
388 OverlayManager::completeRedraw(rRegion
, pPreRenderDevice
);
391 void OverlayManagerBuffered::flush()
393 // call timer handler direct
394 ImpBufferTimerHandler(nullptr);
397 void OverlayManagerBuffered::invalidateRange(const basegfx::B2DRange
& rRange
)
402 // buffered output, do not invalidate but use the timer
403 // to trigger a timer event for refresh
404 maBufferIdle
.Start();
406 // add the discrete range to the remembered region
407 // #i75163# use double precision and floor/ceil rounding to get overlapped pixel region, even
408 // when the given logic region has a width/height of 0.0. This does NOT work with LogicToPixel
409 // since it just transforms the top left and bottom right points equally without taking
410 // discrete pixel coverage into account. An empty B2DRange and thus empty logic Rectangle translated
411 // to an also empty discrete pixel rectangle - what is wrong.
412 basegfx::B2DRange
aDiscreteRange(rRange
);
413 aDiscreteRange
.transform(getOutputDevice().GetViewTransformation());
415 if(getCurrentViewInformation2D().getUseAntiAliasing())
417 // assume AA needs one pixel more and invalidate one pixel more
418 const double fDiscreteOne(getDiscreteOne());
419 const basegfx::B2IPoint
aTopLeft(
420 static_cast<sal_Int32
>(floor(aDiscreteRange
.getMinX() - fDiscreteOne
)),
421 static_cast<sal_Int32
>(floor(aDiscreteRange
.getMinY() - fDiscreteOne
)));
422 const basegfx::B2IPoint
aBottomRight(
423 static_cast<sal_Int32
>(ceil(aDiscreteRange
.getMaxX() + fDiscreteOne
)),
424 static_cast<sal_Int32
>(ceil(aDiscreteRange
.getMaxY() + fDiscreteOne
)));
426 maBufferRememberedRangePixel
.expand(aTopLeft
);
427 maBufferRememberedRangePixel
.expand(aBottomRight
);
431 const basegfx::B2IPoint
aTopLeft(static_cast<sal_Int32
>(floor(aDiscreteRange
.getMinX())), static_cast<sal_Int32
>(floor(aDiscreteRange
.getMinY())));
432 const basegfx::B2IPoint
aBottomRight(static_cast<sal_Int32
>(ceil(aDiscreteRange
.getMaxX())), static_cast<sal_Int32
>(ceil(aDiscreteRange
.getMaxY())));
434 maBufferRememberedRangePixel
.expand(aTopLeft
);
435 maBufferRememberedRangePixel
.expand(aBottomRight
);
438 } // end of namespace
440 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */