Get the style color and number just once
[LibreOffice.git] / svx / source / sdr / overlay / overlaymanagerbuffered.cxx
bloba58ec819200449133505ea0d8680619aa2591b07
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
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())
44 const bool bZoomed(
45 mpBufferDevice->GetMapMode().GetScaleX() != getOutputDevice().GetMapMode().GetScaleX()
46 || mpBufferDevice->GetMapMode().GetScaleY() != getOutputDevice().GetMapMode().GetScaleY());
48 if(!bZoomed)
50 const Point& rOriginOld = mpBufferDevice->GetMapMode().GetOrigin();
51 const Point& rOriginNew = getOutputDevice().GetMapMode().GetOrigin();
52 const bool bScrolled(rOriginOld != rOriginNew);
55 if(bScrolled)
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
72 // restore MapMode
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);
86 // copy new MapMode
87 mpBufferDevice->SetMapMode(getOutputDevice().GetMapMode());
90 // #i29186#
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
108 // MapModes off
109 const bool bMapModeWasEnabledDest(getOutputDevice().IsMapModeEnabled());
110 const bool bMapModeWasEnabledSource(mpBufferDevice->IsMapModeEnabled());
111 getOutputDevice().EnableMapMode(false);
112 const_cast<OverlayManagerBuffered*>(this)->mpBufferDevice->EnableMapMode(false);
114 // local region
115 RectangleVector aRectangles;
116 rRegionPixel.GetRegionRectangles(aRectangles);
118 for(const auto& rRect : aRectangles)
120 // restore the area
121 const Point aTopLeft(rRect.TopLeft());
122 const Size aSize(rRect.GetSize());
124 getOutputDevice().DrawOutDev(
125 aTopLeft, aSize, // destination
126 aTopLeft, aSize, // source
127 *mpBufferDevice);
130 // restore MapModes
131 getOutputDevice().EnableMapMode(bMapModeWasEnabledDest);
132 const_cast<OverlayManagerBuffered*>(this)->mpBufferDevice->EnableMapMode(bMapModeWasEnabledSource);
135 void OverlayManagerBuffered::ImpSaveBackground(const vcl::Region& rRegion, OutputDevice* pPreRenderDevice)
137 // prepare source
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);
163 // MapModes off
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
182 rSource);
185 // restore MapModes
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);
195 // stop timer
196 maBufferIdle.Stop();
198 if(maBufferRememberedRangePixel.isEmpty())
199 return;
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
212 if(bTargetIsWindow)
214 vcl::Window& rWindow = *mrOutputDevice.GetOwnerWindow();
215 vcl::Cursor* pCursor = rWindow.GetCursor();
217 if(pCursor && pCursor->IsVisible())
219 pCursor->Hide();
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());
241 // calculate sizes
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() );
269 // get sizes
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
280 *mpBufferDevice);
282 // restore MapModes
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);
292 // copy to output
294 const bool bMapModeWasEnabledDest(getOutputDevice().IsMapModeEnabled());
295 getOutputDevice().EnableMapMode(false);
297 getOutputDevice().DrawOutDev(
298 aTopLeft, aSize, // destination
299 aTopLeft, aSize, // source
300 *mpOutputBufferDevice);
302 // debug
303 /*getOutputDevice().SetLineCOL_RED);
304 getOutputDevice().SetFillColor();
305 getOutputDevice().DrawRect(Rectangle(aTopLeft, aSize));*/
307 // restore MapModes
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.
320 if(bTargetIsWindow)
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();
338 if(pCursor)
340 // check if cursor still exists. It may have been deleted from someone
341 pCursor->Show();
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" )
356 // Init timer
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()
369 // Clear timer
370 maBufferIdle.Stop();
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);
387 // call parent
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)
399 if(rRange.isEmpty())
400 return;
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);
429 else
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: */