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>
27 #include <svtools/optionsdrawinglayer.hxx>
30 namespace sdr::overlay
32 void OverlayManagerBuffered::ImpPrepareBufferDevice()
34 // compare size of mpBufferDevice with size of visible area
35 if(mpBufferDevice
->GetOutputSizePixel() != getOutputDevice().GetOutputSizePixel())
37 // set new buffer size, copy as much content as possible (use bool parameter for vcl).
38 // Newly uncovered regions will be repainted.
39 mpBufferDevice
->SetOutputSizePixel(getOutputDevice().GetOutputSizePixel(), false);
42 // compare the MapModes for zoom/scroll changes
43 if(mpBufferDevice
->GetMapMode() != getOutputDevice().GetMapMode())
46 mpBufferDevice
->GetMapMode().GetScaleX() != getOutputDevice().GetMapMode().GetScaleX()
47 || mpBufferDevice
->GetMapMode().GetScaleY() != getOutputDevice().GetMapMode().GetScaleY());
51 const Point
& rOriginOld
= mpBufferDevice
->GetMapMode().GetOrigin();
52 const Point
& rOriginNew
= getOutputDevice().GetMapMode().GetOrigin();
53 const bool bScrolled(rOriginOld
!= rOriginNew
);
58 // get pixel bounds (tdf#149322 do subtraction in logic units before converting result back to pixel)
59 const Point
aLogicOriginDiff(rOriginNew
- rOriginOld
);
60 const Size
aPixelOriginDiff(mpBufferDevice
->LogicToPixel(Size(aLogicOriginDiff
.X(), aLogicOriginDiff
.Y())));
61 const Point
aDestinationOffsetPixel(aPixelOriginDiff
.Width(), aPixelOriginDiff
.Height());
62 const Size
aOutputSizePixel(mpBufferDevice
->GetOutputSizePixel());
64 // remember and switch off MapMode
65 const bool bMapModeWasEnabled(mpBufferDevice
->IsMapModeEnabled());
66 mpBufferDevice
->EnableMapMode(false);
68 // scroll internally buffered stuff
69 mpBufferDevice
->DrawOutDev(
70 aDestinationOffsetPixel
, aOutputSizePixel
, // destination
71 Point(), aOutputSizePixel
); // source
74 mpBufferDevice
->EnableMapMode(bMapModeWasEnabled
);
76 // scroll remembered region, too.
77 if(!maBufferRememberedRangePixel
.isEmpty())
79 const basegfx::B2IPoint
aIPointDestinationOffsetPixel(aDestinationOffsetPixel
.X(), aDestinationOffsetPixel
.Y());
80 const basegfx::B2IPoint
aNewMinimum(maBufferRememberedRangePixel
.getMinimum() + aIPointDestinationOffsetPixel
);
81 const basegfx::B2IPoint
aNewMaximum(maBufferRememberedRangePixel
.getMaximum() + aIPointDestinationOffsetPixel
);
82 maBufferRememberedRangePixel
= basegfx::B2IRange(aNewMinimum
, aNewMaximum
);
88 mpBufferDevice
->SetMapMode(getOutputDevice().GetMapMode());
92 mpBufferDevice
->SetDrawMode(getOutputDevice().GetDrawMode());
93 mpBufferDevice
->SetSettings(getOutputDevice().GetSettings());
94 mpBufferDevice
->SetAntialiasing(getOutputDevice().GetAntialiasing());
97 void OverlayManagerBuffered::ImpRestoreBackground() const
99 const tools::Rectangle
aRegionRectanglePixel(
100 maBufferRememberedRangePixel
.getMinX(), maBufferRememberedRangePixel
.getMinY(),
101 maBufferRememberedRangePixel
.getMaxX(), maBufferRememberedRangePixel
.getMaxY());
102 const vcl::Region
aRegionPixel(aRegionRectanglePixel
);
104 ImpRestoreBackground(aRegionPixel
);
107 void OverlayManagerBuffered::ImpRestoreBackground(const vcl::Region
& rRegionPixel
) const
110 const bool bMapModeWasEnabledDest(getOutputDevice().IsMapModeEnabled());
111 const bool bMapModeWasEnabledSource(mpBufferDevice
->IsMapModeEnabled());
112 getOutputDevice().EnableMapMode(false);
113 const_cast<OverlayManagerBuffered
*>(this)->mpBufferDevice
->EnableMapMode(false);
116 RectangleVector aRectangles
;
117 rRegionPixel
.GetRegionRectangles(aRectangles
);
119 for(const auto& rRect
: aRectangles
)
122 const Point
aTopLeft(rRect
.TopLeft());
123 const Size
aSize(rRect
.GetSize());
125 getOutputDevice().DrawOutDev(
126 aTopLeft
, aSize
, // destination
127 aTopLeft
, aSize
, // source
132 getOutputDevice().EnableMapMode(bMapModeWasEnabledDest
);
133 const_cast<OverlayManagerBuffered
*>(this)->mpBufferDevice
->EnableMapMode(bMapModeWasEnabledSource
);
136 void OverlayManagerBuffered::ImpSaveBackground(const vcl::Region
& rRegion
, OutputDevice
* pPreRenderDevice
)
139 OutputDevice
& rSource
= pPreRenderDevice
? *pPreRenderDevice
: getOutputDevice();
141 // Ensure buffer is valid
142 ImpPrepareBufferDevice();
144 // build region which needs to be copied
145 vcl::Region
aRegion(rSource
.LogicToPixel(rRegion
));
147 // limit to PaintRegion if it's a window. This will be evtl. the expanded one,
148 // but always the exact redraw area
149 if(OUTDEV_WINDOW
== rSource
.GetOutDevType())
151 vcl::Window
& rWindow
= *rSource
.GetOwnerWindow();
152 vcl::Region aPaintRegionPixel
= rWindow
.LogicToPixel(rWindow
.GetPaintRegion());
153 aRegion
.Intersect(aPaintRegionPixel
);
155 // #i72754# Make sure content is completely rendered, the window
156 // will be used as source of a DrawOutDev soon
157 rWindow
.GetOutDev()->Flush();
160 // also limit to buffer size
161 const tools::Rectangle
aBufferDeviceRectanglePixel(Point(), mpBufferDevice
->GetOutputSizePixel());
162 aRegion
.Intersect(aBufferDeviceRectanglePixel
);
165 const bool bMapModeWasEnabledDest(rSource
.IsMapModeEnabled());
166 const bool bMapModeWasEnabledSource(mpBufferDevice
->IsMapModeEnabled());
167 rSource
.EnableMapMode(false);
168 mpBufferDevice
->EnableMapMode(false);
170 // prepare to iterate over the rectangles from the region in pixels
171 RectangleVector aRectangles
;
172 aRegion
.GetRegionRectangles(aRectangles
);
174 for(const auto& rRect
: aRectangles
)
176 // for each rectangle, save the area
177 const Point
aTopLeft(rRect
.TopLeft());
178 const Size
aSize(rRect
.GetSize());
180 mpBufferDevice
->DrawOutDev(
181 aTopLeft
, aSize
, // destination
182 aTopLeft
, aSize
, // source
187 rSource
.EnableMapMode(bMapModeWasEnabledDest
);
188 mpBufferDevice
->EnableMapMode(bMapModeWasEnabledSource
);
191 IMPL_LINK_NOARG(OverlayManagerBuffered
, ImpBufferTimerHandler
, Timer
*, void)
193 //Resolves: fdo#46728 ensure this exists until end of scope
194 rtl::Reference
<OverlayManager
> xKeepAlive(this);
199 if(maBufferRememberedRangePixel
.isEmpty())
202 // logic size for impDrawMember call
203 basegfx::B2DRange
aBufferRememberedRangeLogic(
204 maBufferRememberedRangePixel
.getMinX(), maBufferRememberedRangePixel
.getMinY(),
205 maBufferRememberedRangePixel
.getMaxX(), maBufferRememberedRangePixel
.getMaxY());
206 aBufferRememberedRangeLogic
.transform(getOutputDevice().GetInverseViewTransformation());
208 // prepare cursor handling
209 const bool bTargetIsWindow(OUTDEV_WINDOW
== mrOutputDevice
.GetOutDevType());
210 bool bCursorWasEnabled(false);
212 // #i80730# switch off VCL cursor during overlay refresh
215 vcl::Window
& rWindow
= *mrOutputDevice
.GetOwnerWindow();
216 vcl::Cursor
* pCursor
= rWindow
.GetCursor();
218 if(pCursor
&& pCursor
->IsVisible())
221 bCursorWasEnabled
= true;
225 // refresh with prerendering
227 // #i73602# ensure valid and sized mpOutputBufferDevice
228 const Size
aDestinationSizePixel(mpBufferDevice
->GetOutputSizePixel());
229 const Size
aOutputBufferSizePixel(mpOutputBufferDevice
->GetOutputSizePixel());
231 if(aDestinationSizePixel
!= aOutputBufferSizePixel
)
233 mpOutputBufferDevice
->SetOutputSizePixel(aDestinationSizePixel
);
236 mpOutputBufferDevice
->SetMapMode(getOutputDevice().GetMapMode());
237 mpOutputBufferDevice
->EnableMapMode(false);
238 mpOutputBufferDevice
->SetDrawMode(mpBufferDevice
->GetDrawMode());
239 mpOutputBufferDevice
->SetSettings(mpBufferDevice
->GetSettings());
240 mpOutputBufferDevice
->SetAntialiasing(mpBufferDevice
->GetAntialiasing());
243 tools::Rectangle
aRegionRectanglePixel(
244 maBufferRememberedRangePixel
.getMinX(), maBufferRememberedRangePixel
.getMinY(),
245 maBufferRememberedRangePixel
.getMaxX(), maBufferRememberedRangePixel
.getMaxY());
247 // truncate aRegionRectanglePixel to destination pixel size, more does
248 // not need to be prepared since destination is a buffer for a window. So,
249 // maximum size indirectly shall be limited to getOutputDevice().GetOutputSizePixel()
250 if(aRegionRectanglePixel
.Left() < 0)
252 aRegionRectanglePixel
.SetLeft( 0 );
255 if(aRegionRectanglePixel
.Top() < 0)
257 aRegionRectanglePixel
.SetTop( 0 );
260 if(aRegionRectanglePixel
.Right() > aDestinationSizePixel
.getWidth())
262 aRegionRectanglePixel
.SetRight( aDestinationSizePixel
.getWidth() );
265 if(aRegionRectanglePixel
.Bottom() > aDestinationSizePixel
.getHeight())
267 aRegionRectanglePixel
.SetBottom( aDestinationSizePixel
.getHeight() );
271 const Point
aTopLeft(aRegionRectanglePixel
.TopLeft());
272 const Size
aSize(aRegionRectanglePixel
.GetSize());
275 const bool bMapModeWasEnabledDest(mpBufferDevice
->IsMapModeEnabled());
276 mpBufferDevice
->EnableMapMode(false);
278 mpOutputBufferDevice
->DrawOutDev(
279 aTopLeft
, aSize
, // destination
280 aTopLeft
, aSize
, // source
284 mpBufferDevice
->EnableMapMode(bMapModeWasEnabledDest
);
287 // paint overlay content for remembered region, use
288 // method from base class directly
289 mpOutputBufferDevice
->EnableMapMode();
290 OverlayManager::ImpDrawMembers(aBufferRememberedRangeLogic
, *mpOutputBufferDevice
);
291 mpOutputBufferDevice
->EnableMapMode(false);
295 const bool bMapModeWasEnabledDest(getOutputDevice().IsMapModeEnabled());
296 getOutputDevice().EnableMapMode(false);
298 getOutputDevice().DrawOutDev(
299 aTopLeft
, aSize
, // destination
300 aTopLeft
, aSize
, // source
301 *mpOutputBufferDevice
);
304 /*getOutputDevice().SetLineCOL_RED);
305 getOutputDevice().SetFillColor();
306 getOutputDevice().DrawRect(Rectangle(aTopLeft, aSize));*/
309 getOutputDevice().EnableMapMode(bMapModeWasEnabledDest
);
313 // VCL hack for transparent child windows
314 // Problem is e.g. a radiobutton form control in life mode. The used window
315 // is a transparence vcl childwindow. This flag only allows the parent window to
316 // paint into the child windows area, but there is no mechanism which takes
317 // care for a repaint of the child window. A transparent child window is NOT
318 // a window which always keeps it's content consistent over the parent, but it's
319 // more like just a paint flag for the parent.
320 // To get the update, the windows in question are updated manually here.
323 vcl::Window
& rWindow
= *mrOutputDevice
.GetOwnerWindow();
325 const tools::Rectangle
aRegionRectanglePixel(
326 maBufferRememberedRangePixel
.getMinX(),
327 maBufferRememberedRangePixel
.getMinY(),
328 maBufferRememberedRangePixel
.getMaxX(),
329 maBufferRememberedRangePixel
.getMaxY());
330 PaintTransparentChildren(rWindow
, aRegionRectanglePixel
);
333 // #i80730# restore visibility of VCL cursor
334 if(bCursorWasEnabled
)
336 vcl::Window
& rWindow
= *mrOutputDevice
.GetOwnerWindow();
337 vcl::Cursor
* pCursor
= rWindow
.GetCursor();
341 // check if cursor still exists. It may have been deleted from someone
346 // forget remembered Region
347 maBufferRememberedRangePixel
.reset();
350 OverlayManagerBuffered::OverlayManagerBuffered(
351 OutputDevice
& rOutputDevice
)
352 : OverlayManager(rOutputDevice
),
353 mpBufferDevice(VclPtr
<VirtualDevice
>::Create()),
354 mpOutputBufferDevice(VclPtr
<VirtualDevice
>::Create()),
355 maBufferIdle( "sdr::overlay::OverlayManagerBuffered maBufferIdle" )
358 maBufferIdle
.SetPriority( TaskPriority::POST_PAINT
);
359 maBufferIdle
.SetInvokeHandler(LINK(this, OverlayManagerBuffered
, ImpBufferTimerHandler
));
362 rtl::Reference
<OverlayManager
> OverlayManagerBuffered::create(
363 OutputDevice
& rOutputDevice
)
365 return rtl::Reference
<OverlayManager
>(new OverlayManagerBuffered(rOutputDevice
));
368 OverlayManagerBuffered::~OverlayManagerBuffered()
373 if(!maBufferRememberedRangePixel
.isEmpty())
375 // Restore all rectangles for remembered region from buffer
376 ImpRestoreBackground();
380 void OverlayManagerBuffered::completeRedraw(const vcl::Region
& rRegion
, OutputDevice
* pPreRenderDevice
) const
382 if(!rRegion
.IsEmpty())
384 // save new background
385 const_cast<OverlayManagerBuffered
*>(this)->ImpSaveBackground(rRegion
, pPreRenderDevice
);
389 OverlayManager::completeRedraw(rRegion
, pPreRenderDevice
);
392 void OverlayManagerBuffered::flush()
394 // call timer handler direct
395 ImpBufferTimerHandler(nullptr);
398 void OverlayManagerBuffered::invalidateRange(const basegfx::B2DRange
& rRange
)
403 // buffered output, do not invalidate but use the timer
404 // to trigger a timer event for refresh
405 maBufferIdle
.Start();
407 // add the discrete range to the remembered region
408 // #i75163# use double precision and floor/ceil rounding to get overlapped pixel region, even
409 // when the given logic region has a width/height of 0.0. This does NOT work with LogicToPixel
410 // since it just transforms the top left and bottom right points equally without taking
411 // discrete pixel coverage into account. An empty B2DRange and thus empty logic Rectangle translated
412 // to an also empty discrete pixel rectangle - what is wrong.
413 basegfx::B2DRange
aDiscreteRange(rRange
);
414 aDiscreteRange
.transform(getOutputDevice().GetViewTransformation());
416 if(getCurrentViewInformation2D().getUseAntiAliasing())
418 // assume AA needs one pixel more and invalidate one pixel more
419 const double fDiscreteOne(getDiscreteOne());
420 const basegfx::B2IPoint
aTopLeft(
421 static_cast<sal_Int32
>(floor(aDiscreteRange
.getMinX() - fDiscreteOne
)),
422 static_cast<sal_Int32
>(floor(aDiscreteRange
.getMinY() - fDiscreteOne
)));
423 const basegfx::B2IPoint
aBottomRight(
424 static_cast<sal_Int32
>(ceil(aDiscreteRange
.getMaxX() + fDiscreteOne
)),
425 static_cast<sal_Int32
>(ceil(aDiscreteRange
.getMaxY() + fDiscreteOne
)));
427 maBufferRememberedRangePixel
.expand(aTopLeft
);
428 maBufferRememberedRangePixel
.expand(aBottomRight
);
432 const basegfx::B2IPoint
aTopLeft(static_cast<sal_Int32
>(floor(aDiscreteRange
.getMinX())), static_cast<sal_Int32
>(floor(aDiscreteRange
.getMinY())));
433 const basegfx::B2IPoint
aBottomRight(static_cast<sal_Int32
>(ceil(aDiscreteRange
.getMaxX())), static_cast<sal_Int32
>(ceil(aDiscreteRange
.getMaxY())));
435 maBufferRememberedRangePixel
.expand(aTopLeft
);
436 maBufferRememberedRangePixel
.expand(aBottomRight
);
439 } // end of namespace
441 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */