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 <config_features.h>
21 
22 #include <svx/charthelper.hxx>
23 #include <svx/sdr/contact/viewobjectcontact.hxx>
24 #include <svx/sdr/contact/viewcontact.hxx>
25 #include <svx/sdr/contact/objectcontact.hxx>
26 #include <svx/sdr/contact/displayinfo.hxx>
27 #include <vcl/region.hxx>
28 #include <svx/sdr/animation/objectanimator.hxx>
29 #include <svx/sdr/animation/animationstate.hxx>
30 #include <svx/sdr/contact/viewobjectcontactredirector.hxx>
31 #include <basegfx/numeric/ftools.hxx>
32 #include <basegfx/color/bcolor.hxx>
33 #include <drawinglayer/primitive2d/modifiedcolorprimitive2d.hxx>
34 #include <basegfx/utils/canvastools.hxx>
35 #include <drawinglayer/primitive2d/animatedprimitive2d.hxx>
36 #include <drawinglayer/processor2d/baseprocessor2d.hxx>
37 #include <svx/sdr/primitive2d/svx_primitivetypes2d.hxx>
38 #include <svx/svdoole2.hxx>
39 #include <sdr/contact/viewcontactofsdrole2obj.hxx>
40 #include <drawinglayer/primitive2d/transformprimitive2d.hxx>
41 
42 using namespace com::sun::star;
43 
44 namespace {
45 
46 // animated extractor
47 
48 // Necessary to filter a sequence of animated primitives from
49 // a sequence of primitives to find out if animated or not. The decision for
50 // what to decompose is hard-coded and only done for knowingly animated primitives
51 // to not decompose too deeply and unnecessarily. This implies that the list
52 // which is view-specific needs to be expanded by hand when new animated objects
53 // are added. This may eventually be changed to a dynamically configurable approach
54 // if necessary.
55 class AnimatedExtractingProcessor2D : public drawinglayer::processor2d::BaseProcessor2D
56 {
57 protected:
58     // the found animated primitives
59     drawinglayer::primitive2d::Primitive2DContainer  maPrimitive2DSequence;
60 
61     // text animation allowed?
62     bool const                                       mbTextAnimationAllowed : 1;
63 
64     // graphic animation allowed?
65     bool const                                       mbGraphicAnimationAllowed : 1;
66 
67     // as tooling, the process() implementation takes over API handling and calls this
68     // virtual render method when the primitive implementation is BasePrimitive2D-based.
69     virtual void processBasePrimitive2D(const drawinglayer::primitive2d::BasePrimitive2D& rCandidate) override;
70 
71 public:
72     AnimatedExtractingProcessor2D(
73         const drawinglayer::geometry::ViewInformation2D& rViewInformation,
74         bool bTextAnimationAllowed,
75         bool bGraphicAnimationAllowed);
76 
77     // data access
getPrimitive2DSequence() const78     const drawinglayer::primitive2d::Primitive2DContainer& getPrimitive2DSequence() const { return maPrimitive2DSequence; }
isTextAnimationAllowed() const79     bool isTextAnimationAllowed() const { return mbTextAnimationAllowed; }
isGraphicAnimationAllowed() const80     bool isGraphicAnimationAllowed() const { return mbGraphicAnimationAllowed; }
81 };
82 
AnimatedExtractingProcessor2D(const drawinglayer::geometry::ViewInformation2D & rViewInformation,bool bTextAnimationAllowed,bool bGraphicAnimationAllowed)83 AnimatedExtractingProcessor2D::AnimatedExtractingProcessor2D(
84     const drawinglayer::geometry::ViewInformation2D& rViewInformation,
85     bool bTextAnimationAllowed,
86     bool bGraphicAnimationAllowed)
87 :   drawinglayer::processor2d::BaseProcessor2D(rViewInformation),
88     maPrimitive2DSequence(),
89     mbTextAnimationAllowed(bTextAnimationAllowed),
90     mbGraphicAnimationAllowed(bGraphicAnimationAllowed)
91 {
92 }
93 
processBasePrimitive2D(const drawinglayer::primitive2d::BasePrimitive2D & rCandidate)94 void AnimatedExtractingProcessor2D::processBasePrimitive2D(const drawinglayer::primitive2d::BasePrimitive2D& rCandidate)
95 {
96     // known implementation, access directly
97     switch(rCandidate.getPrimitive2DID())
98     {
99         // add and accept animated primitives directly, no need to decompose
100         case PRIMITIVE2D_ID_ANIMATEDSWITCHPRIMITIVE2D :
101         case PRIMITIVE2D_ID_ANIMATEDBLINKPRIMITIVE2D :
102         case PRIMITIVE2D_ID_ANIMATEDINTERPOLATEPRIMITIVE2D :
103         {
104             const drawinglayer::primitive2d::AnimatedSwitchPrimitive2D& rSwitchPrimitive = static_cast< const drawinglayer::primitive2d::AnimatedSwitchPrimitive2D& >(rCandidate);
105 
106             if((rSwitchPrimitive.isTextAnimation() && isTextAnimationAllowed())
107                 || (rSwitchPrimitive.isGraphicAnimation() && isGraphicAnimationAllowed()))
108             {
109                 const drawinglayer::primitive2d::Primitive2DReference xReference(const_cast< drawinglayer::primitive2d::BasePrimitive2D* >(&rCandidate));
110                 maPrimitive2DSequence.push_back(xReference);
111             }
112             break;
113         }
114 
115         // decompose animated gifs where SdrGrafPrimitive2D produces a GraphicPrimitive2D
116         // which then produces the animation infos (all when used/needed)
117         case PRIMITIVE2D_ID_SDRGRAFPRIMITIVE2D :
118         case PRIMITIVE2D_ID_GRAPHICPRIMITIVE2D :
119 
120         // decompose SdrObjects with evtl. animated text
121         case PRIMITIVE2D_ID_SDRCAPTIONPRIMITIVE2D :
122         case PRIMITIVE2D_ID_SDRCONNECTORPRIMITIVE2D :
123         case PRIMITIVE2D_ID_SDRCUSTOMSHAPEPRIMITIVE2D :
124         case PRIMITIVE2D_ID_SDRELLIPSEPRIMITIVE2D :
125         case PRIMITIVE2D_ID_SDRELLIPSESEGMENTPRIMITIVE2D :
126         case PRIMITIVE2D_ID_SDRMEASUREPRIMITIVE2D :
127         case PRIMITIVE2D_ID_SDRPATHPRIMITIVE2D :
128         case PRIMITIVE2D_ID_SDRRECTANGLEPRIMITIVE2D :
129 
130         // #121194# With Graphic as Bitmap FillStyle, also check
131         // for primitives filled with animated graphics
132         case PRIMITIVE2D_ID_POLYPOLYGONGRAPHICPRIMITIVE2D:
133         case PRIMITIVE2D_ID_FILLGRAPHICPRIMITIVE2D:
134         case PRIMITIVE2D_ID_TRANSFORMPRIMITIVE2D:
135 
136         // decompose evtl. animated text contained in MaskPrimitive2D
137         // or group primitives
138         case PRIMITIVE2D_ID_MASKPRIMITIVE2D :
139         case PRIMITIVE2D_ID_GROUPPRIMITIVE2D :
140         {
141             process(rCandidate);
142             break;
143         }
144 
145         default :
146         {
147             // nothing to do for the rest
148             break;
149         }
150     }
151 }
152 
153 } // end of anonymous namespace
154 
155 namespace sdr { namespace contact {
156 
ViewObjectContact(ObjectContact & rObjectContact,ViewContact & rViewContact)157 ViewObjectContact::ViewObjectContact(ObjectContact& rObjectContact, ViewContact& rViewContact)
158 :   mrObjectContact(rObjectContact),
159     mrViewContact(rViewContact),
160     maObjectRange(),
161     mxPrimitive2DSequence(),
162     maGridOffset(0.0, 0.0),
163     mbLazyInvalidate(false)
164 {
165     // make the ViewContact remember me
166     mrViewContact.AddViewObjectContact(*this);
167 
168     // make the ObjectContact remember me
169     mrObjectContact.AddViewObjectContact(*this);
170 }
171 
~ViewObjectContact()172 ViewObjectContact::~ViewObjectContact()
173 {
174     // invalidate in view
175     if(!maObjectRange.isEmpty())
176     {
177         GetObjectContact().InvalidatePartOfView(maObjectRange);
178     }
179 
180     // delete PrimitiveAnimation
181     mpPrimitiveAnimation.reset();
182 
183     // take care of remembered ObjectContact. Remove from
184     // OC first. The VC removal (below) CAN trigger a StopGettingViewed()
185     // which (depending of its implementation) may destroy other OCs. This
186     // can trigger the deletion of the helper OC of a page visualising object
187     // which IS the OC of this object. Eventually StopGettingViewed() needs
188     // to get asynchron later
189     GetObjectContact().RemoveViewObjectContact(*this);
190 
191     // take care of remembered ViewContact
192     GetViewContact().RemoveViewObjectContact(*this);
193 }
194 
getObjectRange() const195 const basegfx::B2DRange& ViewObjectContact::getObjectRange() const
196 {
197     if(maObjectRange.isEmpty())
198     {
199         const drawinglayer::geometry::ViewInformation2D& rViewInfo2D = GetObjectContact().getViewInformation2D();
200         basegfx::B2DRange aTempRange = GetViewContact().getRange(rViewInfo2D);
201         if (!aTempRange.isEmpty())
202         {
203             const_cast< ViewObjectContact* >(this)->maObjectRange = aTempRange;
204         }
205         else
206         {
207             // if range is not computed (new or LazyInvalidate objects), force it
208             const DisplayInfo aDisplayInfo;
209             const drawinglayer::primitive2d::Primitive2DContainer xSequence(getPrimitive2DSequence(aDisplayInfo));
210 
211             if(!xSequence.empty())
212             {
213                 const_cast< ViewObjectContact* >(this)->maObjectRange =
214                     xSequence.getB2DRange(rViewInfo2D);
215             }
216         }
217     }
218 
219     return maObjectRange;
220 }
221 
ActionChanged()222 void ViewObjectContact::ActionChanged()
223 {
224     if(!mbLazyInvalidate)
225     {
226         // set local flag
227         mbLazyInvalidate = true;
228 
229         // force ObjectRange
230         getObjectRange();
231 
232         if(!maObjectRange.isEmpty())
233         {
234             // invalidate current valid range
235             GetObjectContact().InvalidatePartOfView(maObjectRange);
236 
237             // reset ObjectRange, it needs to be recalculated
238             maObjectRange.reset();
239         }
240 
241         // register at OC for lazy invalidate
242         GetObjectContact().setLazyInvalidate(*this);
243     }
244 }
245 
triggerLazyInvalidate()246 void ViewObjectContact::triggerLazyInvalidate()
247 {
248     if(mbLazyInvalidate)
249     {
250         // reset flag
251         mbLazyInvalidate = false;
252 
253         // force ObjectRange
254         getObjectRange();
255 
256         if(!maObjectRange.isEmpty())
257         {
258             // invalidate current valid range
259             GetObjectContact().InvalidatePartOfView(maObjectRange);
260         }
261     }
262 }
263 
264 // Take some action when new objects are inserted
ActionChildInserted(ViewContact & rChild)265 void ViewObjectContact::ActionChildInserted(ViewContact& rChild)
266 {
267     // force creation of the new VOC and trigger it's refresh, so it
268     // will take part in LazyInvalidate immediately
269     rChild.GetViewObjectContact(GetObjectContact()).ActionChanged();
270 
271     // forward action to ObjectContact
272     // const ViewObjectContact& rChildVOC = rChild.GetViewObjectContact(GetObjectContact());
273     // GetObjectContact().InvalidatePartOfView(rChildVOC.getObjectRange());
274 }
275 
checkForPrimitive2DAnimations()276 void ViewObjectContact::checkForPrimitive2DAnimations()
277 {
278     // remove old one
279     mpPrimitiveAnimation.reset();
280 
281     // check for animated primitives
282     if(!mxPrimitive2DSequence.empty())
283     {
284         const bool bTextAnimationAllowed(GetObjectContact().IsTextAnimationAllowed());
285         const bool bGraphicAnimationAllowed(GetObjectContact().IsGraphicAnimationAllowed());
286 
287         if(bTextAnimationAllowed || bGraphicAnimationAllowed)
288         {
289             AnimatedExtractingProcessor2D aAnimatedExtractor(GetObjectContact().getViewInformation2D(),
290                 bTextAnimationAllowed, bGraphicAnimationAllowed);
291             aAnimatedExtractor.process(mxPrimitive2DSequence);
292 
293             if(!aAnimatedExtractor.getPrimitive2DSequence().empty())
294             {
295                 // derived primitiveList is animated, setup new PrimitiveAnimation
296                 mpPrimitiveAnimation.reset( new sdr::animation::PrimitiveAnimation(*this, aAnimatedExtractor.getPrimitive2DSequence()) );
297             }
298         }
299     }
300 }
301 
createPrimitive2DSequence(const DisplayInfo & rDisplayInfo) const302 drawinglayer::primitive2d::Primitive2DContainer ViewObjectContact::createPrimitive2DSequence(const DisplayInfo& rDisplayInfo) const
303 {
304     // get the view-independent Primitive from the viewContact
305     drawinglayer::primitive2d::Primitive2DContainer xRetval(GetViewContact().getViewIndependentPrimitive2DContainer());
306 
307     if(!xRetval.empty())
308     {
309         // handle GluePoint
310         if(!GetObjectContact().isOutputToPrinter() && GetObjectContact().AreGluePointsVisible())
311         {
312             const drawinglayer::primitive2d::Primitive2DContainer xGlue(GetViewContact().createGluePointPrimitive2DSequence());
313 
314             if(!xGlue.empty())
315             {
316                 xRetval.append(xGlue);
317             }
318         }
319 
320         // handle ghosted
321         if(isPrimitiveGhosted(rDisplayInfo))
322         {
323             const basegfx::BColor aRGBWhite(1.0, 1.0, 1.0);
324             const basegfx::BColorModifierSharedPtr aBColorModifier(
325                 new basegfx::BColorModifier_interpolate(
326                     aRGBWhite,
327                     0.5));
328             const drawinglayer::primitive2d::Primitive2DReference xReference(
329                 new drawinglayer::primitive2d::ModifiedColorPrimitive2D(
330                     xRetval,
331                     aBColorModifier));
332 
333             xRetval = drawinglayer::primitive2d::Primitive2DContainer { xReference };
334         }
335     }
336 
337     return xRetval;
338 }
339 
getPrimitive2DSequence(const DisplayInfo & rDisplayInfo) const340 drawinglayer::primitive2d::Primitive2DContainer const & ViewObjectContact::getPrimitive2DSequence(const DisplayInfo& rDisplayInfo) const
341 {
342     drawinglayer::primitive2d::Primitive2DContainer xNewPrimitiveSequence;
343 
344     // take care of redirectors and create new list
345     ViewObjectContactRedirector* pRedirector = GetObjectContact().GetViewObjectContactRedirector();
346 
347     if(pRedirector)
348     {
349         xNewPrimitiveSequence = pRedirector->createRedirectedPrimitive2DSequence(*this, rDisplayInfo);
350     }
351     else
352     {
353         xNewPrimitiveSequence = createPrimitive2DSequence(rDisplayInfo);
354     }
355 
356     // local up-to-date checks. New list different from local one?
357     if(mxPrimitive2DSequence != xNewPrimitiveSequence)
358     {
359         // has changed, copy content
360         const_cast< ViewObjectContact* >(this)->mxPrimitive2DSequence = xNewPrimitiveSequence;
361 
362         // check for animated stuff
363         const_cast< ViewObjectContact* >(this)->checkForPrimitive2DAnimations();
364 
365         // always update object range when PrimitiveSequence changes
366         const drawinglayer::geometry::ViewInformation2D& rViewInformation2D(GetObjectContact().getViewInformation2D());
367         const_cast< ViewObjectContact* >(this)->maObjectRange = mxPrimitive2DSequence.getB2DRange(rViewInformation2D);
368 
369         // check and eventually embed to GridOffset transform primitive
370         if(GetObjectContact().supportsGridOffsets())
371         {
372             const basegfx::B2DVector& rGridOffset(getGridOffset());
373 
374             if(0.0 != rGridOffset.getX() || 0.0 != rGridOffset.getY())
375             {
376                 const basegfx::B2DHomMatrix aTranslateGridOffset(
377                     basegfx::utils::createTranslateB2DHomMatrix(
378                         rGridOffset));
379                 const drawinglayer::primitive2d::Primitive2DReference aEmbed(
380                     new drawinglayer::primitive2d::TransformPrimitive2D(
381                         aTranslateGridOffset,
382                         mxPrimitive2DSequence));
383 
384                 // Set values at local data. So for now, the mechanism is to reset some of the
385                 // defining things (mxPrimitive2DSequence, maGridOffset) and re-create the
386                 // buffered data (including maObjectRange). It *could* be changed to keep
387                 // the unmodified PrimitiveSequence and only update the GridOffset, but this
388                 // would require a 2nd instance of maObjectRange and mxPrimitive2DSequence. I
389                 // started doing so, but it just makes the code more complicated. For now,
390                 // just allow re-creation of the PrimitiveSequence (and removing buffered
391                 // decomposed content of it). May be optimized, though. OTOH it only happens
392                 // in calc which traditionally does not have a huge amount of DrawObjects anyways.
393                 const_cast< ViewObjectContact* >(this)->mxPrimitive2DSequence = drawinglayer::primitive2d::Primitive2DContainer { aEmbed };
394                 const_cast< ViewObjectContact* >(this)->maObjectRange.transform(aTranslateGridOffset);
395             }
396         }
397     }
398 
399     // return current Primitive2DContainer
400     return mxPrimitive2DSequence;
401 }
402 
isPrimitiveVisible(const DisplayInfo &) const403 bool ViewObjectContact::isPrimitiveVisible(const DisplayInfo& /*rDisplayInfo*/) const
404 {
405     // default: always visible
406     return true;
407 }
408 
isPrimitiveGhosted(const DisplayInfo & rDisplayInfo) const409 bool ViewObjectContact::isPrimitiveGhosted(const DisplayInfo& rDisplayInfo) const
410 {
411     // default: standard check
412     return (GetObjectContact().DoVisualizeEnteredGroup() && !GetObjectContact().isOutputToPrinter() && rDisplayInfo.IsGhostedDrawModeActive());
413 }
414 
getPrimitive2DSequenceHierarchy(DisplayInfo & rDisplayInfo) const415 drawinglayer::primitive2d::Primitive2DContainer ViewObjectContact::getPrimitive2DSequenceHierarchy(DisplayInfo& rDisplayInfo) const
416 {
417     drawinglayer::primitive2d::Primitive2DContainer xRetval;
418 
419     // check model-view visibility
420     if(isPrimitiveVisible(rDisplayInfo))
421     {
422         xRetval = getPrimitive2DSequence(rDisplayInfo);
423 
424         if(!xRetval.empty())
425         {
426             // get ranges
427             const drawinglayer::geometry::ViewInformation2D& rViewInformation2D(GetObjectContact().getViewInformation2D());
428             const basegfx::B2DRange aObjectRange(xRetval.getB2DRange(rViewInformation2D));
429             const basegfx::B2DRange aViewRange(rViewInformation2D.getViewport());
430 
431             // check geometrical visibility
432             bool bVisible = aViewRange.isEmpty() || aViewRange.overlaps(aObjectRange);
433             if(!bVisible)
434             {
435                 // not visible, release
436                 xRetval.clear();
437             }
438         }
439     }
440 
441     return xRetval;
442 }
443 
getPrimitive2DSequenceSubHierarchy(DisplayInfo & rDisplayInfo) const444 drawinglayer::primitive2d::Primitive2DContainer ViewObjectContact::getPrimitive2DSequenceSubHierarchy(DisplayInfo& rDisplayInfo) const
445 {
446     const sal_uInt32 nSubHierarchyCount(GetViewContact().GetObjectCount());
447     drawinglayer::primitive2d::Primitive2DContainer xSeqRetval;
448 
449     for(sal_uInt32 a(0); a < nSubHierarchyCount; a++)
450     {
451         const ViewObjectContact& rCandidate(GetViewContact().GetViewContact(a).GetViewObjectContact(GetObjectContact()));
452 
453         xSeqRetval.append(rCandidate.getPrimitive2DSequenceHierarchy(rDisplayInfo));
454     }
455 
456     return xSeqRetval;
457 }
458 
459 // Support getting a GridOffset per object and view for non-linear ViewToDevice
460 // transformation (calc). On-demand created by delegating to the ObjectContact
461 // (->View) that has then all needed information
getGridOffset() const462 const basegfx::B2DVector& ViewObjectContact::getGridOffset() const
463 {
464     if(0.0 == maGridOffset.getX() && 0.0 == maGridOffset.getY() && GetObjectContact().supportsGridOffsets())
465     {
466         // create on-demand
467         GetObjectContact().calculateGridOffsetForViewOjectContact(const_cast<ViewObjectContact*>(this)->maGridOffset, *this);
468     }
469 
470     return maGridOffset;
471 }
472 
resetGridOffset()473 void ViewObjectContact::resetGridOffset()
474 {
475     // reset buffered GridOffset itself
476     maGridOffset.setX(0.0);
477     maGridOffset.setY(0.0);
478 
479     // also reset sequence to get a re-calculation when GridOffset changes
480     mxPrimitive2DSequence.clear();
481 }
482 
483 }}
484 
485 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
486