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
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 wxAnimationDisposal
wxAnimation::GetDisposalMethod(size_t frame
) const
87 wxCHECK_MSG( IsOk(), wxANIM_UNSPECIFIED
, wxT("invalid animation") );
89 return M_ANIMDATA
->GetDisposalMethod(frame
);
92 wxColour
wxAnimation::GetBackgroundColour() const
94 wxCHECK_MSG( IsOk(), wxNullColour
, wxT("invalid animation") );
96 return M_ANIMDATA
->GetBackgroundColour();
99 bool wxAnimation::LoadFile(const wxString
& filename
, wxAnimationType type
)
101 wxFileInputStream
stream(filename
);
102 if ( !stream
.IsOk() )
105 return Load(stream
, type
);
108 bool wxAnimation::Load(wxInputStream
&stream
, wxAnimationType type
)
112 const wxAnimationDecoder
*handler
;
113 if ( type
== wxANIMATION_TYPE_ANY
)
115 for ( wxAnimationDecoderList::compatibility_iterator node
= sm_handlers
.GetFirst();
116 node
; node
= node
->GetNext() )
118 handler
=(const wxAnimationDecoder
*)node
->GetData();
120 if ( handler
->CanRead(stream
) )
122 // do a copy of the handler from the static list which we will own
123 // as our reference data
124 m_refData
= handler
->Clone();
125 return M_ANIMDATA
->Load(stream
);
130 wxLogWarning( _("No handler found for animation type.") );
134 handler
= FindHandler(type
);
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();
142 wxLogWarning( _("No animation handler for type %ld defined."), type
);
147 if (stream
.IsSeekable() && !M_ANIMDATA
->CanRead(stream
))
149 wxLogError(_("Animation file is not of type %ld."), type
);
153 return M_ANIMDATA
->Load(stream
);
157 // ----------------------------------------------------------------------------
158 // animation decoders
159 // ----------------------------------------------------------------------------
161 void wxAnimation::AddHandler( wxAnimationDecoder
*handler
)
163 // Check for an existing handler of the type being added.
164 if (FindHandler( handler
->GetType() ) == 0)
166 sm_handlers
.Append( handler
);
170 // This is not documented behaviour, merely the simplest 'fix'
171 // for preventing duplicate additions. If someone ever has
172 // a good reason to add and remove duplicate handlers (and they
173 // may) we should probably refcount the duplicates.
174 // also an issue in InsertHandler below.
176 wxLogDebug( _T("Adding duplicate animation handler for '%d' type"),
177 handler
->GetType() );
182 void wxAnimation::InsertHandler( wxAnimationDecoder
*handler
)
184 // Check for an existing handler of the type being added.
185 if (FindHandler( handler
->GetType() ) == 0)
187 sm_handlers
.Insert( handler
);
191 // see AddHandler for additional comments.
192 wxLogDebug( _T("Inserting duplicate animation handler for '%d' type"),
193 handler
->GetType() );
198 const wxAnimationDecoder
*wxAnimation::FindHandler( wxAnimationType animType
)
200 wxAnimationDecoderList::compatibility_iterator node
= sm_handlers
.GetFirst();
203 const wxAnimationDecoder
*handler
= (const wxAnimationDecoder
*)node
->GetData();
204 if (handler
->GetType() == animType
) return handler
;
205 node
= node
->GetNext();
210 void wxAnimation::InitStandardHandlers()
213 AddHandler(new wxGIFDecoder
);
216 AddHandler(new wxANIDecoder
);
217 #endif // wxUSE_ICO_CUR
220 void wxAnimation::CleanUpHandlers()
222 wxAnimationDecoderList::compatibility_iterator node
= sm_handlers
.GetFirst();
225 wxAnimationDecoder
*handler
= (wxAnimationDecoder
*)node
->GetData();
226 wxAnimationDecoderList::compatibility_iterator next
= node
->GetNext();
235 // A module to allow wxAnimation initialization/cleanup
236 // without calling these functions from app.cpp or from
237 // the user's application.
239 class wxAnimationModule
: public wxModule
241 DECLARE_DYNAMIC_CLASS(wxAnimationModule
)
243 wxAnimationModule() {}
244 bool OnInit() { wxAnimation::InitStandardHandlers(); return true; };
245 void OnExit() { wxAnimation::CleanUpHandlers(); };
248 IMPLEMENT_DYNAMIC_CLASS(wxAnimationModule
, wxModule
)
251 // ----------------------------------------------------------------------------
253 // ----------------------------------------------------------------------------
255 IMPLEMENT_CLASS(wxAnimationCtrl
, wxAnimationCtrlBase
)
256 BEGIN_EVENT_TABLE(wxAnimationCtrl
, wxAnimationCtrlBase
)
257 EVT_PAINT(wxAnimationCtrl::OnPaint
)
258 EVT_SIZE(wxAnimationCtrl::OnSize
)
259 EVT_TIMER(wxID_ANY
, wxAnimationCtrl::OnTimer
)
262 wxAnimationCtrl::wxAnimationCtrl()
267 m_useWinBackgroundColour
= false;
270 bool wxAnimationCtrl::Create(wxWindow
*parent
, wxWindowID id
,
271 const wxAnimation
& animation
, const wxPoint
& pos
,
272 const wxSize
& size
, long style
, const wxString
& name
)
274 m_animation
= animation
;
278 m_useWinBackgroundColour
= false;
279 m_timer
.SetOwner(this);
281 if (!base_type::Create(parent
, id
, pos
, size
, style
, wxDefaultValidator
, name
))
284 // by default we get the same background colour of our parent
285 SetBackgroundColour(parent
->GetBackgroundColour());
289 wxAnimationCtrl::~wxAnimationCtrl()
294 bool wxAnimationCtrl::LoadFile(const wxString
& filename
, wxAnimationType type
)
297 if (!anim
.LoadFile(filename
, type
) ||
305 wxSize
wxAnimationCtrl::DoGetBestSize() const
307 if (m_animation
.IsOk() && !this->HasFlag(wxAC_NO_AUTORESIZE
))
308 return m_animation
.GetSize();
310 return wxSize(100, 100);
313 void wxAnimationCtrl::SetAnimation(const wxAnimation
& animation
)
318 m_animation
= animation
;
320 if (m_animation
.GetBackgroundColour() == wxNullColour
)
321 SetUseWindowBackgroundColour();
322 if (!this->HasFlag(wxAC_NO_AUTORESIZE
))
325 // display first frame
327 if (m_animation
.IsOk())
328 RebuildBackingStoreUpToFrame(0);
333 dc
.SelectObject(m_backingStore
);
335 // Draw the background
336 DisposeToBackground(dc
);
342 void wxAnimationCtrl::FitToAnimation()
344 SetSize(m_animation
.GetSize());
348 // ----------------------------------------------------------------------------
349 // wxAnimationCtrl - stop/play methods
350 // ----------------------------------------------------------------------------
352 void wxAnimationCtrl::Stop()
354 // leave current frame displayed until Play() is called again
359 bool wxAnimationCtrl::Play(bool looped
)
361 if (!m_animation
.IsOk())
364 int oldframe
= m_currentFrame
;
369 // small optimization: if the back store was already updated to the
370 // first frame, don't rebuild it
372 RebuildBackingStoreUpToFrame(0);
374 // DrawCurrentFrame() will use our updated backing store
375 wxClientDC
clientDC(this);
376 DrawCurrentFrame(clientDC
);
379 int delay
= m_animation
.GetDelay(0);
381 delay
= 1; // 0 is invalid timeout for wxTimer.
382 m_timer
.Start(delay
);
389 // ----------------------------------------------------------------------------
390 // wxAnimationCtrl - rendering methods
391 // ----------------------------------------------------------------------------
393 void wxAnimationCtrl::RebuildBackingStoreUpToFrame(size_t frame
)
395 // if we've not created the backing store yet or it's too
396 // small, then recreate it
397 wxSize sz
= m_animation
.GetSize(),
398 winsz
= GetClientSize();
399 int w
= wxMin(sz
.GetWidth(), winsz
.GetWidth());
400 int h
= wxMin(sz
.GetHeight(), winsz
.GetHeight());
402 if ( !m_backingStore
.IsOk() ||
403 m_backingStore
.GetWidth() < w
|| m_backingStore
.GetHeight() < h
)
405 m_backingStore
.Create(w
, h
);
409 dc
.SelectObject(m_backingStore
);
411 // Draw the background
412 DisposeToBackground(dc
);
414 // Draw all intermediate frames that haven't been removed from the animation
415 for (size_t i
= 0; i
< frame
; i
++)
417 if (m_animation
.GetDisposalMethod(i
) == wxANIM_DONOTREMOVE
||
418 m_animation
.GetDisposalMethod(i
) == wxANIM_UNSPECIFIED
)
424 // finally draw this frame
425 DrawFrame(dc
, frame
);
426 dc
.SelectObject(wxNullBitmap
);
429 void wxAnimationCtrl::IncrementalUpdateBackingStore()
432 dc
.SelectObject(m_backingStore
);
435 // since wxAnimationCtrl can only play animations forward, without skipping
436 // frames, we can be sure that m_backingStore contains the m_currentFrame-1
437 // frame and thus we just need to dispose the m_currentFrame-1 frame and
438 // render the m_currentFrame-th one.
440 if (m_currentFrame
== 0)
442 // before drawing the first frame always dispose to bg colour
443 DisposeToBackground(dc
);
447 switch (m_animation
.GetDisposalMethod(m_currentFrame
-1))
449 case wxANIM_TOBACKGROUND
:
450 DisposeToBackground(dc
);
453 case wxANIM_TOPREVIOUS
:
454 // this disposal should never be used too often.
455 // E.g. GIF specification explicitely say to keep the usage of this
456 // disposal limited to the minimum.
457 // In fact it may require a lot of time to restore
458 if (m_currentFrame
== 1)
460 // if 0-th frame disposal is to restore to previous frame,
461 // the best we can do is to restore to background
462 DisposeToBackground(dc
);
465 RebuildBackingStoreUpToFrame(m_currentFrame
-2);
468 case wxANIM_DONOTREMOVE
:
469 case wxANIM_UNSPECIFIED
:
474 // now just draw the current frame on the top of the backing store
475 DrawFrame(dc
, m_currentFrame
);
476 dc
.SelectObject(wxNullBitmap
);
479 void wxAnimationCtrl::DrawFrame(wxDC
&dc
, size_t frame
)
482 // this draw stuff is not as fast as possible: the wxAnimationDecoder
483 // needs first to convert from its internal format to wxImage RGB24;
484 // the wxImage is then converted as a wxBitmap and finally blitted.
485 // If wxAnimationDecoder had a function to convert directly from its
486 // internal format to a port-specific wxBitmap, it would be somewhat faster.
487 wxBitmap
bmp(m_animation
.GetFrame(frame
));
488 dc
.DrawBitmap(bmp
, m_animation
.GetFramePosition(frame
),
489 true /* use mask */);
492 void wxAnimationCtrl::DrawCurrentFrame(wxDC
& dc
)
494 wxASSERT( m_backingStore
.IsOk() );
496 // m_backingStore always contains the current frame
497 dc
.DrawBitmap(m_backingStore
, 0, 0);
500 void wxAnimationCtrl::DisposeToBackground(wxDC
& dc
)
502 wxBrush
brush(IsUsingWindowBackgroundColour()
503 ? GetBackgroundColour()
504 : m_animation
.GetBackgroundColour());
505 dc
.SetBackground(brush
);
509 // ----------------------------------------------------------------------------
510 // wxAnimationCtrl - event handlers
511 // ----------------------------------------------------------------------------
513 void wxAnimationCtrl::OnPaint(wxPaintEvent
& WXUNUSED(event
))
515 // VERY IMPORTANT: the wxPaintDC *must* be created in any case
518 // both if we are playing or not, we need to refresh the current frame
519 if ( m_backingStore
.IsOk() )
520 DrawCurrentFrame(dc
);
521 //else: m_animation is not valid and thus we don't have a valid backing store...
524 void wxAnimationCtrl::OnTimer(wxTimerEvent
&WXUNUSED(event
))
527 if (m_currentFrame
== m_animation
.GetFrameCount())
529 // Should a non-looped animation display the last frame?
537 m_currentFrame
= 0; // let's restart
540 IncrementalUpdateBackingStore();
543 DrawCurrentFrame(dc
);
546 // without this, the animation currently doesn't redraw under Mac
550 // Set the timer for the next frame
551 int delay
= m_animation
.GetDelay(m_currentFrame
);
553 delay
= 1; // 0 is invalid timeout for wxTimer.
554 m_timer
.Start(delay
);
557 void wxAnimationCtrl::OnSize(wxSizeEvent
&WXUNUSED(event
))
559 // NB: resizing an animation control may take a lot of time
560 // for big animations as the backing store must be
561 // extended and rebuilt. Try to avoid it!!
562 if (m_animation
.IsOk())
563 RebuildBackingStoreUpToFrame(m_currentFrame
);
566 #endif // wxUSE_ANIMATIONCTRL