The rounded corners look really dumb at this size.
[wxWidgets.git] / src / generic / animateg.cpp
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
6 // Created: 13/8/99
7 // Copyright: (c) Julian Smart and Guillermo Rodriguez Garcia
8 // Licence: wxWindows licence
9 ///////////////////////////////////////////////////////////////////////////////
10
11 #include "wx/wxprec.h"
12
13 #ifdef __BORLANDC__
14 #pragma hdrstop
15 #endif //__BORLANDC__
16
17 #if wxUSE_ANIMATIONCTRL
18
19 #include "wx/animate.h"
20
21 #ifndef WX_PRECOMP
22 #include "wx/log.h"
23 #include "wx/image.h"
24 #include "wx/dcmemory.h"
25 #include "wx/dcclient.h"
26 #include "wx/module.h"
27 #endif
28
29 #include "wx/wfstream.h"
30 #include "wx/gifdecod.h"
31 #include "wx/anidecod.h"
32
33 #include "wx/listimpl.cpp"
34 WX_DEFINE_LIST(wxAnimationDecoderList)
35
36 wxAnimationDecoderList wxAnimation::sm_handlers;
37
38
39 // ----------------------------------------------------------------------------
40 // wxAnimation
41 // ----------------------------------------------------------------------------
42
43 IMPLEMENT_DYNAMIC_CLASS(wxAnimation, wxAnimationBase)
44 #define M_ANIMDATA static_cast<wxAnimationDecoder*>(m_refData)
45
46 wxSize wxAnimation::GetSize() const
47 {
48 wxCHECK_MSG( IsOk(), wxDefaultSize, wxT("invalid animation") );
49
50 return M_ANIMDATA->GetAnimationSize();
51 }
52
53 unsigned int wxAnimation::GetFrameCount() const
54 {
55 wxCHECK_MSG( IsOk(), 0, wxT("invalid animation") );
56
57 return M_ANIMDATA->GetFrameCount();
58 }
59
60 wxImage wxAnimation::GetFrame(unsigned int i) const
61 {
62 wxCHECK_MSG( IsOk(), wxNullImage, wxT("invalid animation") );
63
64 wxImage ret;
65 if (!M_ANIMDATA->ConvertToImage(i, &ret))
66 return wxNullImage;
67 return ret;
68 }
69
70 int wxAnimation::GetDelay(unsigned int i) const
71 {
72 wxCHECK_MSG( IsOk(), 0, wxT("invalid animation") );
73
74 return M_ANIMDATA->GetDelay(i);
75 }
76
77 wxPoint wxAnimation::GetFramePosition(unsigned int frame) const
78 {
79 wxCHECK_MSG( IsOk(), wxDefaultPosition, wxT("invalid animation") );
80
81 return M_ANIMDATA->GetFramePosition(frame);
82 }
83
84 wxSize wxAnimation::GetFrameSize(unsigned int frame) const
85 {
86 wxCHECK_MSG( IsOk(), wxDefaultSize, wxT("invalid animation") );
87
88 return M_ANIMDATA->GetFrameSize(frame);
89 }
90
91 wxAnimationDisposal wxAnimation::GetDisposalMethod(unsigned int frame) const
92 {
93 wxCHECK_MSG( IsOk(), wxANIM_UNSPECIFIED, wxT("invalid animation") );
94
95 return M_ANIMDATA->GetDisposalMethod(frame);
96 }
97
98 wxColour wxAnimation::GetTransparentColour(unsigned int frame) const
99 {
100 wxCHECK_MSG( IsOk(), wxNullColour, wxT("invalid animation") );
101
102 return M_ANIMDATA->GetTransparentColour(frame);
103 }
104
105 wxColour wxAnimation::GetBackgroundColour() const
106 {
107 wxCHECK_MSG( IsOk(), wxNullColour, wxT("invalid animation") );
108
109 return M_ANIMDATA->GetBackgroundColour();
110 }
111
112 bool wxAnimation::LoadFile(const wxString& filename, wxAnimationType type)
113 {
114 wxFileInputStream stream(filename);
115 if ( !stream.IsOk() )
116 return false;
117
118 return Load(stream, type);
119 }
120
121 bool wxAnimation::Load(wxInputStream &stream, wxAnimationType type)
122 {
123 UnRef();
124
125 const wxAnimationDecoder *handler;
126 if ( type == wxANIMATION_TYPE_ANY )
127 {
128 for ( wxAnimationDecoderList::compatibility_iterator node = sm_handlers.GetFirst();
129 node; node = node->GetNext() )
130 {
131 handler=(const wxAnimationDecoder*)node->GetData();
132
133 if ( handler->CanRead(stream) )
134 {
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);
139 }
140 }
141
142 wxLogWarning( _("No handler found for animation type.") );
143 return false;
144 }
145
146 handler = FindHandler(type);
147
148 if (handler == NULL)
149 {
150 wxLogWarning( _("No animation handler for type %ld defined."), type );
151
152 return false;
153 }
154
155
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();
159
160 if (stream.IsSeekable() && !M_ANIMDATA->CanRead(stream))
161 {
162 wxLogError(_("Animation file is not of type %ld."), type);
163 return false;
164 }
165 else
166 return M_ANIMDATA->Load(stream);
167 }
168
169
170 // ----------------------------------------------------------------------------
171 // animation decoders
172 // ----------------------------------------------------------------------------
173
174 void wxAnimation::AddHandler( wxAnimationDecoder *handler )
175 {
176 // Check for an existing handler of the type being added.
177 if (FindHandler( handler->GetType() ) == 0)
178 {
179 sm_handlers.Append( handler );
180 }
181 else
182 {
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.
187
188 wxLogDebug( wxT("Adding duplicate animation handler for '%d' type"),
189 handler->GetType() );
190 delete handler;
191 }
192 }
193
194 void wxAnimation::InsertHandler( wxAnimationDecoder *handler )
195 {
196 // Check for an existing handler of the type being added.
197 if (FindHandler( handler->GetType() ) == 0)
198 {
199 sm_handlers.Insert( handler );
200 }
201 else
202 {
203 // see AddHandler for additional comments.
204 wxLogDebug( wxT("Inserting duplicate animation handler for '%d' type"),
205 handler->GetType() );
206 delete handler;
207 }
208 }
209
210 const wxAnimationDecoder *wxAnimation::FindHandler( wxAnimationType animType )
211 {
212 wxAnimationDecoderList::compatibility_iterator node = sm_handlers.GetFirst();
213 while (node)
214 {
215 const wxAnimationDecoder *handler = (const wxAnimationDecoder *)node->GetData();
216 if (handler->GetType() == animType) return handler;
217 node = node->GetNext();
218 }
219 return 0;
220 }
221
222 void wxAnimation::InitStandardHandlers()
223 {
224 #if wxUSE_GIF
225 AddHandler(new wxGIFDecoder);
226 #endif // wxUSE_GIF
227 #if wxUSE_ICO_CUR
228 AddHandler(new wxANIDecoder);
229 #endif // wxUSE_ICO_CUR
230 }
231
232 void wxAnimation::CleanUpHandlers()
233 {
234 wxAnimationDecoderList::compatibility_iterator node = sm_handlers.GetFirst();
235 while (node)
236 {
237 wxAnimationDecoder *handler = (wxAnimationDecoder *)node->GetData();
238 wxAnimationDecoderList::compatibility_iterator next = node->GetNext();
239 delete handler;
240 node = next;
241 }
242
243 sm_handlers.Clear();
244 }
245
246
247 // A module to allow wxAnimation initialization/cleanup
248 // without calling these functions from app.cpp or from
249 // the user's application.
250
251 class wxAnimationModule: public wxModule
252 {
253 DECLARE_DYNAMIC_CLASS(wxAnimationModule)
254 public:
255 wxAnimationModule() {}
256 bool OnInit() { wxAnimation::InitStandardHandlers(); return true; }
257 void OnExit() { wxAnimation::CleanUpHandlers(); }
258 };
259
260 IMPLEMENT_DYNAMIC_CLASS(wxAnimationModule, wxModule)
261
262
263 // ----------------------------------------------------------------------------
264 // wxAnimationCtrl
265 // ----------------------------------------------------------------------------
266
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)
272 END_EVENT_TABLE()
273
274 void wxAnimationCtrl::Init()
275 {
276 m_currentFrame = 0;
277 m_looped = false;
278 m_isPlaying = false;
279
280 // use the window background colour by default to be consistent
281 // with the GTK+ native version
282 m_useWinBackgroundColour = true;
283 }
284
285 bool wxAnimationCtrl::Create(wxWindow *parent, wxWindowID id,
286 const wxAnimation& animation, const wxPoint& pos,
287 const wxSize& size, long style, const wxString& name)
288 {
289 m_timer.SetOwner(this);
290
291 if (!base_type::Create(parent, id, pos, size, style, wxDefaultValidator, name))
292 return false;
293
294 // by default we get the same background colour of our parent
295 SetBackgroundColour(parent->GetBackgroundColour());
296
297 SetAnimation(animation);
298
299 return true;
300 }
301
302 wxAnimationCtrl::~wxAnimationCtrl()
303 {
304 Stop();
305 }
306
307 bool wxAnimationCtrl::LoadFile(const wxString& filename, wxAnimationType type)
308 {
309 wxFileInputStream fis(filename);
310 if (!fis.IsOk())
311 return false;
312 return Load(fis, type);
313 }
314
315 bool wxAnimationCtrl::Load(wxInputStream& stream, wxAnimationType type)
316 {
317 wxAnimation anim;
318 if ( !anim.Load(stream, type) || !anim.IsOk() )
319 return false;
320
321 SetAnimation(anim);
322 return true;
323 }
324
325 wxSize wxAnimationCtrl::DoGetBestSize() const
326 {
327 if (m_animation.IsOk() && !this->HasFlag(wxAC_NO_AUTORESIZE))
328 return m_animation.GetSize();
329
330 return wxSize(100, 100);
331 }
332
333 void wxAnimationCtrl::SetAnimation(const wxAnimation& animation)
334 {
335 if (IsPlaying())
336 Stop();
337
338 // set new animation even if it's wxNullAnimation
339 m_animation = animation;
340 if (!m_animation.IsOk())
341 {
342 DisplayStaticImage();
343 return;
344 }
345
346 if (m_animation.GetBackgroundColour() == wxNullColour)
347 SetUseWindowBackgroundColour();
348 if (!this->HasFlag(wxAC_NO_AUTORESIZE))
349 FitToAnimation();
350
351 DisplayStaticImage();
352 }
353
354 void wxAnimationCtrl::SetInactiveBitmap(const wxBitmap &bmp)
355 {
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());
363
364 wxAnimationCtrlBase::SetInactiveBitmap(bmp);
365 }
366
367 void wxAnimationCtrl::FitToAnimation()
368 {
369 SetSize(m_animation.GetSize());
370 }
371
372 bool wxAnimationCtrl::SetBackgroundColour(const wxColour& colour)
373 {
374 if ( !wxWindow::SetBackgroundColour(colour) )
375 return false;
376
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)
379 if ( !IsPlaying() )
380 DisplayStaticImage();
381
382 return true;
383 }
384
385
386 // ----------------------------------------------------------------------------
387 // wxAnimationCtrl - stop/play methods
388 // ----------------------------------------------------------------------------
389
390 void wxAnimationCtrl::Stop()
391 {
392 m_timer.Stop();
393 m_isPlaying = false;
394
395 // reset frame counter
396 m_currentFrame = 0;
397
398 DisplayStaticImage();
399 }
400
401 bool wxAnimationCtrl::Play(bool looped)
402 {
403 if (!m_animation.IsOk())
404 return false;
405
406 m_looped = looped;
407 m_currentFrame = 0;
408
409 if (!RebuildBackingStoreUpToFrame(0))
410 return false;
411
412 m_isPlaying = true;
413
414 // do a ClearBackground() to avoid that e.g. the custom static bitmap which
415 // was eventually shown previously remains partially drawn
416 ClearBackground();
417
418 // DrawCurrentFrame() will use our updated backing store
419 wxClientDC clientDC(this);
420 DrawCurrentFrame(clientDC);
421
422 // start the timer
423 int delay = m_animation.GetDelay(0);
424 if (delay == 0)
425 delay = 1; // 0 is invalid timeout for wxTimer.
426 m_timer.Start(delay, true);
427
428 return true;
429 }
430
431
432
433 // ----------------------------------------------------------------------------
434 // wxAnimationCtrl - rendering methods
435 // ----------------------------------------------------------------------------
436
437 bool wxAnimationCtrl::RebuildBackingStoreUpToFrame(unsigned int frame)
438 {
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());
445
446 if ( !m_backingStore.IsOk() ||
447 m_backingStore.GetWidth() < w || m_backingStore.GetHeight() < h )
448 {
449 if (!m_backingStore.Create(w, h))
450 return false;
451 }
452
453 wxMemoryDC dc;
454 dc.SelectObject(m_backingStore);
455
456 // Draw the background
457 DisposeToBackground(dc);
458
459 // Draw all intermediate frames that haven't been removed from the animation
460 for (unsigned int i = 0; i < frame; i++)
461 {
462 if (m_animation.GetDisposalMethod(i) == wxANIM_DONOTREMOVE ||
463 m_animation.GetDisposalMethod(i) == wxANIM_UNSPECIFIED)
464 {
465 DrawFrame(dc, i);
466 }
467 else if (m_animation.GetDisposalMethod(i) == wxANIM_TOBACKGROUND)
468 DisposeToBackground(dc, m_animation.GetFramePosition(i),
469 m_animation.GetFrameSize(i));
470 }
471
472 // finally draw this frame
473 DrawFrame(dc, frame);
474 dc.SelectObject(wxNullBitmap);
475
476 return true;
477 }
478
479 void wxAnimationCtrl::IncrementalUpdateBackingStore()
480 {
481 wxMemoryDC dc;
482 dc.SelectObject(m_backingStore);
483
484 // OPTIMIZATION:
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.
489
490 if (m_currentFrame == 0)
491 {
492 // before drawing the first frame always dispose to bg colour
493 DisposeToBackground(dc);
494 }
495 else
496 {
497 switch (m_animation.GetDisposalMethod(m_currentFrame-1))
498 {
499 case wxANIM_TOBACKGROUND:
500 DisposeToBackground(dc, m_animation.GetFramePosition(m_currentFrame-1),
501 m_animation.GetFrameSize(m_currentFrame-1));
502 break;
503
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)
510 {
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);
514 }
515 else
516 if (!RebuildBackingStoreUpToFrame(m_currentFrame-2))
517 Stop();
518 break;
519
520 case wxANIM_DONOTREMOVE:
521 case wxANIM_UNSPECIFIED:
522 break;
523 }
524 }
525
526 // now just draw the current frame on the top of the backing store
527 DrawFrame(dc, m_currentFrame);
528 dc.SelectObject(wxNullBitmap);
529 }
530
531 void wxAnimationCtrl::DisplayStaticImage()
532 {
533 wxASSERT(!IsPlaying());
534
535 // m_bmpStaticReal will be updated only if necessary...
536 UpdateStaticImage();
537
538 if (m_bmpStaticReal.IsOk())
539 {
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() )
543 {
544 wxMemoryDC temp;
545 temp.SelectObject(m_backingStore);
546 DisposeToBackground(temp);
547 temp.DrawBitmap(m_bmpStaticReal, 0, 0, true /* use mask */);
548 }
549 else
550 m_backingStore = m_bmpStaticReal;
551 }
552 else
553 {
554 // put in the backing store the first frame of the animation
555 if (!m_animation.IsOk() ||
556 !RebuildBackingStoreUpToFrame(0))
557 {
558 m_animation = wxNullAnimation;
559 DisposeToBackground();
560 }
561 }
562
563 Refresh();
564 }
565
566 void wxAnimationCtrl::DrawFrame(wxDC &dc, unsigned int frame)
567 {
568 // PERFORMANCE NOTE:
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 */);
577 }
578
579 void wxAnimationCtrl::DrawCurrentFrame(wxDC& dc)
580 {
581 wxASSERT( m_backingStore.IsOk() );
582
583 // m_backingStore always contains the current frame
584 dc.DrawBitmap(m_backingStore, 0, 0, true /* use mask in case it's present */);
585 }
586
587 void wxAnimationCtrl::DisposeToBackground()
588 {
589 // clear the backing store
590 wxMemoryDC dc;
591 dc.SelectObject(m_backingStore);
592 if ( dc.IsOk() )
593 DisposeToBackground(dc);
594 }
595
596 void wxAnimationCtrl::DisposeToBackground(wxDC& dc)
597 {
598 wxColour col = IsUsingWindowBackgroundColour()
599 ? GetBackgroundColour()
600 : m_animation.GetBackgroundColour();
601
602 wxBrush brush(col);
603 dc.SetBackground(brush);
604 dc.Clear();
605 }
606
607 void wxAnimationCtrl::DisposeToBackground(wxDC& dc, const wxPoint &pos, const wxSize &sz)
608 {
609 wxColour col = IsUsingWindowBackgroundColour()
610 ? GetBackgroundColour()
611 : m_animation.GetBackgroundColour();
612 wxBrush brush(col);
613 dc.SetBrush(brush); // SetBrush and not SetBackground !!
614 dc.SetPen(*wxTRANSPARENT_PEN);
615 dc.DrawRectangle(pos, sz);
616 }
617
618 // ----------------------------------------------------------------------------
619 // wxAnimationCtrl - event handlers
620 // ----------------------------------------------------------------------------
621
622 void wxAnimationCtrl::OnPaint(wxPaintEvent& WXUNUSED(event))
623 {
624 // VERY IMPORTANT: the wxPaintDC *must* be created in any case
625 wxPaintDC dc(this);
626
627 if ( m_backingStore.IsOk() )
628 {
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 */);
633 }
634 else
635 {
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);
639 }
640 }
641
642 void wxAnimationCtrl::OnTimer(wxTimerEvent &WXUNUSED(event))
643 {
644 m_currentFrame++;
645 if (m_currentFrame == m_animation.GetFrameCount())
646 {
647 // Should a non-looped animation display the last frame?
648 if (!m_looped)
649 {
650 Stop();
651 return;
652 }
653 else
654 m_currentFrame = 0; // let's restart
655 }
656
657 IncrementalUpdateBackingStore();
658
659 wxClientDC dc(this);
660 DrawCurrentFrame(dc);
661
662 #ifdef __WXMAC__
663 // without this, the animation currently doesn't redraw under Mac
664 Refresh();
665 #endif // __WXMAC__
666
667 // Set the timer for the next frame
668 int delay = m_animation.GetDelay(m_currentFrame);
669 if (delay == 0)
670 delay = 1; // 0 is invalid timeout for wxTimer.
671 m_timer.Start(delay, true);
672 }
673
674 void wxAnimationCtrl::OnSize(wxSizeEvent &WXUNUSED(event))
675 {
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())
682 {
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
687 if (IsPlaying())
688 {
689 if (!RebuildBackingStoreUpToFrame(m_currentFrame))
690 Stop(); // in case we are playing
691 }
692 }
693 }
694
695 #endif // wxUSE_ANIMATIONCTRL
696