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