1 ///////////////////////////////////////////////////////////////////////////////
2 // Name:        animateg.cpp
3 // Purpose:     wxAnimation and wxAnimationCtrl
4 // Author:      Julian Smart and Guillermo Rodriguez Garcia
5 // Modified by: Francesco Montorsi
6 // Created:     13/8/99
7 // RCS-ID:      $Id: animateg.cpp 48085 2007-08-15 11:36:50Z VZ $
8 // Copyright:   (c) Julian Smart and Guillermo Rodriguez Garcia
9 // Licence:     wxWindows licence
10 ///////////////////////////////////////////////////////////////////////////////
11 
12 #include "wx/wxprec.h"
13 
14 #ifdef __BORLANDC__
15   #pragma hdrstop
16 #endif  //__BORLANDC__
17 
18 #if wxUSE_ANIMATIONCTRL && (!defined(__WXGTK20__) || defined(__WXUNIVERSAL__))
19 
20 #include "wx/animate.h"
21 
22 #ifndef WX_PRECOMP
23     #include "wx/log.h"
24     #include "wx/image.h"
25     #include "wx/dcmemory.h"
26     #include "wx/dcclient.h"
27     #include "wx/module.h"
28 #endif
29 
30 #include "wx/wfstream.h"
31 #include "wx/gifdecod.h"
32 #include "wx/anidecod.h"
33 
34 #include "wx/listimpl.cpp"
35 WX_DEFINE_LIST(wxAnimationDecoderList)
36 
37 wxAnimationDecoderList wxAnimation::sm_handlers;
38 
39 
40 // ----------------------------------------------------------------------------
41 // wxAnimation
42 // ----------------------------------------------------------------------------
43 
IMPLEMENT_DYNAMIC_CLASS(wxAnimation,wxAnimationBase)44 IMPLEMENT_DYNAMIC_CLASS(wxAnimation, wxAnimationBase)
45 #define M_ANIMDATA      wx_static_cast(wxAnimationDecoder*, m_refData)
46 
47 wxSize wxAnimation::GetSize() const
48 {
49     wxCHECK_MSG( IsOk(), wxDefaultSize, wxT("invalid animation") );
50 
51     return M_ANIMDATA->GetAnimationSize();
52 }
53 
GetFrameCount() const54 unsigned int wxAnimation::GetFrameCount() const
55 {
56     wxCHECK_MSG( IsOk(), 0, wxT("invalid animation") );
57 
58     return M_ANIMDATA->GetFrameCount();
59 }
60 
GetFrame(unsigned int i) const61 wxImage wxAnimation::GetFrame(unsigned int i) const
62 {
63     wxCHECK_MSG( IsOk(), wxNullImage, wxT("invalid animation") );
64 
65     wxImage ret;
66     if (!M_ANIMDATA->ConvertToImage(i, &ret))
67         return wxNullImage;
68     return ret;
69 }
70 
GetDelay(unsigned int i) const71 int wxAnimation::GetDelay(unsigned int i) const
72 {
73     wxCHECK_MSG( IsOk(), 0, wxT("invalid animation") );
74 
75     return M_ANIMDATA->GetDelay(i);
76 }
77 
GetFramePosition(unsigned int frame) const78 wxPoint wxAnimation::GetFramePosition(unsigned int frame) const
79 {
80     wxCHECK_MSG( IsOk(), wxDefaultPosition, wxT("invalid animation") );
81 
82     return M_ANIMDATA->GetFramePosition(frame);
83 }
84 
GetFrameSize(unsigned int frame) const85 wxSize wxAnimation::GetFrameSize(unsigned int frame) const
86 {
87     wxCHECK_MSG( IsOk(), wxDefaultSize, wxT("invalid animation") );
88 
89     return M_ANIMDATA->GetFrameSize(frame);
90 }
91 
GetDisposalMethod(unsigned int frame) const92 wxAnimationDisposal wxAnimation::GetDisposalMethod(unsigned int frame) const
93 {
94     wxCHECK_MSG( IsOk(), wxANIM_UNSPECIFIED, wxT("invalid animation") );
95 
96     return M_ANIMDATA->GetDisposalMethod(frame);
97 }
98 
GetTransparentColour(unsigned int frame) const99 wxColour wxAnimation::GetTransparentColour(unsigned int frame) const
100 {
101     wxCHECK_MSG( IsOk(), wxNullColour, wxT("invalid animation") );
102 
103     return M_ANIMDATA->GetTransparentColour(frame);
104 }
105 
GetBackgroundColour() const106 wxColour wxAnimation::GetBackgroundColour() const
107 {
108     wxCHECK_MSG( IsOk(), wxNullColour, wxT("invalid animation") );
109 
110     return M_ANIMDATA->GetBackgroundColour();
111 }
112 
LoadFile(const wxString & filename,wxAnimationType type)113 bool wxAnimation::LoadFile(const wxString& filename, wxAnimationType type)
114 {
115     wxFileInputStream stream(filename);
116     if ( !stream.IsOk() )
117         return false;
118 
119     return Load(stream, type);
120 }
121 
Load(wxInputStream & stream,wxAnimationType type)122 bool wxAnimation::Load(wxInputStream &stream, wxAnimationType type)
123 {
124     UnRef();
125 
126     const wxAnimationDecoder *handler;
127     if ( type == wxANIMATION_TYPE_ANY )
128     {
129         for ( wxAnimationDecoderList::compatibility_iterator node = sm_handlers.GetFirst();
130               node; node = node->GetNext() )
131         {
132             handler=(const wxAnimationDecoder*)node->GetData();
133 
134             if ( handler->CanRead(stream) )
135             {
136                 // do a copy of the handler from the static list which we will own
137                 // as our reference data
138                 m_refData = handler->Clone();
139                 return M_ANIMDATA->Load(stream);
140             }
141 
142         }
143 
144         wxLogWarning( _("No handler found for animation type.") );
145         return false;
146     }
147 
148     handler = FindHandler(type);
149 
150     // do a copy of the handler from the static list which we will own
151     // as our reference data
152     m_refData = handler->Clone();
153 
154     if (handler == NULL)
155     {
156         wxLogWarning( _("No animation handler for type %ld defined."), type );
157 
158         return false;
159     }
160 
161     if (stream.IsSeekable() && !M_ANIMDATA->CanRead(stream))
162     {
163         wxLogError(_("Animation file is not of type %ld."), type);
164         return false;
165     }
166     else
167         return M_ANIMDATA->Load(stream);
168 }
169 
170 
171 // ----------------------------------------------------------------------------
172 // animation decoders
173 // ----------------------------------------------------------------------------
174 
AddHandler(wxAnimationDecoder * handler)175 void wxAnimation::AddHandler( wxAnimationDecoder *handler )
176 {
177     // Check for an existing handler of the type being added.
178     if (FindHandler( handler->GetType() ) == 0)
179     {
180         sm_handlers.Append( handler );
181     }
182     else
183     {
184         // This is not documented behaviour, merely the simplest 'fix'
185         // for preventing duplicate additions.  If someone ever has
186         // a good reason to add and remove duplicate handlers (and they
187         // may) we should probably refcount the duplicates.
188 
189         wxLogDebug( _T("Adding duplicate animation handler for '%d' type"),
190                     handler->GetType() );
191         delete handler;
192     }
193 }
194 
InsertHandler(wxAnimationDecoder * handler)195 void wxAnimation::InsertHandler( wxAnimationDecoder *handler )
196 {
197     // Check for an existing handler of the type being added.
198     if (FindHandler( handler->GetType() ) == 0)
199     {
200         sm_handlers.Insert( handler );
201     }
202     else
203     {
204         // see AddHandler for additional comments.
205         wxLogDebug( _T("Inserting duplicate animation handler for '%d' type"),
206                     handler->GetType() );
207         delete handler;
208     }
209 }
210 
FindHandler(wxAnimationType animType)211 const wxAnimationDecoder *wxAnimation::FindHandler( wxAnimationType animType )
212 {
213     wxAnimationDecoderList::compatibility_iterator node = sm_handlers.GetFirst();
214     while (node)
215     {
216         const wxAnimationDecoder *handler = (const wxAnimationDecoder *)node->GetData();
217         if (handler->GetType() == animType) return handler;
218         node = node->GetNext();
219     }
220     return 0;
221 }
222 
InitStandardHandlers()223 void wxAnimation::InitStandardHandlers()
224 {
225 #if wxUSE_GIF
226     AddHandler(new wxGIFDecoder);
227 #endif // wxUSE_GIF
228 #if wxUSE_ICO_CUR
229     AddHandler(new wxANIDecoder);
230 #endif // wxUSE_ICO_CUR
231 }
232 
CleanUpHandlers()233 void wxAnimation::CleanUpHandlers()
234 {
235     wxAnimationDecoderList::compatibility_iterator node = sm_handlers.GetFirst();
236     while (node)
237     {
238         wxAnimationDecoder *handler = (wxAnimationDecoder *)node->GetData();
239         wxAnimationDecoderList::compatibility_iterator next = node->GetNext();
240         delete handler;
241         node = next;
242     }
243 
244     sm_handlers.Clear();
245 }
246 
247 
248 // A module to allow wxAnimation initialization/cleanup
249 // without calling these functions from app.cpp or from
250 // the user's application.
251 
252 class wxAnimationModule: public wxModule
253 {
254 DECLARE_DYNAMIC_CLASS(wxAnimationModule)
255 public:
wxAnimationModule()256     wxAnimationModule() {}
OnInit()257     bool OnInit() { wxAnimation::InitStandardHandlers(); return true; }
OnExit()258     void OnExit() { wxAnimation::CleanUpHandlers(); }
259 };
260 
IMPLEMENT_DYNAMIC_CLASS(wxAnimationModule,wxModule)261 IMPLEMENT_DYNAMIC_CLASS(wxAnimationModule, wxModule)
262 
263 
264 // ----------------------------------------------------------------------------
265 // wxAnimationCtrl
266 // ----------------------------------------------------------------------------
267 
268 IMPLEMENT_CLASS(wxAnimationCtrl, wxAnimationCtrlBase)
269 BEGIN_EVENT_TABLE(wxAnimationCtrl, wxAnimationCtrlBase)
270     EVT_PAINT(wxAnimationCtrl::OnPaint)
271     EVT_SIZE(wxAnimationCtrl::OnSize)
272     EVT_TIMER(wxID_ANY, wxAnimationCtrl::OnTimer)
273 END_EVENT_TABLE()
274 
275 void wxAnimationCtrl::Init()
276 {
277     m_currentFrame = 0;
278     m_looped = false;
279     m_isPlaying = false;
280 
281     // use the window background colour by default to be consistent
282     // with the GTK+ native version
283     m_useWinBackgroundColour = true;
284 }
285 
Create(wxWindow * parent,wxWindowID id,const wxAnimation & animation,const wxPoint & pos,const wxSize & size,long style,const wxString & name)286 bool wxAnimationCtrl::Create(wxWindow *parent, wxWindowID id,
287             const wxAnimation& animation, const wxPoint& pos,
288             const wxSize& size, long style, const wxString& name)
289 {
290     m_timer.SetOwner(this);
291 
292     if (!base_type::Create(parent, id, pos, size, style, wxDefaultValidator, name))
293         return false;
294 
295     // by default we get the same background colour of our parent
296     SetBackgroundColour(parent->GetBackgroundColour());
297 
298     SetAnimation(animation);
299 
300     return true;
301 }
302 
~wxAnimationCtrl()303 wxAnimationCtrl::~wxAnimationCtrl()
304 {
305     Stop();
306 }
307 
LoadFile(const wxString & filename,wxAnimationType type)308 bool wxAnimationCtrl::LoadFile(const wxString& filename, wxAnimationType type)
309 {
310     wxAnimation anim;
311     if (!anim.LoadFile(filename, type) ||
312         !anim.IsOk())
313         return false;
314 
315     SetAnimation(anim);
316     return true;
317 }
318 
DoGetBestSize() const319 wxSize wxAnimationCtrl::DoGetBestSize() const
320 {
321     if (m_animation.IsOk() && !this->HasFlag(wxAC_NO_AUTORESIZE))
322         return m_animation.GetSize();
323 
324     return wxSize(100, 100);
325 }
326 
SetAnimation(const wxAnimation & animation)327 void wxAnimationCtrl::SetAnimation(const wxAnimation& animation)
328 {
329     if (IsPlaying())
330         Stop();
331 
332     // set new animation even if it's wxNullAnimation
333     m_animation = animation;
334     if (!m_animation.IsOk())
335     {
336         DisplayStaticImage();
337         return;
338     }
339 
340     if (m_animation.GetBackgroundColour() == wxNullColour)
341         SetUseWindowBackgroundColour();
342     if (!this->HasFlag(wxAC_NO_AUTORESIZE))
343         FitToAnimation();
344 
345     DisplayStaticImage();
346 }
347 
SetInactiveBitmap(const wxBitmap & bmp)348 void wxAnimationCtrl::SetInactiveBitmap(const wxBitmap &bmp)
349 {
350     // if the bitmap has an associated mask, we need to set our background to
351     // the colour of our parent otherwise when calling DrawCurrentFrame()
352     // (which uses the bitmap's mask), our background colour would be used for
353     // transparent areas - and that's not what we want (at least for
354     // consistency with the GTK version)
355     if ( bmp.GetMask() != NULL && GetParent() != NULL )
356         SetBackgroundColour(GetParent()->GetBackgroundColour());
357 
358     wxAnimationCtrlBase::SetInactiveBitmap(bmp);
359 }
360 
FitToAnimation()361 void wxAnimationCtrl::FitToAnimation()
362 {
363     SetSize(m_animation.GetSize());
364 }
365 
SetBackgroundColour(const wxColour & colour)366 bool wxAnimationCtrl::SetBackgroundColour(const wxColour& colour)
367 {
368     if ( !wxWindow::SetBackgroundColour(colour) )
369         return false;
370 
371     // if not playing, then this change must be seen immediately (unless
372     // there's an inactive bitmap set which has higher priority than bg colour)
373     if ( !IsPlaying() )
374         DisplayStaticImage();
375 
376     return true;
377 }
378 
379 
380 // ----------------------------------------------------------------------------
381 // wxAnimationCtrl - stop/play methods
382 // ----------------------------------------------------------------------------
383 
Stop()384 void wxAnimationCtrl::Stop()
385 {
386     m_timer.Stop();
387     m_isPlaying = false;
388 
389     // reset frame counter
390     m_currentFrame = 0;
391 
392     DisplayStaticImage();
393 }
394 
Play(bool looped)395 bool wxAnimationCtrl::Play(bool looped)
396 {
397     if (!m_animation.IsOk())
398         return false;
399 
400     m_looped = looped;
401     m_currentFrame = 0;
402 
403     if (!RebuildBackingStoreUpToFrame(0))
404         return false;
405 
406     m_isPlaying = true;
407 
408     // do a ClearBackground() to avoid that e.g. the custom static bitmap which
409     // was eventually shown previously remains partially drawn
410     ClearBackground();
411 
412     // DrawCurrentFrame() will use our updated backing store
413     wxClientDC clientDC(this);
414     DrawCurrentFrame(clientDC);
415 
416     // start the timer
417     int delay = m_animation.GetDelay(0);
418     if (delay == 0)
419         delay = 1;      // 0 is invalid timeout for wxTimer.
420     m_timer.Start(delay, true);
421 
422     return true;
423 }
424 
425 
426 
427 // ----------------------------------------------------------------------------
428 // wxAnimationCtrl - rendering methods
429 // ----------------------------------------------------------------------------
430 
RebuildBackingStoreUpToFrame(unsigned int frame)431 bool wxAnimationCtrl::RebuildBackingStoreUpToFrame(unsigned int frame)
432 {
433     // if we've not created the backing store yet or it's too
434     // small, then recreate it
435     wxSize sz = m_animation.GetSize(),
436            winsz = GetClientSize();
437     int w = wxMin(sz.GetWidth(), winsz.GetWidth());
438     int h = wxMin(sz.GetHeight(), winsz.GetHeight());
439 
440     if ( !m_backingStore.IsOk() ||
441             m_backingStore.GetWidth() < w || m_backingStore.GetHeight() < h )
442     {
443         if (!m_backingStore.Create(w, h))
444             return false;
445     }
446 
447     wxMemoryDC dc;
448     dc.SelectObject(m_backingStore);
449 
450     // Draw the background
451     DisposeToBackground(dc);
452 
453     // Draw all intermediate frames that haven't been removed from the animation
454     for (unsigned int i = 0; i < frame; i++)
455     {
456         if (m_animation.GetDisposalMethod(i) == wxANIM_DONOTREMOVE ||
457             m_animation.GetDisposalMethod(i) == wxANIM_UNSPECIFIED)
458         {
459             DrawFrame(dc, i);
460         }
461         else if (m_animation.GetDisposalMethod(i) == wxANIM_TOBACKGROUND)
462             DisposeToBackground(dc, m_animation.GetFramePosition(i),
463                                     m_animation.GetFrameSize(i));
464     }
465 
466     // finally draw this frame
467     DrawFrame(dc, frame);
468     dc.SelectObject(wxNullBitmap);
469 
470     return true;
471 }
472 
IncrementalUpdateBackingStore()473 void wxAnimationCtrl::IncrementalUpdateBackingStore()
474 {
475     wxMemoryDC dc;
476     dc.SelectObject(m_backingStore);
477 
478     // OPTIMIZATION:
479     // since wxAnimationCtrl can only play animations forward, without skipping
480     // frames, we can be sure that m_backingStore contains the m_currentFrame-1
481     // frame and thus we just need to dispose the m_currentFrame-1 frame and
482     // render the m_currentFrame-th one.
483 
484     if (m_currentFrame == 0)
485     {
486         // before drawing the first frame always dispose to bg colour
487         DisposeToBackground(dc);
488     }
489     else
490     {
491         switch (m_animation.GetDisposalMethod(m_currentFrame-1))
492         {
493         case wxANIM_TOBACKGROUND:
494             DisposeToBackground(dc, m_animation.GetFramePosition(m_currentFrame-1),
495                                     m_animation.GetFrameSize(m_currentFrame-1));
496             break;
497 
498         case wxANIM_TOPREVIOUS:
499             // this disposal should never be used too often.
500             // E.g. GIF specification explicitely say to keep the usage of this
501             //      disposal limited to the minimum.
502             // In fact it may require a lot of time to restore
503             if (m_currentFrame == 1)
504             {
505                 // if 0-th frame disposal is to restore to previous frame,
506                 // the best we can do is to restore to background
507                 DisposeToBackground(dc);
508             }
509             else
510                 if (!RebuildBackingStoreUpToFrame(m_currentFrame-2))
511                     Stop();
512             break;
513 
514         case wxANIM_DONOTREMOVE:
515         case wxANIM_UNSPECIFIED:
516             break;
517         }
518     }
519 
520     // now just draw the current frame on the top of the backing store
521     DrawFrame(dc, m_currentFrame);
522     dc.SelectObject(wxNullBitmap);
523 }
524 
DisplayStaticImage()525 void wxAnimationCtrl::DisplayStaticImage()
526 {
527     wxASSERT(!IsPlaying());
528 
529     // m_bmpStaticReal will be updated only if necessary...
530     UpdateStaticImage();
531 
532     if (m_bmpStaticReal.IsOk())
533     {
534         // copy the inactive bitmap in the backing store
535         // eventually using the mask if the static bitmap has one
536         if ( m_bmpStaticReal.GetMask() )
537         {
538             wxMemoryDC temp;
539             temp.SelectObject(m_backingStore);
540             DisposeToBackground(temp);
541             temp.DrawBitmap(m_bmpStaticReal, 0, 0, true /* use mask */);
542         }
543         else
544             m_backingStore = m_bmpStaticReal;
545     }
546     else
547     {
548         // put in the backing store the first frame of the animation
549         if (!m_animation.IsOk() ||
550             !RebuildBackingStoreUpToFrame(0))
551         {
552             m_animation = wxNullAnimation;
553             DisposeToBackground();
554         }
555     }
556 
557     Refresh();
558 }
559 
DrawFrame(wxDC & dc,unsigned int frame)560 void wxAnimationCtrl::DrawFrame(wxDC &dc, unsigned int frame)
561 {
562     // PERFORMANCE NOTE:
563     // this draw stuff is not as fast as possible: the wxAnimationDecoder
564     // needs first to convert from its internal format to wxImage RGB24;
565     // the wxImage is then converted as a wxBitmap and finally blitted.
566     // If wxAnimationDecoder had a function to convert directly from its
567     // internal format to a port-specific wxBitmap, it would be somewhat faster.
568     wxBitmap bmp(m_animation.GetFrame(frame));
569     dc.DrawBitmap(bmp, m_animation.GetFramePosition(frame),
570                   true /* use mask */);
571 }
572 
DrawCurrentFrame(wxDC & dc)573 void wxAnimationCtrl::DrawCurrentFrame(wxDC& dc)
574 {
575     wxASSERT( m_backingStore.IsOk() );
576 
577     // m_backingStore always contains the current frame
578     dc.DrawBitmap(m_backingStore, 0, 0, true /* use mask in case it's present */);
579 }
580 
DisposeToBackground()581 void wxAnimationCtrl::DisposeToBackground()
582 {
583     // clear the backing store
584     wxMemoryDC dc;
585     dc.SelectObject(m_backingStore);
586     if ( dc.IsOk() )
587         DisposeToBackground(dc);
588 }
589 
DisposeToBackground(wxDC & dc)590 void wxAnimationCtrl::DisposeToBackground(wxDC& dc)
591 {
592     wxColour col = IsUsingWindowBackgroundColour()
593                     ? GetBackgroundColour()
594                     : m_animation.GetBackgroundColour();
595 
596     wxBrush brush(col);
597     dc.SetBackground(brush);
598     dc.Clear();
599 }
600 
DisposeToBackground(wxDC & dc,const wxPoint & pos,const wxSize & sz)601 void wxAnimationCtrl::DisposeToBackground(wxDC& dc, const wxPoint &pos, const wxSize &sz)
602 {
603     wxColour col = IsUsingWindowBackgroundColour()
604                     ? GetBackgroundColour()
605                     : m_animation.GetBackgroundColour();
606     wxBrush brush(col);
607     dc.SetBrush(brush);         // SetBrush and not SetBackground !!
608     dc.SetPen(*wxTRANSPARENT_PEN);
609     dc.DrawRectangle(pos, sz);
610 }
611 
612 // ----------------------------------------------------------------------------
613 // wxAnimationCtrl - event handlers
614 // ----------------------------------------------------------------------------
615 
OnPaint(wxPaintEvent & WXUNUSED (event))616 void wxAnimationCtrl::OnPaint(wxPaintEvent& WXUNUSED(event))
617 {
618     // VERY IMPORTANT: the wxPaintDC *must* be created in any case
619     wxPaintDC dc(this);
620 
621     if ( m_backingStore.IsOk() )
622     {
623         // NOTE: we draw the bitmap explicitely ignoring the mask (if any);
624         //       i.e. we don't want to combine the backing store with the
625         //       possibly wrong preexisting contents of the window!
626         dc.DrawBitmap(m_backingStore, 0, 0, false /* no mask */);
627     }
628     else
629     {
630         // m_animation is not valid and thus we don't have a valid backing store...
631         // clear then our area to the background colour
632         DisposeToBackground(dc);
633     }
634 }
635 
OnTimer(wxTimerEvent & WXUNUSED (event))636 void wxAnimationCtrl::OnTimer(wxTimerEvent &WXUNUSED(event))
637 {
638     m_currentFrame++;
639     if (m_currentFrame == m_animation.GetFrameCount())
640     {
641         // Should a non-looped animation display the last frame?
642         if (!m_looped)
643         {
644             Stop();
645             return;
646         }
647         else
648             m_currentFrame = 0;     // let's restart
649     }
650 
651     IncrementalUpdateBackingStore();
652 
653     wxClientDC dc(this);
654     DrawCurrentFrame(dc);
655 
656 #ifdef __WXMAC__
657     // without this, the animation currently doesn't redraw under Mac
658     Refresh();
659 #endif // __WXMAC__
660 
661     // Set the timer for the next frame
662     int delay = m_animation.GetDelay(m_currentFrame);
663     if (delay == 0)
664         delay = 1;      // 0 is invalid timeout for wxTimer.
665     m_timer.Start(delay, true);
666 }
667 
OnSize(wxSizeEvent & WXUNUSED (event))668 void wxAnimationCtrl::OnSize(wxSizeEvent &WXUNUSED(event))
669 {
670     // NB: resizing an animation control may take a lot of time
671     //     for big animations as the backing store must be
672     //     extended and rebuilt. Try to avoid it e.g. using
673     //     a null proportion value for your wxAnimationCtrls
674     //     when using them inside sizers.
675     if (m_animation.IsOk())
676     {
677         // be careful to change the backing store *only* if we are
678         // playing the animation as otherwise we may be displaying
679         // the inactive bitmap and overwriting the backing store
680         // with the last played frame is wrong in this case
681         if (IsPlaying())
682         {
683             if (!RebuildBackingStoreUpToFrame(m_currentFrame))
684                 Stop();     // in case we are playing
685         }
686     }
687 }
688 
689 #endif      // wxUSE_ANIMATIONCTRL
690 
691