added wxAnimationCtrl (patch 1570325)
[wxWidgets.git] / src / generic / animateg.cpp
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$
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
19 #if wxUSE_ANIMATIONCTRL
20
21 #include "wx/log.h"
22 #include "wx/wfstream.h"
23 #include "wx/image.h"
24 #include "wx/gifdecod.h"
25 #include "wx/anidecod.h"
26 #include "wx/dcmemory.h"
27 #include "wx/dc.h"
28 #include "wx/dcclient.h"
29 #include "wx/animate.h"
30 #include "wx/animdecod.h"
31
32
33 #include <wx/listimpl.cpp>
34 WX_DEFINE_LIST(wxAnimationDecoderList);
35
36 wxAnimationDecoderList wxAnimation::sm_handlers;
37
38
39
40 // ----------------------------------------------------------------------------
41 // wxAnimation
42 // ----------------------------------------------------------------------------
43
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
54 size_t wxAnimation::GetFrameCount() const
55 {
56 wxCHECK_MSG( IsOk(), 0, wxT("invalid animation") );
57
58 return M_ANIMDATA->GetFrameCount();
59 }
60
61 wxImage wxAnimation::GetFrame(size_t 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
71 int wxAnimation::GetDelay(size_t i) const
72 {
73 wxCHECK_MSG( IsOk(), 0, wxT("invalid animation") );
74
75 return M_ANIMDATA->GetDelay(i);
76 }
77
78 wxPoint wxAnimation::GetFramePosition(size_t frame) const
79 {
80 wxCHECK_MSG( IsOk(), wxDefaultPosition, wxT("invalid animation") );
81
82 return M_ANIMDATA->GetFramePosition(frame);
83 }
84
85 wxAnimationDisposal wxAnimation::GetDisposalMethod(size_t frame) const
86 {
87 wxCHECK_MSG( IsOk(), wxANIM_UNSPECIFIED, wxT("invalid animation") );
88
89 return M_ANIMDATA->GetDisposalMethod(frame);
90 }
91
92 wxColour wxAnimation::GetBackgroundColour() const
93 {
94 wxCHECK_MSG( IsOk(), wxNullColour, wxT("invalid animation") );
95
96 return M_ANIMDATA->GetBackgroundColour();
97 }
98
99 bool wxAnimation::LoadFile(const wxString& filename, wxAnimationType type)
100 {
101 wxFileInputStream stream(filename);
102 if (!stream.Ok())
103 return false;
104
105 return Load(stream, type);
106 }
107
108 bool wxAnimation::Load(wxInputStream &stream, wxAnimationType type)
109 {
110 UnRef();
111
112 const wxAnimationDecoder *handler;
113 if ( type == wxANIMATION_TYPE_ANY )
114 {
115 for ( wxAnimationDecoderList::compatibility_iterator node = sm_handlers.GetFirst();
116 node; node = node->GetNext() )
117 {
118 handler=(const wxAnimationDecoder*)node->GetData();
119
120 if ( handler->CanRead(stream) )
121 {
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);
126 }
127
128 }
129
130 wxLogWarning( _("No handler found for animation type.") );
131 return false;
132 }
133
134 handler = FindHandler(type);
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
140 if (handler == NULL)
141 {
142 wxLogWarning( _("No animation handler for type %ld defined."), type );
143
144 return false;
145 }
146
147 if (stream.IsSeekable() && !M_ANIMDATA->CanRead(stream))
148 {
149 wxLogError(_("Animation file is not of type %ld."), type);
150 return false;
151 }
152 else
153 return M_ANIMDATA->Load(stream);
154 }
155
156
157 // ----------------------------------------------------------------------------
158 // animation decoders
159 // ----------------------------------------------------------------------------
160
161 void wxAnimation::AddHandler( wxAnimationDecoder *handler )
162 {
163 // Check for an existing handler of the type being added.
164 if (FindHandler( handler->GetType() ) == 0)
165 {
166 sm_handlers.Append( handler );
167 }
168 else
169 {
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.
175
176 wxLogDebug( _T("Adding duplicate animation handler for '%d' type"),
177 handler->GetType() );
178 delete handler;
179 }
180 }
181
182 void wxAnimation::InsertHandler( wxAnimationDecoder *handler )
183 {
184 // Check for an existing handler of the type being added.
185 if (FindHandler( handler->GetType() ) == 0)
186 {
187 sm_handlers.Insert( handler );
188 }
189 else
190 {
191 // see AddHandler for additional comments.
192 wxLogDebug( _T("Inserting duplicate animation handler for '%d' type"),
193 handler->GetType() );
194 delete handler;
195 }
196 }
197
198 const wxAnimationDecoder *wxAnimation::FindHandler( wxAnimationType animType )
199 {
200 wxAnimationDecoderList::compatibility_iterator node = sm_handlers.GetFirst();
201 while (node)
202 {
203 const wxAnimationDecoder *handler = (const wxAnimationDecoder *)node->GetData();
204 if (handler->GetType() == animType) return handler;
205 node = node->GetNext();
206 }
207 return 0;
208 }
209
210 void wxAnimation::InitStandardHandlers()
211 {
212 AddHandler(new wxGIFDecoder);
213 AddHandler(new wxANIDecoder);
214 }
215
216 void wxAnimation::CleanUpHandlers()
217 {
218 wxAnimationDecoderList::compatibility_iterator node = sm_handlers.GetFirst();
219 while (node)
220 {
221 wxAnimationDecoder *handler = (wxAnimationDecoder *)node->GetData();
222 wxAnimationDecoderList::compatibility_iterator next = node->GetNext();
223 delete handler;
224 node = next;
225 }
226
227 sm_handlers.Clear();
228 }
229
230
231 // A module to allow wxAnimation initialization/cleanup
232 // without calling these functions from app.cpp or from
233 // the user's application.
234
235 class wxAnimationModule: public wxModule
236 {
237 DECLARE_DYNAMIC_CLASS(wxAnimationModule)
238 public:
239 wxAnimationModule() {}
240 bool OnInit() { wxAnimation::InitStandardHandlers(); return true; };
241 void OnExit() { wxAnimation::CleanUpHandlers(); };
242 };
243
244 IMPLEMENT_DYNAMIC_CLASS(wxAnimationModule, wxModule)
245
246
247
248
249 // ----------------------------------------------------------------------------
250 // wxAnimationCtrl
251 // ----------------------------------------------------------------------------
252
253 IMPLEMENT_CLASS(wxAnimationCtrl, wxAnimationCtrlBase)
254 BEGIN_EVENT_TABLE(wxAnimationCtrl, wxAnimationCtrlBase)
255 EVT_PAINT(wxAnimationCtrl::OnPaint)
256 EVT_SIZE(wxAnimationCtrl::OnSize)
257 EVT_TIMER(wxID_ANY, wxAnimationCtrl::OnTimer)
258 END_EVENT_TABLE()
259
260 bool wxAnimationCtrl::Create(wxWindow *parent, wxWindowID id,
261 const wxAnimation& animation, const wxPoint& pos,
262 const wxSize& size, long style, const wxString& name)
263 {
264 m_animation = animation;
265 m_currentFrame = 0;
266 m_looped = true;
267 m_isPlaying = false;
268 m_useWinBackgroundColour = false;
269 m_timer.SetOwner(this);
270
271 if (!wxControl::Create(parent, id, pos, size, style, wxDefaultValidator, name))
272 return false;
273
274 // by default we get the same background colour of our parent
275 SetBackgroundColour(parent->GetBackgroundColour());
276 return true;
277 }
278
279 wxAnimationCtrl::~wxAnimationCtrl()
280 {
281 Stop();
282 }
283
284 bool wxAnimationCtrl::LoadFile(const wxString& filename, wxAnimationType type)
285 {
286 wxAnimation anim;
287 if (!anim.LoadFile(filename, type) ||
288 !anim.IsOk())
289 return false;
290
291 SetAnimation(anim);
292 return true;
293 }
294
295 wxSize wxAnimationCtrl::DoGetBestSize() const
296 {
297 if (m_animation.IsOk() && !this->HasFlag(wxAC_NO_AUTORESIZE))
298 return m_animation.GetSize();
299
300 return wxSize(100, 100);
301 }
302
303 void wxAnimationCtrl::SetAnimation(const wxAnimation& animation)
304 {
305 if (IsPlaying())
306 Stop();
307
308 m_animation = animation;
309
310 if (m_animation.GetBackgroundColour() == wxNullColour)
311 SetUseWindowBackgroundColour();
312 if (!this->HasFlag(wxAC_NO_AUTORESIZE))
313 FitToAnimation();
314
315 // display first frame
316 m_currentFrame = 0;
317 if (m_animation.IsOk())
318 RebuildBackingStoreUpToFrame(0);
319 else
320 {
321 // clear to
322 wxMemoryDC dc;
323 dc.SelectObject(m_backingStore);
324
325 // Draw the background
326 DisposeToBackground(dc);
327 }
328
329 Refresh();
330 }
331
332 void wxAnimationCtrl::FitToAnimation()
333 {
334 SetSize(m_animation.GetSize());
335 }
336
337
338 // ----------------------------------------------------------------------------
339 // wxAnimationCtrl - stop/play methods
340 // ----------------------------------------------------------------------------
341
342 void wxAnimationCtrl::Stop()
343 {
344 // leave current frame displayed until Play() is called again
345 m_timer.Stop();
346 m_isPlaying = false;
347 }
348
349 bool wxAnimationCtrl::Play(bool looped)
350 {
351 if (!m_animation.IsOk())
352 return false;
353
354 int oldframe = m_currentFrame;
355 m_looped = looped;
356 m_currentFrame = 0;
357 m_isPlaying = true;
358
359 // small optimization: if the back store was already updated to the
360 // first frame, don't rebuild it
361 if (oldframe != 0)
362 RebuildBackingStoreUpToFrame(0);
363
364 // DrawCurrentFrame() will use our updated backing store
365 wxClientDC clientDC(this);
366 DrawCurrentFrame(clientDC);
367
368 // start the timer
369 int delay = m_animation.GetDelay(0);
370 if (delay == 0)
371 delay = 1; // 0 is invalid timeout for wxTimer.
372 m_timer.Start(delay);
373
374 return true;
375 }
376
377
378
379 // ----------------------------------------------------------------------------
380 // wxAnimationCtrl - rendering methods
381 // ----------------------------------------------------------------------------
382
383 void wxAnimationCtrl::RebuildBackingStoreUpToFrame(size_t frame)
384 {
385 // if we've not created the backing store yet or it's too
386 // small, then recreate it
387 wxSize sz = m_animation.GetSize(),
388 winsz = GetClientSize();
389 int w = wxMin(sz.GetWidth(), winsz.GetWidth());
390 int h = wxMin(sz.GetHeight(), winsz.GetHeight());
391
392 if (m_backingStore.GetWidth() < w ||
393 m_backingStore.GetHeight() < h)
394 m_backingStore.Create(w, h);
395
396 wxMemoryDC dc;
397 dc.SelectObject(m_backingStore);
398
399 // Draw the background
400 DisposeToBackground(dc);
401
402 // Draw all intermediate frames that haven't been removed from the animation
403 for (size_t i = 0; i < frame; i++)
404 {
405 if (m_animation.GetDisposalMethod(i) == wxANIM_DONOTREMOVE ||
406 m_animation.GetDisposalMethod(i) == wxANIM_UNSPECIFIED)
407 {
408 DrawFrame(dc, i);
409 }
410 }
411
412 // finally draw this frame
413 DrawFrame(dc, frame);
414 dc.SelectObject(wxNullBitmap);
415 }
416
417 void wxAnimationCtrl::IncrementalUpdateBackingStore()
418 {
419 wxMemoryDC dc;
420 dc.SelectObject(m_backingStore);
421
422 // OPTIMIZATION:
423 // since wxAnimationCtrl can only play animations forward, without skipping
424 // frames, we can be sure that m_backingStore contains the m_currentFrame-1
425 // frame and thus we just need to dispose the m_currentFrame-1 frame and
426 // render the m_currentFrame-th one.
427
428 if (m_currentFrame == 0)
429 {
430 // before drawing the first frame always dispose to bg colour
431 DisposeToBackground(dc);
432 }
433 else
434 {
435 switch (m_animation.GetDisposalMethod(m_currentFrame-1))
436 {
437 case wxANIM_TOBACKGROUND:
438 DisposeToBackground(dc);
439 break;
440
441 case wxANIM_TOPREVIOUS:
442 // this disposal should never be used too often.
443 // E.g. GIF specification explicitely say to keep the usage of this
444 // disposal limited to the minimum.
445 // In fact it may require a lot of time to restore
446 if (m_currentFrame == 1)
447 {
448 // if 0-th frame disposal is to restore to previous frame,
449 // the best we can do is to restore to background
450 DisposeToBackground(dc);
451 }
452 else
453 RebuildBackingStoreUpToFrame(m_currentFrame-2);
454 break;
455
456 case wxANIM_DONOTREMOVE:
457 case wxANIM_UNSPECIFIED:
458 break;
459 }
460 }
461
462 // now just draw the current frame on the top of the backing store
463 DrawFrame(dc, m_currentFrame);
464 dc.SelectObject(wxNullBitmap);
465 }
466
467 void wxAnimationCtrl::DrawFrame(wxDC &dc, size_t frame)
468 {
469 // PERFORMANCE NOTE:
470 // this draw stuff is not as fast as possible: the wxAnimationDecoder
471 // needs first to convert from its internal format to wxImage RGB24;
472 // the wxImage is then converted as a wxBitmap and finally blitted.
473 // If wxAnimationDecoder had a function to convert directly from its
474 // internal format to a port-specific wxBitmap, it would be somewhat faster.
475 wxBitmap bmp(m_animation.GetFrame(frame));
476 dc.DrawBitmap(bmp, m_animation.GetFramePosition(frame),
477 true /* use mask */);
478 }
479
480 void wxAnimationCtrl::DrawCurrentFrame(wxDC& dc)
481 {
482 wxASSERT(m_backingStore.Ok());
483
484 // m_backingStore always contains the current frame
485 dc.DrawBitmap(m_backingStore, 0, 0);
486 }
487
488 void wxAnimationCtrl::DisposeToBackground(wxDC& dc)
489 {
490 wxBrush brush(IsUsingWindowBackgroundColour() ?
491 this->GetBackgroundColour() : m_animation.GetBackgroundColour(), wxSOLID);
492 dc.SetBackground(brush);
493 dc.Clear();
494 }
495
496 // ----------------------------------------------------------------------------
497 // wxAnimationCtrl - event handlers
498 // ----------------------------------------------------------------------------
499
500 void wxAnimationCtrl::OnPaint(wxPaintEvent& WXUNUSED(event))
501 {
502 // VERY IMPORTANT: the wxPaintDC *must* be created in any case
503 wxPaintDC dc(this);
504
505 // both if we are playing or not, we need to refresh the current frame
506 if (m_backingStore.Ok())
507 DrawCurrentFrame(dc);
508 //else: m_animation is not valid and thus we don't have a valid backing store...
509 }
510
511 void wxAnimationCtrl::OnTimer(wxTimerEvent &WXUNUSED(event))
512 {
513 m_currentFrame++;
514 if (m_currentFrame == m_animation.GetFrameCount())
515 {
516 // Should a non-looped animation display the last frame?
517 if (!m_looped)
518 {
519 m_timer.Stop();
520 m_isPlaying = false;
521 return;
522 }
523 else
524 m_currentFrame = 0; // let's restart
525 }
526
527 IncrementalUpdateBackingStore();
528
529 wxClientDC dc(this);
530 DrawCurrentFrame(dc);
531
532 // Set the timer for the next frame
533 int delay = m_animation.GetDelay(m_currentFrame);
534 if (delay == 0)
535 delay = 1; // 0 is invalid timeout for wxTimer.
536 m_timer.Start(delay);
537 }
538
539 void wxAnimationCtrl::OnSize(wxSizeEvent &WXUNUSED(event))
540 {
541 // NB: resizing an animation control may take a lot of time
542 // for big animations as the backing store must be
543 // extended and rebuilt. Try to avoid it!!
544 if (m_animation.IsOk())
545 RebuildBackingStoreUpToFrame(m_currentFrame);
546 }
547
548 #endif // wxUSE_ANIMATIONCTRL
549