- fix wxAnimationCtrl::SetBackgroundColour both for generic and native GTK
[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 && (!defined(__WXGTK20__) || defined(__WXUNIVERSAL__))
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
189 wxLogDebug( _T("Adding duplicate animation handler for '%d' type"),
190 handler->GetType() );
191 delete handler;
192 }
193 }
194
195 void wxAnimation::InsertHandler( wxAnimationDecoder *handler )
196 {
197 // Check for an existing handler of the type being added.
198 if (FindHandler( handler->GetType() ) == 0)
199 {
200 sm_handlers.Insert( handler );
201 }
202 else
203 {
204 // see AddHandler for additional comments.
205 wxLogDebug( _T("Inserting duplicate animation handler for '%d' type"),
206 handler->GetType() );
207 delete handler;
208 }
209 }
210
211 const wxAnimationDecoder *wxAnimation::FindHandler( wxAnimationType animType )
212 {
213 wxAnimationDecoderList::compatibility_iterator node = sm_handlers.GetFirst();
214 while (node)
215 {
216 const wxAnimationDecoder *handler = (const wxAnimationDecoder *)node->GetData();
217 if (handler->GetType() == animType) return handler;
218 node = node->GetNext();
219 }
220 return 0;
221 }
222
223 void wxAnimation::InitStandardHandlers()
224 {
225 #if wxUSE_GIF
226 AddHandler(new wxGIFDecoder);
227 #endif // wxUSE_GIF
228 #if wxUSE_ICO_CUR
229 AddHandler(new wxANIDecoder);
230 #endif // wxUSE_ICO_CUR
231 }
232
233 void wxAnimation::CleanUpHandlers()
234 {
235 wxAnimationDecoderList::compatibility_iterator node = sm_handlers.GetFirst();
236 while (node)
237 {
238 wxAnimationDecoder *handler = (wxAnimationDecoder *)node->GetData();
239 wxAnimationDecoderList::compatibility_iterator next = node->GetNext();
240 delete handler;
241 node = next;
242 }
243
244 sm_handlers.Clear();
245 }
246
247
248 // A module to allow wxAnimation initialization/cleanup
249 // without calling these functions from app.cpp or from
250 // the user's application.
251
252 class wxAnimationModule: public wxModule
253 {
254 DECLARE_DYNAMIC_CLASS(wxAnimationModule)
255 public:
256 wxAnimationModule() {}
257 bool OnInit() { wxAnimation::InitStandardHandlers(); return true; };
258 void OnExit() { wxAnimation::CleanUpHandlers(); };
259 };
260
261 IMPLEMENT_DYNAMIC_CLASS(wxAnimationModule, wxModule)
262
263
264 // ----------------------------------------------------------------------------
265 // wxAnimationCtrl
266 // ----------------------------------------------------------------------------
267
268 IMPLEMENT_CLASS(wxAnimationCtrl, wxAnimationCtrlBase)
269 BEGIN_EVENT_TABLE(wxAnimationCtrl, wxAnimationCtrlBase)
270 EVT_PAINT(wxAnimationCtrl::OnPaint)
271 EVT_SIZE(wxAnimationCtrl::OnSize)
272 EVT_TIMER(wxID_ANY, wxAnimationCtrl::OnTimer)
273 END_EVENT_TABLE()
274
275 void wxAnimationCtrl::Init()
276 {
277 m_currentFrame = 0;
278 m_looped = false;
279 m_isPlaying = false;
280
281 // use the window background colour by default to be consistent
282 // with the GTK+ native version
283 m_useWinBackgroundColour = true;
284 }
285
286 bool wxAnimationCtrl::Create(wxWindow *parent, wxWindowID id,
287 const wxAnimation& animation, const wxPoint& pos,
288 const wxSize& size, long style, const wxString& name)
289 {
290 m_timer.SetOwner(this);
291
292 if (!base_type::Create(parent, id, pos, size, style, wxDefaultValidator, name))
293 return false;
294
295 // by default we get the same background colour of our parent
296 SetBackgroundColour(parent->GetBackgroundColour());
297
298 SetAnimation(animation);
299
300 return true;
301 }
302
303 wxAnimationCtrl::~wxAnimationCtrl()
304 {
305 Stop();
306 }
307
308 bool wxAnimationCtrl::LoadFile(const wxString& filename, wxAnimationType type)
309 {
310 wxAnimation anim;
311 if (!anim.LoadFile(filename, type) ||
312 !anim.IsOk())
313 return false;
314
315 SetAnimation(anim);
316 return true;
317 }
318
319 wxSize wxAnimationCtrl::DoGetBestSize() const
320 {
321 if (m_animation.IsOk() && !this->HasFlag(wxAC_NO_AUTORESIZE))
322 return m_animation.GetSize();
323
324 return wxSize(100, 100);
325 }
326
327 void wxAnimationCtrl::SetAnimation(const wxAnimation& animation)
328 {
329 if (IsPlaying())
330 Stop();
331
332 // set new animation even if it's wxNullAnimation
333 m_animation = animation;
334 if (!m_animation.IsOk())
335 {
336 UpdateBackingStoreWithStaticImage();
337 return;
338 }
339
340 if (m_animation.GetBackgroundColour() == wxNullColour)
341 SetUseWindowBackgroundColour();
342 if (!this->HasFlag(wxAC_NO_AUTORESIZE))
343 FitToAnimation();
344
345 UpdateBackingStoreWithStaticImage();
346 }
347
348 void wxAnimationCtrl::SetInactiveBitmap(const wxBitmap &bmp)
349 {
350 m_bmpStatic = bmp;
351
352 // if the bitmap has an associated mask, we need to set our background to
353 // the colour of our parent otherwise when calling DrawCurrentFrame()
354 // (which uses the bitmap's mask), our background colour would be used for
355 // transparent areas - and that's not what we want (at least for
356 // consistency with the GTK version)
357 if ( bmp.GetMask() != NULL && GetParent() != NULL )
358 SetBackgroundColour(GetParent()->GetBackgroundColour());
359
360 // if not playing, update the backing store now
361 if ( !IsPlaying() )
362 UpdateBackingStoreWithStaticImage();
363 }
364
365 void wxAnimationCtrl::FitToAnimation()
366 {
367 SetSize(m_animation.GetSize());
368 }
369
370 bool wxAnimationCtrl::SetBackgroundColour(const wxColour& colour)
371 {
372 if ( !wxWindow::SetBackgroundColour(colour) )
373 return false;
374
375 // if not playing, then this change must be seen immediately (unless
376 // there's an inactive bitmap set which has higher priority than bg colour)
377 if ( !IsPlaying() )
378 UpdateBackingStoreWithStaticImage();
379
380 return true;
381 }
382
383
384 // ----------------------------------------------------------------------------
385 // wxAnimationCtrl - stop/play methods
386 // ----------------------------------------------------------------------------
387
388 void wxAnimationCtrl::Stop()
389 {
390 m_timer.Stop();
391 m_isPlaying = false;
392
393 // reset frame counter
394 m_currentFrame = 0;
395
396 UpdateBackingStoreWithStaticImage();
397 }
398
399 bool wxAnimationCtrl::Play(bool looped)
400 {
401 if (!m_animation.IsOk())
402 return false;
403
404 m_looped = looped;
405 m_currentFrame = 0;
406
407 if (!RebuildBackingStoreUpToFrame(0))
408 return false;
409
410 m_isPlaying = true;
411
412 // do a ClearBackground() to avoid that e.g. the custom static bitmap which
413 // was eventually shown previously remains partially drawn
414 ClearBackground();
415
416 // DrawCurrentFrame() will use our updated backing store
417 wxClientDC clientDC(this);
418 DrawCurrentFrame(clientDC);
419
420 // start the timer
421 int delay = m_animation.GetDelay(0);
422 if (delay == 0)
423 delay = 1; // 0 is invalid timeout for wxTimer.
424 m_timer.Start(delay);
425
426 return true;
427 }
428
429
430
431 // ----------------------------------------------------------------------------
432 // wxAnimationCtrl - rendering methods
433 // ----------------------------------------------------------------------------
434
435 bool wxAnimationCtrl::RebuildBackingStoreUpToFrame(size_t frame)
436 {
437 // if we've not created the backing store yet or it's too
438 // small, then recreate it
439 wxSize sz = m_animation.GetSize(),
440 winsz = GetClientSize();
441 int w = wxMin(sz.GetWidth(), winsz.GetWidth());
442 int h = wxMin(sz.GetHeight(), winsz.GetHeight());
443
444 if ( !m_backingStore.IsOk() ||
445 m_backingStore.GetWidth() < w || m_backingStore.GetHeight() < h )
446 {
447 if (!m_backingStore.Create(w, h))
448 return false;
449 }
450
451 wxMemoryDC dc;
452 dc.SelectObject(m_backingStore);
453
454 // Draw the background
455 DisposeToBackground(dc);
456
457 // Draw all intermediate frames that haven't been removed from the animation
458 for (size_t i = 0; i < frame; i++)
459 {
460 if (m_animation.GetDisposalMethod(i) == wxANIM_DONOTREMOVE ||
461 m_animation.GetDisposalMethod(i) == wxANIM_UNSPECIFIED)
462 {
463 DrawFrame(dc, i);
464 }
465 else if (m_animation.GetDisposalMethod(i) == wxANIM_TOBACKGROUND)
466 DisposeToBackground(dc, m_animation.GetFramePosition(i),
467 m_animation.GetFrameSize(i));
468 }
469
470 // finally draw this frame
471 DrawFrame(dc, frame);
472 dc.SelectObject(wxNullBitmap);
473
474 return true;
475 }
476
477 void wxAnimationCtrl::IncrementalUpdateBackingStore()
478 {
479 wxMemoryDC dc;
480 dc.SelectObject(m_backingStore);
481
482 // OPTIMIZATION:
483 // since wxAnimationCtrl can only play animations forward, without skipping
484 // frames, we can be sure that m_backingStore contains the m_currentFrame-1
485 // frame and thus we just need to dispose the m_currentFrame-1 frame and
486 // render the m_currentFrame-th one.
487
488 if (m_currentFrame == 0)
489 {
490 // before drawing the first frame always dispose to bg colour
491 DisposeToBackground(dc);
492 }
493 else
494 {
495 switch (m_animation.GetDisposalMethod(m_currentFrame-1))
496 {
497 case wxANIM_TOBACKGROUND:
498 DisposeToBackground(dc, m_animation.GetFramePosition(m_currentFrame-1),
499 m_animation.GetFrameSize(m_currentFrame-1));
500 break;
501
502 case wxANIM_TOPREVIOUS:
503 // this disposal should never be used too often.
504 // E.g. GIF specification explicitely say to keep the usage of this
505 // disposal limited to the minimum.
506 // In fact it may require a lot of time to restore
507 if (m_currentFrame == 1)
508 {
509 // if 0-th frame disposal is to restore to previous frame,
510 // the best we can do is to restore to background
511 DisposeToBackground(dc);
512 }
513 else
514 if (!RebuildBackingStoreUpToFrame(m_currentFrame-2))
515 Stop();
516 break;
517
518 case wxANIM_DONOTREMOVE:
519 case wxANIM_UNSPECIFIED:
520 break;
521 }
522 }
523
524 // now just draw the current frame on the top of the backing store
525 DrawFrame(dc, m_currentFrame);
526 dc.SelectObject(wxNullBitmap);
527 }
528
529 void wxAnimationCtrl::UpdateBackingStoreWithStaticImage()
530 {
531 wxASSERT(!IsPlaying());
532
533 if (m_bmpStatic.IsOk())
534 {
535 // copy the inactive bitmap in the backing store
536 // eventually using the mask if the static bitmap has one
537 if ( m_bmpStatic.GetMask() )
538 {
539 wxMemoryDC temp;
540 temp.SelectObject(m_backingStore);
541 DisposeToBackground(temp);
542 temp.DrawBitmap(m_bmpStatic, 0, 0, true /* use mask */);
543 }
544 else
545 m_backingStore = m_bmpStatic;
546 }
547 else
548 {
549 // put in the backing store the first frame of the animation
550 if (!m_animation.IsOk() ||
551 !RebuildBackingStoreUpToFrame(0))
552 {
553 m_animation = wxNullAnimation;
554 DisposeToBackground();
555 }
556 }
557
558 Refresh();
559 }
560
561 void wxAnimationCtrl::DrawFrame(wxDC &dc, size_t frame)
562 {
563 // PERFORMANCE NOTE:
564 // this draw stuff is not as fast as possible: the wxAnimationDecoder
565 // needs first to convert from its internal format to wxImage RGB24;
566 // the wxImage is then converted as a wxBitmap and finally blitted.
567 // If wxAnimationDecoder had a function to convert directly from its
568 // internal format to a port-specific wxBitmap, it would be somewhat faster.
569 wxBitmap bmp(m_animation.GetFrame(frame));
570 dc.DrawBitmap(bmp, m_animation.GetFramePosition(frame),
571 true /* use mask */);
572 }
573
574 void wxAnimationCtrl::DrawCurrentFrame(wxDC& dc)
575 {
576 wxASSERT( m_backingStore.IsOk() );
577
578 // m_backingStore always contains the current frame
579 dc.DrawBitmap(m_backingStore, 0, 0, true /* use mask in case it's present */);
580 }
581
582 void wxAnimationCtrl::DisposeToBackground()
583 {
584 // clear the backing store
585 wxMemoryDC dc;
586 dc.SelectObject(m_backingStore);
587 DisposeToBackground(dc);
588 }
589
590 void wxAnimationCtrl::DisposeToBackground(wxDC& dc)
591 {
592 wxColour col = IsUsingWindowBackgroundColour()
593 ? GetBackgroundColour()
594 : m_animation.GetBackgroundColour();
595
596 wxBrush brush(col);
597 dc.SetBackground(brush);
598 dc.Clear();
599 }
600
601 void wxAnimationCtrl::DisposeToBackground(wxDC& dc, const wxPoint &pos, const wxSize &sz)
602 {
603 wxColour col = IsUsingWindowBackgroundColour()
604 ? GetBackgroundColour()
605 : m_animation.GetBackgroundColour();
606 wxBrush brush(col);
607 dc.SetBrush(brush); // SetBrush and not SetBackground !!
608 dc.SetPen(*wxTRANSPARENT_PEN);
609 dc.DrawRectangle(pos, sz);
610 }
611
612 // ----------------------------------------------------------------------------
613 // wxAnimationCtrl - event handlers
614 // ----------------------------------------------------------------------------
615
616 void wxAnimationCtrl::OnPaint(wxPaintEvent& WXUNUSED(event))
617 {
618 // VERY IMPORTANT: the wxPaintDC *must* be created in any case
619 wxPaintDC dc(this);
620
621 if ( m_backingStore.IsOk() )
622 {
623 // NOTE: we draw the bitmap explicitely ignoring the mask (if any);
624 // i.e. we don't want to combine the backing store with the
625 // possibly wrong preexisting contents of the window!
626 dc.DrawBitmap(m_backingStore, 0, 0, false /* no mask */);
627 }
628 else
629 {
630 // m_animation is not valid and thus we don't have a valid backing store...
631 // clear then our area to the background colour
632 DisposeToBackground(dc);
633 }
634 }
635
636 void wxAnimationCtrl::OnTimer(wxTimerEvent &WXUNUSED(event))
637 {
638 m_currentFrame++;
639 if (m_currentFrame == m_animation.GetFrameCount())
640 {
641 // Should a non-looped animation display the last frame?
642 if (!m_looped)
643 {
644 Stop();
645 return;
646 }
647 else
648 m_currentFrame = 0; // let's restart
649 }
650
651 IncrementalUpdateBackingStore();
652
653 wxClientDC dc(this);
654 DrawCurrentFrame(dc);
655
656 #ifdef __WXMAC__
657 // without this, the animation currently doesn't redraw under Mac
658 Refresh();
659 #endif // __WXMAC__
660
661 // Set the timer for the next frame
662 int delay = m_animation.GetDelay(m_currentFrame);
663 if (delay == 0)
664 delay = 1; // 0 is invalid timeout for wxTimer.
665 m_timer.Start(delay);
666 }
667
668 void wxAnimationCtrl::OnSize(wxSizeEvent &WXUNUSED(event))
669 {
670 // NB: resizing an animation control may take a lot of time
671 // for big animations as the backing store must be
672 // extended and rebuilt. Try to avoid it e.g. using
673 // a null proportion value for your wxAnimationCtrls
674 // when using them inside sizers.
675 if (m_animation.IsOk())
676 {
677 // be careful to change the backing store *only* if we are
678 // playing the animation as otherwise we may be displaying
679 // the inactive bitmap and overwriting the backing store
680 // with the last played frame is wrong in this case
681 if (IsPlaying())
682 {
683 if (!RebuildBackingStoreUpToFrame(m_currentFrame))
684 Stop(); // in case we are playing
685 }
686 }
687 }
688
689 #endif // wxUSE_ANIMATIONCTRL
690