1 ///////////////////////////////////////////////////////////////////////////////
3 // Purpose: wxAnimation and wxAnimationCtrl
4 // Author: Julian Smart and Guillermo Rodriguez Garcia
5 // Modified by: Francesco Montorsi
8 // Copyright: (c) Julian Smart and Guillermo Rodriguez Garcia
9 // Licence: wxWindows licence
10 ///////////////////////////////////////////////////////////////////////////////
12 #include "wx/wxprec.h"
18 #if wxUSE_ANIMATIONCTRL && (!defined(__WXGTK20__) || defined(__WXUNIVERSAL__))
20 #include "wx/animate.h"
25 #include "wx/dcmemory.h"
26 #include "wx/dcclient.h"
27 #include "wx/module.h"
30 #include "wx/wfstream.h"
31 #include "wx/gifdecod.h"
32 #include "wx/anidecod.h"
34 #include "wx/listimpl.cpp"
35 WX_DEFINE_LIST(wxAnimationDecoderList
)
37 wxAnimationDecoderList wxAnimation
::sm_handlers
;
40 // ----------------------------------------------------------------------------
42 // ----------------------------------------------------------------------------
44 IMPLEMENT_DYNAMIC_CLASS(wxAnimation
, wxAnimationBase
)
45 #define M_ANIMDATA wx_static_cast(wxAnimationDecoder*, m_refData)
47 wxSize wxAnimation
::GetSize() const
49 wxCHECK_MSG( IsOk(), wxDefaultSize
, wxT("invalid animation") );
51 return M_ANIMDATA
->GetAnimationSize();
54 size_t wxAnimation
::GetFrameCount() const
56 wxCHECK_MSG( IsOk(), 0, wxT("invalid animation") );
58 return M_ANIMDATA
->GetFrameCount();
61 wxImage wxAnimation
::GetFrame(size_t i
) const
63 wxCHECK_MSG( IsOk(), wxNullImage
, wxT("invalid animation") );
66 if (!M_ANIMDATA
->ConvertToImage(i
, &ret
))
71 int wxAnimation
::GetDelay(size_t i
) const
73 wxCHECK_MSG( IsOk(), 0, wxT("invalid animation") );
75 return M_ANIMDATA
->GetDelay(i
);
78 wxPoint wxAnimation
::GetFramePosition(size_t frame
) const
80 wxCHECK_MSG( IsOk(), wxDefaultPosition
, wxT("invalid animation") );
82 return M_ANIMDATA
->GetFramePosition(frame
);
85 wxSize wxAnimation
::GetFrameSize(size_t frame
) const
87 wxCHECK_MSG( IsOk(), wxDefaultSize
, wxT("invalid animation") );
89 return M_ANIMDATA
->GetFrameSize(frame
);
92 wxAnimationDisposal wxAnimation
::GetDisposalMethod(size_t frame
) const
94 wxCHECK_MSG( IsOk(), wxANIM_UNSPECIFIED
, wxT("invalid animation") );
96 return M_ANIMDATA
->GetDisposalMethod(frame
);
99 wxColour wxAnimation
::GetTransparentColour(size_t frame
) const
101 wxCHECK_MSG( IsOk(), wxNullColour
, wxT("invalid animation") );
103 return M_ANIMDATA
->GetTransparentColour(frame
);
106 wxColour wxAnimation
::GetBackgroundColour() const
108 wxCHECK_MSG( IsOk(), wxNullColour
, wxT("invalid animation") );
110 return M_ANIMDATA
->GetBackgroundColour();
113 bool wxAnimation
::LoadFile(const wxString
& filename
, wxAnimationType type
)
115 wxFileInputStream
stream(filename
);
116 if ( !stream
.IsOk() )
119 return Load(stream
, type
);
122 bool wxAnimation
::Load(wxInputStream
&stream
, wxAnimationType type
)
126 const wxAnimationDecoder
*handler
;
127 if ( type
== wxANIMATION_TYPE_ANY
)
129 for ( wxAnimationDecoderList
::compatibility_iterator node
= sm_handlers
.GetFirst();
130 node
; node
= node
->GetNext() )
132 handler
=(const wxAnimationDecoder
*)node
->GetData();
134 if ( handler
->CanRead(stream
) )
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
);
144 wxLogWarning( _("No handler found for animation type.") );
148 handler
= FindHandler(type
);
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();
156 wxLogWarning( _("No animation handler for type %ld defined."), type
);
161 if (stream
.IsSeekable() && !M_ANIMDATA
->CanRead(stream
))
163 wxLogError(_("Animation file is not of type %ld."), type
);
167 return M_ANIMDATA
->Load(stream
);
171 // ----------------------------------------------------------------------------
172 // animation decoders
173 // ----------------------------------------------------------------------------
175 void wxAnimation
::AddHandler( wxAnimationDecoder
*handler
)
177 // Check for an existing handler of the type being added.
178 if (FindHandler( handler
->GetType() ) == 0)
180 sm_handlers
.Append( handler
);
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.
189 wxLogDebug( _T("Adding duplicate animation handler for '%d' type"),
190 handler
->GetType() );
195 void wxAnimation
::InsertHandler( wxAnimationDecoder
*handler
)
197 // Check for an existing handler of the type being added.
198 if (FindHandler( handler
->GetType() ) == 0)
200 sm_handlers
.Insert( handler
);
204 // see AddHandler for additional comments.
205 wxLogDebug( _T("Inserting duplicate animation handler for '%d' type"),
206 handler
->GetType() );
211 const wxAnimationDecoder
*wxAnimation
::FindHandler( wxAnimationType animType
)
213 wxAnimationDecoderList
::compatibility_iterator node
= sm_handlers
.GetFirst();
216 const wxAnimationDecoder
*handler
= (const wxAnimationDecoder
*)node
->GetData();
217 if (handler
->GetType() == animType
) return handler
;
218 node
= node
->GetNext();
223 void wxAnimation
::InitStandardHandlers()
226 AddHandler(new wxGIFDecoder
);
229 AddHandler(new wxANIDecoder
);
230 #endif // wxUSE_ICO_CUR
233 void wxAnimation
::CleanUpHandlers()
235 wxAnimationDecoderList
::compatibility_iterator node
= sm_handlers
.GetFirst();
238 wxAnimationDecoder
*handler
= (wxAnimationDecoder
*)node
->GetData();
239 wxAnimationDecoderList
::compatibility_iterator next
= node
->GetNext();
248 // A module to allow wxAnimation initialization/cleanup
249 // without calling these functions from app.cpp or from
250 // the user's application.
252 class wxAnimationModule
: public wxModule
254 DECLARE_DYNAMIC_CLASS(wxAnimationModule
)
256 wxAnimationModule() {}
257 bool OnInit() { wxAnimation
::InitStandardHandlers(); return true; };
258 void OnExit() { wxAnimation
::CleanUpHandlers(); };
261 IMPLEMENT_DYNAMIC_CLASS(wxAnimationModule
, wxModule
)
264 // ----------------------------------------------------------------------------
266 // ----------------------------------------------------------------------------
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
)
275 void wxAnimationCtrl
::Init()
281 // use the window background colour by default to be consistent
282 // with the GTK+ native version
283 m_useWinBackgroundColour
= true;
286 bool wxAnimationCtrl
::Create(wxWindow
*parent
, wxWindowID id
,
287 const wxAnimation
& animation
, const wxPoint
& pos
,
288 const wxSize
& size
, long style
, const wxString
& name
)
290 m_timer
.SetOwner(this);
292 if (!base_type
::Create(parent
, id
, pos
, size
, style
, wxDefaultValidator
, name
))
295 // by default we get the same background colour of our parent
296 SetBackgroundColour(parent
->GetBackgroundColour());
298 SetAnimation(animation
);
303 wxAnimationCtrl
::~wxAnimationCtrl()
308 bool wxAnimationCtrl
::LoadFile(const wxString
& filename
, wxAnimationType type
)
311 if (!anim
.LoadFile(filename
, type
) ||
319 wxSize wxAnimationCtrl
::DoGetBestSize() const
321 if (m_animation
.IsOk() && !this->HasFlag(wxAC_NO_AUTORESIZE
))
322 return m_animation
.GetSize();
324 return wxSize(100, 100);
327 void wxAnimationCtrl
::SetAnimation(const wxAnimation
& animation
)
332 // set new animation even if it's wxNullAnimation
333 m_animation
= animation
;
334 if (!m_animation
.IsOk())
336 UpdateBackingStoreWithStaticImage();
340 if (m_animation
.GetBackgroundColour() == wxNullColour
)
341 SetUseWindowBackgroundColour();
342 if (!this->HasFlag(wxAC_NO_AUTORESIZE
))
345 UpdateBackingStoreWithStaticImage();
348 void wxAnimationCtrl
::SetInactiveBitmap(const wxBitmap
&bmp
)
352 // if the bitmap has an associated mask, we need to set our background to
353 // the colour of our parent otherwise when calling DrawCurrentFrame()
354 // (which uses the bitmap's mask), our background colour would be used for
355 // transparent areas - and that's not what we want (at least for
356 // consistency with the GTK version)
357 if ( bmp
.GetMask() != NULL
&& GetParent() != NULL
)
358 SetBackgroundColour(GetParent()->GetBackgroundColour());
360 // if not playing, update the backing store now
362 UpdateBackingStoreWithStaticImage();
365 void wxAnimationCtrl
::FitToAnimation()
367 SetSize(m_animation
.GetSize());
370 bool wxAnimationCtrl
::SetBackgroundColour(const wxColour
& colour
)
372 if ( !wxWindow
::SetBackgroundColour(colour
) )
375 // if not playing, then this change must be seen immediately (unless
376 // there's an inactive bitmap set which has higher priority than bg colour)
378 UpdateBackingStoreWithStaticImage();
384 // ----------------------------------------------------------------------------
385 // wxAnimationCtrl - stop/play methods
386 // ----------------------------------------------------------------------------
388 void wxAnimationCtrl
::Stop()
393 // reset frame counter
396 UpdateBackingStoreWithStaticImage();
399 bool wxAnimationCtrl
::Play(bool looped
)
401 if (!m_animation
.IsOk())
407 if (!RebuildBackingStoreUpToFrame(0))
412 // do a ClearBackground() to avoid that e.g. the custom static bitmap which
413 // was eventually shown previously remains partially drawn
416 // DrawCurrentFrame() will use our updated backing store
417 wxClientDC
clientDC(this);
418 DrawCurrentFrame(clientDC
);
421 int delay
= m_animation
.GetDelay(0);
423 delay
= 1; // 0 is invalid timeout for wxTimer.
424 m_timer
.Start(delay
);
431 // ----------------------------------------------------------------------------
432 // wxAnimationCtrl - rendering methods
433 // ----------------------------------------------------------------------------
435 bool wxAnimationCtrl
::RebuildBackingStoreUpToFrame(size_t frame
)
437 // if we've not created the backing store yet or it's too
438 // small, then recreate it
439 wxSize sz
= m_animation
.GetSize(),
440 winsz
= GetClientSize();
441 int w
= wxMin(sz
.GetWidth(), winsz
.GetWidth());
442 int h
= wxMin(sz
.GetHeight(), winsz
.GetHeight());
444 if ( !m_backingStore
.IsOk() ||
445 m_backingStore
.GetWidth() < w
|| m_backingStore
.GetHeight() < h
)
447 if (!m_backingStore
.Create(w
, h
))
452 dc
.SelectObject(m_backingStore
);
454 // Draw the background
455 DisposeToBackground(dc
);
457 // Draw all intermediate frames that haven't been removed from the animation
458 for (size_t i
= 0; i
< frame
; i
++)
460 if (m_animation
.GetDisposalMethod(i
) == wxANIM_DONOTREMOVE
||
461 m_animation
.GetDisposalMethod(i
) == wxANIM_UNSPECIFIED
)
465 else if (m_animation
.GetDisposalMethod(i
) == wxANIM_TOBACKGROUND
)
466 DisposeToBackground(dc
, m_animation
.GetFramePosition(i
),
467 m_animation
.GetFrameSize(i
));
470 // finally draw this frame
471 DrawFrame(dc
, frame
);
472 dc
.SelectObject(wxNullBitmap
);
477 void wxAnimationCtrl
::IncrementalUpdateBackingStore()
480 dc
.SelectObject(m_backingStore
);
483 // since wxAnimationCtrl can only play animations forward, without skipping
484 // frames, we can be sure that m_backingStore contains the m_currentFrame-1
485 // frame and thus we just need to dispose the m_currentFrame-1 frame and
486 // render the m_currentFrame-th one.
488 if (m_currentFrame
== 0)
490 // before drawing the first frame always dispose to bg colour
491 DisposeToBackground(dc
);
495 switch (m_animation
.GetDisposalMethod(m_currentFrame
-1))
497 case wxANIM_TOBACKGROUND
:
498 DisposeToBackground(dc
, m_animation
.GetFramePosition(m_currentFrame
-1),
499 m_animation
.GetFrameSize(m_currentFrame
-1));
502 case wxANIM_TOPREVIOUS
:
503 // this disposal should never be used too often.
504 // E.g. GIF specification explicitely say to keep the usage of this
505 // disposal limited to the minimum.
506 // In fact it may require a lot of time to restore
507 if (m_currentFrame
== 1)
509 // if 0-th frame disposal is to restore to previous frame,
510 // the best we can do is to restore to background
511 DisposeToBackground(dc
);
514 if (!RebuildBackingStoreUpToFrame(m_currentFrame
-2))
518 case wxANIM_DONOTREMOVE
:
519 case wxANIM_UNSPECIFIED
:
524 // now just draw the current frame on the top of the backing store
525 DrawFrame(dc
, m_currentFrame
);
526 dc
.SelectObject(wxNullBitmap
);
529 void wxAnimationCtrl
::UpdateBackingStoreWithStaticImage()
531 wxASSERT(!IsPlaying());
533 if (m_bmpStatic
.IsOk())
535 // copy the inactive bitmap in the backing store
536 // eventually using the mask if the static bitmap has one
537 if ( m_bmpStatic
.GetMask() )
540 temp
.SelectObject(m_backingStore
);
541 DisposeToBackground(temp
);
542 temp
.DrawBitmap(m_bmpStatic
, 0, 0, true /* use mask */);
545 m_backingStore
= m_bmpStatic
;
549 // put in the backing store the first frame of the animation
550 if (!m_animation
.IsOk() ||
551 !RebuildBackingStoreUpToFrame(0))
553 m_animation
= wxNullAnimation
;
554 DisposeToBackground();
561 void wxAnimationCtrl
::DrawFrame(wxDC
&dc
, size_t frame
)
564 // this draw stuff is not as fast as possible: the wxAnimationDecoder
565 // needs first to convert from its internal format to wxImage RGB24;
566 // the wxImage is then converted as a wxBitmap and finally blitted.
567 // If wxAnimationDecoder had a function to convert directly from its
568 // internal format to a port-specific wxBitmap, it would be somewhat faster.
569 wxBitmap
bmp(m_animation
.GetFrame(frame
));
570 dc
.DrawBitmap(bmp
, m_animation
.GetFramePosition(frame
),
571 true /* use mask */);
574 void wxAnimationCtrl
::DrawCurrentFrame(wxDC
& dc
)
576 wxASSERT( m_backingStore
.IsOk() );
578 // m_backingStore always contains the current frame
579 dc
.DrawBitmap(m_backingStore
, 0, 0, true /* use mask in case it's present */);
582 void wxAnimationCtrl
::DisposeToBackground()
584 // clear the backing store
586 dc
.SelectObject(m_backingStore
);
587 DisposeToBackground(dc
);
590 void wxAnimationCtrl
::DisposeToBackground(wxDC
& dc
)
592 wxColour col
= IsUsingWindowBackgroundColour()
593 ?
GetBackgroundColour()
594 : m_animation
.GetBackgroundColour();
597 dc
.SetBackground(brush
);
601 void wxAnimationCtrl
::DisposeToBackground(wxDC
& dc
, const wxPoint
&pos
, const wxSize
&sz
)
603 wxColour col
= IsUsingWindowBackgroundColour()
604 ?
GetBackgroundColour()
605 : m_animation
.GetBackgroundColour();
607 dc
.SetBrush(brush
); // SetBrush and not SetBackground !!
608 dc
.SetPen(*wxTRANSPARENT_PEN
);
609 dc
.DrawRectangle(pos
, sz
);
612 // ----------------------------------------------------------------------------
613 // wxAnimationCtrl - event handlers
614 // ----------------------------------------------------------------------------
616 void wxAnimationCtrl
::OnPaint(wxPaintEvent
& WXUNUSED(event
))
618 // VERY IMPORTANT: the wxPaintDC *must* be created in any case
621 if ( m_backingStore
.IsOk() )
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 */);
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
);
636 void wxAnimationCtrl
::OnTimer(wxTimerEvent
&WXUNUSED(event
))
639 if (m_currentFrame
== m_animation
.GetFrameCount())
641 // Should a non-looped animation display the last frame?
648 m_currentFrame
= 0; // let's restart
651 IncrementalUpdateBackingStore();
654 DrawCurrentFrame(dc
);
657 // without this, the animation currently doesn't redraw under Mac
661 // Set the timer for the next frame
662 int delay
= m_animation
.GetDelay(m_currentFrame
);
664 delay
= 1; // 0 is invalid timeout for wxTimer.
665 m_timer
.Start(delay
);
668 void wxAnimationCtrl
::OnSize(wxSizeEvent
&WXUNUSED(event
))
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())
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
683 if (!RebuildBackingStoreUpToFrame(m_currentFrame
))
684 Stop(); // in case we are playing
689 #endif // wxUSE_ANIMATIONCTRL