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
7 // Copyright: (c) Julian Smart and Guillermo Rodriguez Garcia
8 // Licence: wxWindows licence
9 ///////////////////////////////////////////////////////////////////////////////
11 #include "wx/wxprec.h"
17 #if wxUSE_ANIMATIONCTRL
19 #include "wx/animate.h"
24 #include "wx/dcmemory.h"
25 #include "wx/dcclient.h"
26 #include "wx/module.h"
29 #include "wx/wfstream.h"
30 #include "wx/gifdecod.h"
31 #include "wx/anidecod.h"
33 #include "wx/listimpl.cpp"
34 WX_DEFINE_LIST(wxAnimationDecoderList
)
36 wxAnimationDecoderList
wxAnimation::sm_handlers
;
39 // ----------------------------------------------------------------------------
41 // ----------------------------------------------------------------------------
43 IMPLEMENT_DYNAMIC_CLASS(wxAnimation
, wxAnimationBase
)
44 #define M_ANIMDATA static_cast<wxAnimationDecoder*>(m_refData)
46 wxSize
wxAnimation::GetSize() const
48 wxCHECK_MSG( IsOk(), wxDefaultSize
, wxT("invalid animation") );
50 return M_ANIMDATA
->GetAnimationSize();
53 unsigned int wxAnimation::GetFrameCount() const
55 wxCHECK_MSG( IsOk(), 0, wxT("invalid animation") );
57 return M_ANIMDATA
->GetFrameCount();
60 wxImage
wxAnimation::GetFrame(unsigned int i
) const
62 wxCHECK_MSG( IsOk(), wxNullImage
, wxT("invalid animation") );
65 if (!M_ANIMDATA
->ConvertToImage(i
, &ret
))
70 int wxAnimation::GetDelay(unsigned int i
) const
72 wxCHECK_MSG( IsOk(), 0, wxT("invalid animation") );
74 return M_ANIMDATA
->GetDelay(i
);
77 wxPoint
wxAnimation::GetFramePosition(unsigned int frame
) const
79 wxCHECK_MSG( IsOk(), wxDefaultPosition
, wxT("invalid animation") );
81 return M_ANIMDATA
->GetFramePosition(frame
);
84 wxSize
wxAnimation::GetFrameSize(unsigned int frame
) const
86 wxCHECK_MSG( IsOk(), wxDefaultSize
, wxT("invalid animation") );
88 return M_ANIMDATA
->GetFrameSize(frame
);
91 wxAnimationDisposal
wxAnimation::GetDisposalMethod(unsigned int frame
) const
93 wxCHECK_MSG( IsOk(), wxANIM_UNSPECIFIED
, wxT("invalid animation") );
95 return M_ANIMDATA
->GetDisposalMethod(frame
);
98 wxColour
wxAnimation::GetTransparentColour(unsigned int frame
) const
100 wxCHECK_MSG( IsOk(), wxNullColour
, wxT("invalid animation") );
102 return M_ANIMDATA
->GetTransparentColour(frame
);
105 wxColour
wxAnimation::GetBackgroundColour() const
107 wxCHECK_MSG( IsOk(), wxNullColour
, wxT("invalid animation") );
109 return M_ANIMDATA
->GetBackgroundColour();
112 bool wxAnimation::LoadFile(const wxString
& filename
, wxAnimationType type
)
114 wxFileInputStream
stream(filename
);
115 if ( !stream
.IsOk() )
118 return Load(stream
, type
);
121 bool wxAnimation::Load(wxInputStream
&stream
, wxAnimationType type
)
125 const wxAnimationDecoder
*handler
;
126 if ( type
== wxANIMATION_TYPE_ANY
)
128 for ( wxAnimationDecoderList::compatibility_iterator node
= sm_handlers
.GetFirst();
129 node
; node
= node
->GetNext() )
131 handler
=(const wxAnimationDecoder
*)node
->GetData();
133 if ( handler
->CanRead(stream
) )
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
);
142 wxLogWarning( _("No handler found for animation type.") );
146 handler
= FindHandler(type
);
150 wxLogWarning( _("No animation handler for type %ld defined."), type
);
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();
160 if (stream
.IsSeekable() && !M_ANIMDATA
->CanRead(stream
))
162 wxLogError(_("Animation file is not of type %ld."), type
);
166 return M_ANIMDATA
->Load(stream
);
170 // ----------------------------------------------------------------------------
171 // animation decoders
172 // ----------------------------------------------------------------------------
174 void wxAnimation::AddHandler( wxAnimationDecoder
*handler
)
176 // Check for an existing handler of the type being added.
177 if (FindHandler( handler
->GetType() ) == 0)
179 sm_handlers
.Append( handler
);
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.
188 wxLogDebug( wxT("Adding duplicate animation handler for '%d' type"),
189 handler
->GetType() );
194 void wxAnimation::InsertHandler( wxAnimationDecoder
*handler
)
196 // Check for an existing handler of the type being added.
197 if (FindHandler( handler
->GetType() ) == 0)
199 sm_handlers
.Insert( handler
);
203 // see AddHandler for additional comments.
204 wxLogDebug( wxT("Inserting duplicate animation handler for '%d' type"),
205 handler
->GetType() );
210 const wxAnimationDecoder
*wxAnimation::FindHandler( wxAnimationType animType
)
212 wxAnimationDecoderList::compatibility_iterator node
= sm_handlers
.GetFirst();
215 const wxAnimationDecoder
*handler
= (const wxAnimationDecoder
*)node
->GetData();
216 if (handler
->GetType() == animType
) return handler
;
217 node
= node
->GetNext();
222 void wxAnimation::InitStandardHandlers()
225 AddHandler(new wxGIFDecoder
);
228 AddHandler(new wxANIDecoder
);
229 #endif // wxUSE_ICO_CUR
232 void wxAnimation::CleanUpHandlers()
234 wxAnimationDecoderList::compatibility_iterator node
= sm_handlers
.GetFirst();
237 wxAnimationDecoder
*handler
= (wxAnimationDecoder
*)node
->GetData();
238 wxAnimationDecoderList::compatibility_iterator next
= node
->GetNext();
247 // A module to allow wxAnimation initialization/cleanup
248 // without calling these functions from app.cpp or from
249 // the user's application.
251 class wxAnimationModule
: public wxModule
253 DECLARE_DYNAMIC_CLASS(wxAnimationModule
)
255 wxAnimationModule() {}
256 bool OnInit() { wxAnimation::InitStandardHandlers(); return true; }
257 void OnExit() { wxAnimation::CleanUpHandlers(); }
260 IMPLEMENT_DYNAMIC_CLASS(wxAnimationModule
, wxModule
)
263 // ----------------------------------------------------------------------------
265 // ----------------------------------------------------------------------------
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
)
274 void wxAnimationCtrl::Init()
280 // use the window background colour by default to be consistent
281 // with the GTK+ native version
282 m_useWinBackgroundColour
= true;
285 bool wxAnimationCtrl::Create(wxWindow
*parent
, wxWindowID id
,
286 const wxAnimation
& animation
, const wxPoint
& pos
,
287 const wxSize
& size
, long style
, const wxString
& name
)
289 m_timer
.SetOwner(this);
291 if (!base_type::Create(parent
, id
, pos
, size
, style
, wxDefaultValidator
, name
))
294 // by default we get the same background colour of our parent
295 SetBackgroundColour(parent
->GetBackgroundColour());
297 SetAnimation(animation
);
302 wxAnimationCtrl::~wxAnimationCtrl()
307 bool wxAnimationCtrl::LoadFile(const wxString
& filename
, wxAnimationType type
)
309 wxFileInputStream
fis(filename
);
312 return Load(fis
, type
);
315 bool wxAnimationCtrl::Load(wxInputStream
& stream
, wxAnimationType type
)
318 if ( !anim
.Load(stream
, type
) || !anim
.IsOk() )
325 wxSize
wxAnimationCtrl::DoGetBestSize() const
327 if (m_animation
.IsOk() && !this->HasFlag(wxAC_NO_AUTORESIZE
))
328 return m_animation
.GetSize();
330 return wxSize(100, 100);
333 void wxAnimationCtrl::SetAnimation(const wxAnimation
& animation
)
338 // set new animation even if it's wxNullAnimation
339 m_animation
= animation
;
340 if (!m_animation
.IsOk())
342 DisplayStaticImage();
346 if (m_animation
.GetBackgroundColour() == wxNullColour
)
347 SetUseWindowBackgroundColour();
348 if (!this->HasFlag(wxAC_NO_AUTORESIZE
))
351 DisplayStaticImage();
354 void wxAnimationCtrl::SetInactiveBitmap(const wxBitmap
&bmp
)
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
.GetMask() != NULL
&& GetParent() != NULL
)
362 SetBackgroundColour(GetParent()->GetBackgroundColour());
364 wxAnimationCtrlBase::SetInactiveBitmap(bmp
);
367 void wxAnimationCtrl::FitToAnimation()
369 SetSize(m_animation
.GetSize());
372 bool wxAnimationCtrl::SetBackgroundColour(const wxColour
& colour
)
374 if ( !wxWindow::SetBackgroundColour(colour
) )
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)
380 DisplayStaticImage();
386 // ----------------------------------------------------------------------------
387 // wxAnimationCtrl - stop/play methods
388 // ----------------------------------------------------------------------------
390 void wxAnimationCtrl::Stop()
395 // reset frame counter
398 DisplayStaticImage();
401 bool wxAnimationCtrl::Play(bool looped
)
403 if (!m_animation
.IsOk())
409 if (!RebuildBackingStoreUpToFrame(0))
414 // do a ClearBackground() to avoid that e.g. the custom static bitmap which
415 // was eventually shown previously remains partially drawn
418 // DrawCurrentFrame() will use our updated backing store
419 wxClientDC
clientDC(this);
420 DrawCurrentFrame(clientDC
);
423 int delay
= m_animation
.GetDelay(0);
425 delay
= 1; // 0 is invalid timeout for wxTimer.
426 m_timer
.Start(delay
, true);
433 // ----------------------------------------------------------------------------
434 // wxAnimationCtrl - rendering methods
435 // ----------------------------------------------------------------------------
437 bool wxAnimationCtrl::RebuildBackingStoreUpToFrame(unsigned int frame
)
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());
446 if ( !m_backingStore
.IsOk() ||
447 m_backingStore
.GetWidth() < w
|| m_backingStore
.GetHeight() < h
)
449 if (!m_backingStore
.Create(w
, h
))
454 dc
.SelectObject(m_backingStore
);
456 // Draw the background
457 DisposeToBackground(dc
);
459 // Draw all intermediate frames that haven't been removed from the animation
460 for (unsigned int i
= 0; i
< frame
; i
++)
462 if (m_animation
.GetDisposalMethod(i
) == wxANIM_DONOTREMOVE
||
463 m_animation
.GetDisposalMethod(i
) == wxANIM_UNSPECIFIED
)
467 else if (m_animation
.GetDisposalMethod(i
) == wxANIM_TOBACKGROUND
)
468 DisposeToBackground(dc
, m_animation
.GetFramePosition(i
),
469 m_animation
.GetFrameSize(i
));
472 // finally draw this frame
473 DrawFrame(dc
, frame
);
474 dc
.SelectObject(wxNullBitmap
);
479 void wxAnimationCtrl::IncrementalUpdateBackingStore()
482 dc
.SelectObject(m_backingStore
);
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.
490 if (m_currentFrame
== 0)
492 // before drawing the first frame always dispose to bg colour
493 DisposeToBackground(dc
);
497 switch (m_animation
.GetDisposalMethod(m_currentFrame
-1))
499 case wxANIM_TOBACKGROUND
:
500 DisposeToBackground(dc
, m_animation
.GetFramePosition(m_currentFrame
-1),
501 m_animation
.GetFrameSize(m_currentFrame
-1));
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)
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
);
516 if (!RebuildBackingStoreUpToFrame(m_currentFrame
-2))
520 case wxANIM_DONOTREMOVE
:
521 case wxANIM_UNSPECIFIED
:
526 // now just draw the current frame on the top of the backing store
527 DrawFrame(dc
, m_currentFrame
);
528 dc
.SelectObject(wxNullBitmap
);
531 void wxAnimationCtrl::DisplayStaticImage()
533 wxASSERT(!IsPlaying());
535 // m_bmpStaticReal will be updated only if necessary...
538 if (m_bmpStaticReal
.IsOk())
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() )
545 temp
.SelectObject(m_backingStore
);
546 DisposeToBackground(temp
);
547 temp
.DrawBitmap(m_bmpStaticReal
, 0, 0, true /* use mask */);
550 m_backingStore
= m_bmpStaticReal
;
554 // put in the backing store the first frame of the animation
555 if (!m_animation
.IsOk() ||
556 !RebuildBackingStoreUpToFrame(0))
558 m_animation
= wxNullAnimation
;
559 DisposeToBackground();
566 void wxAnimationCtrl::DrawFrame(wxDC
&dc
, unsigned int frame
)
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 */);
579 void wxAnimationCtrl::DrawCurrentFrame(wxDC
& dc
)
581 wxASSERT( m_backingStore
.IsOk() );
583 // m_backingStore always contains the current frame
584 dc
.DrawBitmap(m_backingStore
, 0, 0, true /* use mask in case it's present */);
587 void wxAnimationCtrl::DisposeToBackground()
589 // clear the backing store
591 dc
.SelectObject(m_backingStore
);
593 DisposeToBackground(dc
);
596 void wxAnimationCtrl::DisposeToBackground(wxDC
& dc
)
598 wxColour col
= IsUsingWindowBackgroundColour()
599 ? GetBackgroundColour()
600 : m_animation
.GetBackgroundColour();
603 dc
.SetBackground(brush
);
607 void wxAnimationCtrl::DisposeToBackground(wxDC
& dc
, const wxPoint
&pos
, const wxSize
&sz
)
609 wxColour col
= IsUsingWindowBackgroundColour()
610 ? GetBackgroundColour()
611 : m_animation
.GetBackgroundColour();
613 dc
.SetBrush(brush
); // SetBrush and not SetBackground !!
614 dc
.SetPen(*wxTRANSPARENT_PEN
);
615 dc
.DrawRectangle(pos
, sz
);
618 // ----------------------------------------------------------------------------
619 // wxAnimationCtrl - event handlers
620 // ----------------------------------------------------------------------------
622 void wxAnimationCtrl::OnPaint(wxPaintEvent
& WXUNUSED(event
))
624 // VERY IMPORTANT: the wxPaintDC *must* be created in any case
627 if ( m_backingStore
.IsOk() )
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 */);
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
);
642 void wxAnimationCtrl::OnTimer(wxTimerEvent
&WXUNUSED(event
))
645 if (m_currentFrame
== m_animation
.GetFrameCount())
647 // Should a non-looped animation display the last frame?
654 m_currentFrame
= 0; // let's restart
657 IncrementalUpdateBackingStore();
660 DrawCurrentFrame(dc
);
663 // without this, the animation currently doesn't redraw under Mac
667 // Set the timer for the next frame
668 int delay
= m_animation
.GetDelay(m_currentFrame
);
670 delay
= 1; // 0 is invalid timeout for wxTimer.
671 m_timer
.Start(delay
, true);
674 void wxAnimationCtrl::OnSize(wxSizeEvent
&WXUNUSED(event
))
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())
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
689 if (!RebuildBackingStoreUpToFrame(m_currentFrame
))
690 Stop(); // in case we are playing
695 #endif // wxUSE_ANIMATIONCTRL