LibreOffice Module svx (master)  1
overlaymanagerbuffered.cxx
Go to the documentation of this file.
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 
21 #include <svx/sdrpaintwindow.hxx>
22 #include <vcl/outdev.hxx>
24 #include <vcl/window.hxx>
25 #include <tools/fract.hxx>
26 #include <vcl/cursor.hxx>
28 
29 
30 namespace sdr::overlay
31 {
33  {
34  // compare size of mpBufferDevice with size of visible area
35  if(mpBufferDevice->GetOutputSizePixel() != getOutputDevice().GetOutputSizePixel())
36  {
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);
40  }
41 
42  // compare the MapModes for zoom/scroll changes
43  if(mpBufferDevice->GetMapMode() != getOutputDevice().GetMapMode())
44  {
45  const bool bZoomed(
46  mpBufferDevice->GetMapMode().GetScaleX() != getOutputDevice().GetMapMode().GetScaleX()
47  || mpBufferDevice->GetMapMode().GetScaleY() != getOutputDevice().GetMapMode().GetScaleY());
48 
49  if(!bZoomed)
50  {
51  const Point& rOriginOld = mpBufferDevice->GetMapMode().GetOrigin();
52  const Point& rOriginNew = getOutputDevice().GetMapMode().GetOrigin();
53  const bool bScrolled(rOriginOld != rOriginNew);
54 
55  if(bScrolled)
56  {
57  // get pixel bounds
58  const Point aOriginOldPixel(mpBufferDevice->LogicToPixel(rOriginOld));
59  const Point aOriginNewPixel(mpBufferDevice->LogicToPixel(rOriginNew));
60  const Size aOutputSizePixel(mpBufferDevice->GetOutputSizePixel());
61 
62  // remember and switch off MapMode
63  const bool bMapModeWasEnabled(mpBufferDevice->IsMapModeEnabled());
64  mpBufferDevice->EnableMapMode(false);
65 
66  // scroll internally buffered stuff
67  const Point aDestinationOffsetPixel(aOriginNewPixel - aOriginOldPixel);
68  mpBufferDevice->DrawOutDev(
69  aDestinationOffsetPixel, aOutputSizePixel, // destination
70  Point(), aOutputSizePixel); // source
71 
72  // restore MapMode
73  mpBufferDevice->EnableMapMode(bMapModeWasEnabled);
74 
75  // scroll remembered region, too.
77  {
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);
82  }
83  }
84  }
85 
86  // copy new MapMode
87  mpBufferDevice->SetMapMode(getOutputDevice().GetMapMode());
88  }
89 
90  // #i29186#
91  mpBufferDevice->SetDrawMode(getOutputDevice().GetDrawMode());
92  mpBufferDevice->SetSettings(getOutputDevice().GetSettings());
93  mpBufferDevice->SetAntialiasing(getOutputDevice().GetAntialiasing());
94  }
95 
97  {
98  const tools::Rectangle aRegionRectanglePixel(
101  const vcl::Region aRegionPixel(aRegionRectanglePixel);
102 
103  ImpRestoreBackground(aRegionPixel);
104  }
105 
107  {
108  // MapModes off
109  const bool bMapModeWasEnabledDest(getOutputDevice().IsMapModeEnabled());
110  const bool bMapModeWasEnabledSource(mpBufferDevice->IsMapModeEnabled());
112  const_cast<OverlayManagerBuffered*>(this)->mpBufferDevice->EnableMapMode(false);
113 
114  // local region
115  RectangleVector aRectangles;
116  rRegionPixel.GetRegionRectangles(aRectangles);
117 
118  for(const auto& rRect : aRectangles)
119  {
120  // restore the area
121  const Point aTopLeft(rRect.TopLeft());
122  const Size aSize(rRect.GetSize());
123 
125  aTopLeft, aSize, // destination
126  aTopLeft, aSize, // source
127  *mpBufferDevice);
128  }
129 
130  // restore MapModes
131  getOutputDevice().EnableMapMode(bMapModeWasEnabledDest);
132  const_cast<OverlayManagerBuffered*>(this)->mpBufferDevice->EnableMapMode(bMapModeWasEnabledSource);
133  }
134 
136  {
137  // prepare source
138  OutputDevice& rSource = pPreRenderDevice ? *pPreRenderDevice : getOutputDevice();
139 
140  // Ensure buffer is valid
142 
143  // build region which needs to be copied
144  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  if(OUTDEV_WINDOW == rSource.GetOutDevType())
149  {
150  vcl::Window& rWindow = *rSource.GetOwnerWindow();
151  vcl::Region aPaintRegionPixel = rWindow.LogicToPixel(rWindow.GetPaintRegion());
152  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  rWindow.GetOutDev()->Flush();
157  }
158 
159  // also limit to buffer size
160  const tools::Rectangle aBufferDeviceRectanglePixel(Point(), mpBufferDevice->GetOutputSizePixel());
161  aRegion.Intersect(aBufferDeviceRectanglePixel);
162 
163  // MapModes off
164  const bool bMapModeWasEnabledDest(rSource.IsMapModeEnabled());
165  const bool bMapModeWasEnabledSource(mpBufferDevice->IsMapModeEnabled());
166  rSource.EnableMapMode(false);
167  mpBufferDevice->EnableMapMode(false);
168 
169  // prepare to iterate over the rectangles from the region in pixels
170  RectangleVector aRectangles;
171  aRegion.GetRegionRectangles(aRectangles);
172 
173  for(const auto& rRect : aRectangles)
174  {
175  // for each rectangle, save the area
176  const Point aTopLeft(rRect.TopLeft());
177  const Size aSize(rRect.GetSize());
178 
179  mpBufferDevice->DrawOutDev(
180  aTopLeft, aSize, // destination
181  aTopLeft, aSize, // source
182  rSource);
183  }
184 
185  // restore MapModes
186  rSource.EnableMapMode(bMapModeWasEnabledDest);
187  mpBufferDevice->EnableMapMode(bMapModeWasEnabledSource);
188  }
189 
190  IMPL_LINK_NOARG(OverlayManagerBuffered, ImpBufferTimerHandler, Timer*, void)
191  {
192  //Resolves: fdo#46728 ensure this exists until end of scope
193  rtl::Reference<OverlayManager> xKeepAlive(this);
194 
195  // stop timer
196  maBufferIdle.Stop();
197 
198  if(maBufferRememberedRangePixel.isEmpty())
199  return;
200 
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());
206 
207  // prepare cursor handling
208  const bool bTargetIsWindow(OUTDEV_WINDOW == mrOutputDevice.GetOutDevType());
209  bool bCursorWasEnabled(false);
210 
211  // #i80730# switch off VCL cursor during overlay refresh
212  if(bTargetIsWindow)
213  {
214  vcl::Window& rWindow = *mrOutputDevice.GetOwnerWindow();
215  vcl::Cursor* pCursor = rWindow.GetCursor();
216 
217  if(pCursor && pCursor->IsVisible())
218  {
219  pCursor->Hide();
220  bCursorWasEnabled = true;
221  }
222  }
223 
224  // refresh with prerendering
225  {
226  // #i73602# ensure valid and sized mpOutputBufferDevice
227  const Size aDestinationSizePixel(mpBufferDevice->GetOutputSizePixel());
228  const Size aOutputBufferSizePixel(mpOutputBufferDevice->GetOutputSizePixel());
229 
230  if(aDestinationSizePixel != aOutputBufferSizePixel)
231  {
232  mpOutputBufferDevice->SetOutputSizePixel(aDestinationSizePixel);
233  }
234 
235  mpOutputBufferDevice->SetMapMode(getOutputDevice().GetMapMode());
236  mpOutputBufferDevice->EnableMapMode(false);
237  mpOutputBufferDevice->SetDrawMode(mpBufferDevice->GetDrawMode());
238  mpOutputBufferDevice->SetSettings(mpBufferDevice->GetSettings());
239  mpOutputBufferDevice->SetAntialiasing(mpBufferDevice->GetAntialiasing());
240 
241  // calculate sizes
242  tools::Rectangle aRegionRectanglePixel(
243  maBufferRememberedRangePixel.getMinX(), maBufferRememberedRangePixel.getMinY(),
244  maBufferRememberedRangePixel.getMaxX(), maBufferRememberedRangePixel.getMaxY());
245 
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)
250  {
251  aRegionRectanglePixel.SetLeft( 0 );
252  }
253 
254  if(aRegionRectanglePixel.Top() < 0)
255  {
256  aRegionRectanglePixel.SetTop( 0 );
257  }
258 
259  if(aRegionRectanglePixel.Right() > aDestinationSizePixel.getWidth())
260  {
261  aRegionRectanglePixel.SetRight( aDestinationSizePixel.getWidth() );
262  }
263 
264  if(aRegionRectanglePixel.Bottom() > aDestinationSizePixel.getHeight())
265  {
266  aRegionRectanglePixel.SetBottom( aDestinationSizePixel.getHeight() );
267  }
268 
269  // get sizes
270  const Point aTopLeft(aRegionRectanglePixel.TopLeft());
271  const Size aSize(aRegionRectanglePixel.GetSize());
272 
273  {
274  const bool bMapModeWasEnabledDest(mpBufferDevice->IsMapModeEnabled());
275  mpBufferDevice->EnableMapMode(false);
276 
277  mpOutputBufferDevice->DrawOutDev(
278  aTopLeft, aSize, // destination
279  aTopLeft, aSize, // source
280  *mpBufferDevice);
281 
282  // restore MapModes
283  mpBufferDevice->EnableMapMode(bMapModeWasEnabledDest);
284  }
285 
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);
291 
292  // copy to output
293  {
294  const bool bMapModeWasEnabledDest(getOutputDevice().IsMapModeEnabled());
295  getOutputDevice().EnableMapMode(false);
296 
297  getOutputDevice().DrawOutDev(
298  aTopLeft, aSize, // destination
299  aTopLeft, aSize, // source
300  *mpOutputBufferDevice);
301 
302  // debug
303  /*getOutputDevice().SetLineCOL_RED);
304  getOutputDevice().SetFillColor();
305  getOutputDevice().DrawRect(Rectangle(aTopLeft, aSize));*/
306 
307  // restore MapModes
308  getOutputDevice().EnableMapMode(bMapModeWasEnabledDest);
309  }
310  }
311 
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)
321  {
322  vcl::Window& rWindow = *mrOutputDevice.GetOwnerWindow();
323 
324  const tools::Rectangle aRegionRectanglePixel(
325  maBufferRememberedRangePixel.getMinX(),
326  maBufferRememberedRangePixel.getMinY(),
327  maBufferRememberedRangePixel.getMaxX(),
328  maBufferRememberedRangePixel.getMaxY());
329  PaintTransparentChildren(rWindow, aRegionRectanglePixel);
330  }
331 
332  // #i80730# restore visibility of VCL cursor
333  if(bCursorWasEnabled)
334  {
335  vcl::Window& rWindow = *mrOutputDevice.GetOwnerWindow();
336  vcl::Cursor* pCursor = rWindow.GetCursor();
337 
338  if(pCursor)
339  {
340  // check if cursor still exists. It may have been deleted from someone
341  pCursor->Show();
342  }
343  }
344 
345  // forget remembered Region
346  maBufferRememberedRangePixel.reset();
347  }
348 
350  OutputDevice& rOutputDevice)
351  : OverlayManager(rOutputDevice),
352  mpBufferDevice(VclPtr<VirtualDevice>::Create()),
353  mpOutputBufferDevice(VclPtr<VirtualDevice>::Create()),
354  maBufferIdle( "sdr::overlay::OverlayManagerBuffered maBufferIdle" )
355  {
356  // Init timer
357  maBufferIdle.SetPriority( TaskPriority::POST_PAINT );
358  maBufferIdle.SetInvokeHandler(LINK(this, OverlayManagerBuffered, ImpBufferTimerHandler));
359  }
360 
362  OutputDevice& rOutputDevice)
363  {
364  return rtl::Reference<OverlayManager>(new OverlayManagerBuffered(rOutputDevice));
365  }
366 
368  {
369  // Clear timer
370  maBufferIdle.Stop();
371 
373  {
374  // Restore all rectangles for remembered region from buffer
376  }
377  }
378 
379  void OverlayManagerBuffered::completeRedraw(const vcl::Region& rRegion, OutputDevice* pPreRenderDevice) const
380  {
381  if(!rRegion.IsEmpty())
382  {
383  // save new background
384  const_cast<OverlayManagerBuffered*>(this)->ImpSaveBackground(rRegion, pPreRenderDevice);
385  }
386 
387  // call parent
388  OverlayManager::completeRedraw(rRegion, pPreRenderDevice);
389  }
390 
392  {
393  // call timer handler direct
394  ImpBufferTimerHandler(nullptr);
395  }
396 
398  {
399  if(rRange.isEmpty())
400  return;
401 
402  // buffered output, do not invalidate but use the timer
403  // to trigger a timer event for refresh
405 
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());
414 
416  {
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)));
425 
427  maBufferRememberedRangePixel.expand(aBottomRight);
428  }
429  else
430  {
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())));
433 
435  maBufferRememberedRangePixel.expand(aBottomRight);
436  }
437  }
438 } // end of namespace
439 
440 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
const Fraction & GetScaleX() const
OutDevType GetOutDevType() const
virtual void Flush()
ScopedVclPtr< VirtualDevice > mpBufferDevice
void Create(SwFormatVertOrient &rItem, SvStream &rStrm, sal_uInt16 nVersionAbusedAsSize)
bool IsMapModeEnabled() const
const MapMode & GetMapMode() const
vcl::Region GetPaintRegion() const
void EnableMapMode(bool bEnable=true)
void PaintTransparentChildren(vcl::Window const &rWindow, tools::Rectangle const &rPixelRect)
paint the transparent children of rWin that overlap rPixelRect (for example, transparent form control...
std::vector< tools::Rectangle > RectangleVector
OUTDEV_WINDOW
const Fraction & GetScaleY() const
BASEGFX_DLLPUBLIC void transform(const B2DHomMatrix &rMatrix)
constexpr void SetLeft(tools::Long v)
bool IsEmpty() const
Point LogicToPixel(const Point &rLogicPt) const
virtual void completeRedraw(const vcl::Region &rRegion, OutputDevice *pPreRenderDevice=nullptr) const override
B2IPoint getMinimum() const
void ImpSaveBackground(const vcl::Region &rRegion, OutputDevice *pPreRenderDevice)
static rtl::Reference< OverlayManager > create(OutputDevice &rOutputDevice)
vcl::Cursor * GetCursor() const
Size GetOutputSizePixel() const
SAL_WARN_UNUSED_RESULT Point LogicToPixel(const Point &rLogicPt) const
virtual void invalidateRange(const basegfx::B2DRange &rRange) override
OutputDevice & getOutputDevice() const
void Stop()
virtual void completeRedraw(const vcl::Region &rRegion, OutputDevice *pPreRenderDevice=nullptr) const
OverlayManagerBuffered(OutputDevice &rOutputDevice)
void ImpDrawMembers(const basegfx::B2DRange &rRange, OutputDevice &rDestinationDevice) const
::OutputDevice const * GetOutDev() const
void GetRegionRectangles(RectangleVector &rTarget) const
void SetInvokeHandler(const Link< Timer *, void > &rLink)
const Point & GetOrigin() const
bool IsVisible() const
virtual void Start(bool bStartTimer=true) override
void SetPriority(TaskPriority ePriority)
virtual vcl::Window * GetOwnerWindow() const
void expand(const Tuple2D< sal_Int32 > &rTuple)
SAL_DLLPRIVATE void DrawOutDev(const Point &, const Size &, const Point &, const Size &, const Printer &)=delete
IMPL_LINK_NOARG(OverlayManagerBuffered, ImpBufferTimerHandler, Timer *, void)
B2IPoint getMaximum() const