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