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