]> git.saurik.com Git - wxWidgets.git/blame - src/generic/animateg.cpp
add -mno-cygwin detection: we should treat cygwin as Windows, not Unix, when it's...
[wxWidgets.git] / src / generic / animateg.cpp
CommitLineData
72045d57
VZ
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
5f8047a6 18#if wxUSE_ANIMATIONCTRL && (!defined(__WXGTK20__) || defined(__WXUNIVERSAL__))
72045d57 19
c2f12218
PC
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
72045d57 30#include "wx/wfstream.h"
72045d57
VZ
31#include "wx/gifdecod.h"
32#include "wx/anidecod.h"
72045d57 33
c2f12218 34#include "wx/listimpl.cpp"
14c0d834 35WX_DEFINE_LIST(wxAnimationDecoderList)
72045d57
VZ
36
37wxAnimationDecoderList wxAnimation::sm_handlers;
38
39
72045d57
VZ
40// ----------------------------------------------------------------------------
41// wxAnimation
42// ----------------------------------------------------------------------------
43
44IMPLEMENT_DYNAMIC_CLASS(wxAnimation, wxAnimationBase)
45#define M_ANIMDATA wx_static_cast(wxAnimationDecoder*, m_refData)
46
47wxSize wxAnimation::GetSize() const
48{
49 wxCHECK_MSG( IsOk(), wxDefaultSize, wxT("invalid animation") );
50
51 return M_ANIMDATA->GetAnimationSize();
52}
53
54size_t wxAnimation::GetFrameCount() const
55{
56 wxCHECK_MSG( IsOk(), 0, wxT("invalid animation") );
57
58 return M_ANIMDATA->GetFrameCount();
59}
60
61wxImage 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
71int wxAnimation::GetDelay(size_t i) const
72{
73 wxCHECK_MSG( IsOk(), 0, wxT("invalid animation") );
74
75 return M_ANIMDATA->GetDelay(i);
76}
77
78wxPoint wxAnimation::GetFramePosition(size_t frame) const
79{
80 wxCHECK_MSG( IsOk(), wxDefaultPosition, wxT("invalid animation") );
81
82 return M_ANIMDATA->GetFramePosition(frame);
83}
84
05a98b6d
RR
85wxSize wxAnimation::GetFrameSize(size_t frame) const
86{
87 wxCHECK_MSG( IsOk(), wxDefaultSize, wxT("invalid animation") );
88
89 return M_ANIMDATA->GetFrameSize(frame);
90}
91
72045d57
VZ
92wxAnimationDisposal 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
05a98b6d
RR
99wxColour wxAnimation::GetTransparentColour(size_t frame) const
100{
101 wxCHECK_MSG( IsOk(), wxNullColour, wxT("invalid animation") );
102
103 return M_ANIMDATA->GetTransparentColour(frame);
104}
105
72045d57
VZ
106wxColour wxAnimation::GetBackgroundColour() const
107{
108 wxCHECK_MSG( IsOk(), wxNullColour, wxT("invalid animation") );
109
110 return M_ANIMDATA->GetBackgroundColour();
111}
112
113bool wxAnimation::LoadFile(const wxString& filename, wxAnimationType type)
114{
115 wxFileInputStream stream(filename);
9d9e3858 116 if ( !stream.IsOk() )
72045d57
VZ
117 return false;
118
119 return Load(stream, type);
120}
121
122bool wxAnimation::Load(wxInputStream &stream, wxAnimationType type)
123{
124 UnRef();
125
126 const wxAnimationDecoder *handler;
127 if ( type == wxANIMATION_TYPE_ANY )
128 {
9d9e3858 129 for ( wxAnimationDecoderList::compatibility_iterator node = sm_handlers.GetFirst();
72045d57
VZ
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
175void 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
196void 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
212const 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
224void wxAnimation::InitStandardHandlers()
225{
d18868bb 226#if wxUSE_GIF
72045d57 227 AddHandler(new wxGIFDecoder);
d18868bb
WS
228#endif // wxUSE_GIF
229#if wxUSE_ICO_CUR
72045d57 230 AddHandler(new wxANIDecoder);
d18868bb 231#endif // wxUSE_ICO_CUR
72045d57
VZ
232}
233
234void 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
253class wxAnimationModule: public wxModule
254{
255DECLARE_DYNAMIC_CLASS(wxAnimationModule)
256public:
257 wxAnimationModule() {}
258 bool OnInit() { wxAnimation::InitStandardHandlers(); return true; };
259 void OnExit() { wxAnimation::CleanUpHandlers(); };
260};
261
262IMPLEMENT_DYNAMIC_CLASS(wxAnimationModule, wxModule)
263
264
72045d57
VZ
265// ----------------------------------------------------------------------------
266// wxAnimationCtrl
267// ----------------------------------------------------------------------------
268
269IMPLEMENT_CLASS(wxAnimationCtrl, wxAnimationCtrlBase)
270BEGIN_EVENT_TABLE(wxAnimationCtrl, wxAnimationCtrlBase)
271 EVT_PAINT(wxAnimationCtrl::OnPaint)
272 EVT_SIZE(wxAnimationCtrl::OnSize)
273 EVT_TIMER(wxID_ANY, wxAnimationCtrl::OnTimer)
274END_EVENT_TABLE()
275
05a98b6d 276void wxAnimationCtrl::Init()
c2f12218
PC
277{
278 m_currentFrame = 0;
279 m_looped = false;
280 m_isPlaying = false;
05a98b6d
RR
281
282 // use the window background colour by default to be consistent
283 // with the GTK+ native version
284 m_useWinBackgroundColour = true;
c2f12218
PC
285}
286
72045d57
VZ
287bool 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;
72045d57
VZ
292 m_timer.SetOwner(this);
293
c2f12218 294 if (!base_type::Create(parent, id, pos, size, style, wxDefaultValidator, name))
72045d57
VZ
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
302wxAnimationCtrl::~wxAnimationCtrl()
303{
304 Stop();
305}
306
307bool wxAnimationCtrl::LoadFile(const wxString& filename, wxAnimationType type)
308{
309 wxAnimation anim;
9d9e3858 310 if (!anim.LoadFile(filename, type) ||
72045d57
VZ
311 !anim.IsOk())
312 return false;
313
314 SetAnimation(anim);
315 return true;
316}
317
318wxSize 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
326void 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
8e458bb5 338 // reset frame counter
72045d57 339 m_currentFrame = 0;
72045d57 340
8e458bb5
RR
341 UpdateBackingStoreWithStaticImage();
342}
72045d57 343
8e458bb5
RR
344void wxAnimationCtrl::SetInactiveBitmap(const wxBitmap &bmp)
345{
580ca0a4 346 m_bmpStatic = bmp;
8e458bb5
RR
347
348 // if not playing, update the backing store now
349 if (!IsPlaying())
350 UpdateBackingStoreWithStaticImage();
72045d57
VZ
351}
352
353void wxAnimationCtrl::FitToAnimation()
354{
355 SetSize(m_animation.GetSize());
356}
357
358
359// ----------------------------------------------------------------------------
360// wxAnimationCtrl - stop/play methods
361// ----------------------------------------------------------------------------
362
363void wxAnimationCtrl::Stop()
364{
72045d57
VZ
365 m_timer.Stop();
366 m_isPlaying = false;
8e458bb5
RR
367
368 UpdateBackingStoreWithStaticImage();
72045d57
VZ
369}
370
371bool wxAnimationCtrl::Play(bool looped)
372{
373 if (!m_animation.IsOk())
374 return false;
375
376 int oldframe = m_currentFrame;
377 m_looped = looped;
378 m_currentFrame = 0;
72045d57
VZ
379
380 // small optimization: if the back store was already updated to the
381 // first frame, don't rebuild it
382 if (oldframe != 0)
05a98b6d
RR
383 if (!RebuildBackingStoreUpToFrame(0))
384 return false;
385
386 m_isPlaying = true;
72045d57 387
8e458bb5
RR
388 // do a ClearBackground() to avoid that e.g. the custom static bitmap which
389 // was eventually shown previously remains partially drawn
390 ClearBackground();
391
72045d57
VZ
392 // DrawCurrentFrame() will use our updated backing store
393 wxClientDC clientDC(this);
394 DrawCurrentFrame(clientDC);
395
396 // start the timer
397 int delay = m_animation.GetDelay(0);
398 if (delay == 0)
399 delay = 1; // 0 is invalid timeout for wxTimer.
400 m_timer.Start(delay);
401
402 return true;
403}
404
405
406
407// ----------------------------------------------------------------------------
408// wxAnimationCtrl - rendering methods
409// ----------------------------------------------------------------------------
410
05a98b6d 411bool wxAnimationCtrl::RebuildBackingStoreUpToFrame(size_t frame)
72045d57
VZ
412{
413 // if we've not created the backing store yet or it's too
414 // small, then recreate it
415 wxSize sz = m_animation.GetSize(),
416 winsz = GetClientSize();
417 int w = wxMin(sz.GetWidth(), winsz.GetWidth());
418 int h = wxMin(sz.GetHeight(), winsz.GetHeight());
419
9d9e3858 420 if ( !m_backingStore.IsOk() ||
6003de2f
VZ
421 m_backingStore.GetWidth() < w || m_backingStore.GetHeight() < h )
422 {
05a98b6d
RR
423 if (!m_backingStore.Create(w, h))
424 return false;
6003de2f 425 }
72045d57
VZ
426
427 wxMemoryDC dc;
428 dc.SelectObject(m_backingStore);
429
430 // Draw the background
431 DisposeToBackground(dc);
432
433 // Draw all intermediate frames that haven't been removed from the animation
434 for (size_t i = 0; i < frame; i++)
435 {
436 if (m_animation.GetDisposalMethod(i) == wxANIM_DONOTREMOVE ||
437 m_animation.GetDisposalMethod(i) == wxANIM_UNSPECIFIED)
438 {
439 DrawFrame(dc, i);
440 }
05a98b6d
RR
441 else if (m_animation.GetDisposalMethod(i) == wxANIM_TOBACKGROUND)
442 DisposeToBackground(dc, m_animation.GetFramePosition(i),
443 m_animation.GetFrameSize(i));
72045d57
VZ
444 }
445
446 // finally draw this frame
447 DrawFrame(dc, frame);
448 dc.SelectObject(wxNullBitmap);
05a98b6d
RR
449
450 return true;
72045d57
VZ
451}
452
453void wxAnimationCtrl::IncrementalUpdateBackingStore()
454{
455 wxMemoryDC dc;
456 dc.SelectObject(m_backingStore);
457
458 // OPTIMIZATION:
459 // since wxAnimationCtrl can only play animations forward, without skipping
460 // frames, we can be sure that m_backingStore contains the m_currentFrame-1
461 // frame and thus we just need to dispose the m_currentFrame-1 frame and
462 // render the m_currentFrame-th one.
463
464 if (m_currentFrame == 0)
465 {
466 // before drawing the first frame always dispose to bg colour
467 DisposeToBackground(dc);
468 }
469 else
470 {
471 switch (m_animation.GetDisposalMethod(m_currentFrame-1))
472 {
473 case wxANIM_TOBACKGROUND:
05a98b6d
RR
474 DisposeToBackground(dc, m_animation.GetFramePosition(m_currentFrame-1),
475 m_animation.GetFrameSize(m_currentFrame-1));
72045d57
VZ
476 break;
477
478 case wxANIM_TOPREVIOUS:
479 // this disposal should never be used too often.
480 // E.g. GIF specification explicitely say to keep the usage of this
9d9e3858
VZ
481 // disposal limited to the minimum.
482 // In fact it may require a lot of time to restore
72045d57
VZ
483 if (m_currentFrame == 1)
484 {
485 // if 0-th frame disposal is to restore to previous frame,
486 // the best we can do is to restore to background
487 DisposeToBackground(dc);
488 }
489 else
05a98b6d
RR
490 if (!RebuildBackingStoreUpToFrame(m_currentFrame-2))
491 Stop();
72045d57
VZ
492 break;
493
494 case wxANIM_DONOTREMOVE:
495 case wxANIM_UNSPECIFIED:
496 break;
497 }
498 }
499
500 // now just draw the current frame on the top of the backing store
501 DrawFrame(dc, m_currentFrame);
502 dc.SelectObject(wxNullBitmap);
503}
504
8e458bb5
RR
505void wxAnimationCtrl::UpdateBackingStoreWithStaticImage()
506{
507 wxASSERT(!IsPlaying());
508
509 if (m_bmpStatic.IsOk())
510 {
511 // copy the inactive bitmap in the backing store
512 m_backingStore = m_bmpStatic;
513 }
514 else
515 {
516 // put in the backing store the first frame of the animation
517 if (!m_animation.IsOk() ||
518 !RebuildBackingStoreUpToFrame(0))
519 {
520 m_animation = wxNullAnimation;
521 DisposeToBackground();
522 }
523 }
524
525 Refresh();
526}
527
72045d57
VZ
528void wxAnimationCtrl::DrawFrame(wxDC &dc, size_t frame)
529{
530 // PERFORMANCE NOTE:
531 // this draw stuff is not as fast as possible: the wxAnimationDecoder
532 // needs first to convert from its internal format to wxImage RGB24;
533 // the wxImage is then converted as a wxBitmap and finally blitted.
534 // If wxAnimationDecoder had a function to convert directly from its
535 // internal format to a port-specific wxBitmap, it would be somewhat faster.
536 wxBitmap bmp(m_animation.GetFrame(frame));
537 dc.DrawBitmap(bmp, m_animation.GetFramePosition(frame),
538 true /* use mask */);
539}
540
541void wxAnimationCtrl::DrawCurrentFrame(wxDC& dc)
542{
9d9e3858 543 wxASSERT( m_backingStore.IsOk() );
72045d57
VZ
544
545 // m_backingStore always contains the current frame
8e458bb5
RR
546 dc.DrawBitmap(m_backingStore, 0, 0, true /* use mask in case it's present */);
547}
548
549void wxAnimationCtrl::DisposeToBackground()
550{
551 // clear the backing store
552 wxMemoryDC dc;
553 dc.SelectObject(m_backingStore);
554 DisposeToBackground(dc);
72045d57
VZ
555}
556
557void wxAnimationCtrl::DisposeToBackground(wxDC& dc)
87d2b3f1
CE
558{
559 wxColour col = IsUsingWindowBackgroundColour()
9d9e3858 560 ? GetBackgroundColour()
05a98b6d 561 : m_animation.GetBackgroundColour();
87d2b3f1 562 wxBrush brush(col);
72045d57
VZ
563 dc.SetBackground(brush);
564 dc.Clear();
565}
566
05a98b6d
RR
567void wxAnimationCtrl::DisposeToBackground(wxDC& dc, const wxPoint &pos, const wxSize &sz)
568{
569 wxColour col = IsUsingWindowBackgroundColour()
570 ? GetBackgroundColour()
571 : m_animation.GetBackgroundColour();
572 wxBrush brush(col);
573 dc.SetBrush(brush); // SetBrush and not SetBackground !!
574 dc.SetPen(*wxTRANSPARENT_PEN);
575 dc.DrawRectangle(pos, sz);
576}
577
72045d57
VZ
578// ----------------------------------------------------------------------------
579// wxAnimationCtrl - event handlers
580// ----------------------------------------------------------------------------
581
582void wxAnimationCtrl::OnPaint(wxPaintEvent& WXUNUSED(event))
583{
584 // VERY IMPORTANT: the wxPaintDC *must* be created in any case
585 wxPaintDC dc(this);
586
9d9e3858 587 if ( m_backingStore.IsOk() )
72045d57 588 DrawCurrentFrame(dc);
8e458bb5
RR
589 else
590 {
591 // m_animation is not valid and thus we don't have a valid backing store...
592 // clear then our area to the background colour
593 DisposeToBackground(dc);
594 }
72045d57
VZ
595}
596
597void wxAnimationCtrl::OnTimer(wxTimerEvent &WXUNUSED(event))
598{
599 m_currentFrame++;
600 if (m_currentFrame == m_animation.GetFrameCount())
601 {
602 // Should a non-looped animation display the last frame?
603 if (!m_looped)
604 {
8e458bb5 605 Stop();
72045d57
VZ
606 return;
607 }
608 else
609 m_currentFrame = 0; // let's restart
610 }
611
612 IncrementalUpdateBackingStore();
613
614 wxClientDC dc(this);
615 DrawCurrentFrame(dc);
616
7444cb50
VZ
617#ifdef __WXMAC__
618 // without this, the animation currently doesn't redraw under Mac
619 Refresh();
620#endif // __WXMAC__
621
72045d57
VZ
622 // Set the timer for the next frame
623 int delay = m_animation.GetDelay(m_currentFrame);
624 if (delay == 0)
625 delay = 1; // 0 is invalid timeout for wxTimer.
626 m_timer.Start(delay);
627}
628
629void wxAnimationCtrl::OnSize(wxSizeEvent &WXUNUSED(event))
630{
631 // NB: resizing an animation control may take a lot of time
632 // for big animations as the backing store must be
580ca0a4
VZ
633 // extended and rebuilt. Try to avoid it e.g. using
634 // a null proportion value for your wxAnimationCtrls
635 // when using them inside sizers.
72045d57 636 if (m_animation.IsOk())
580ca0a4
VZ
637 {
638 // be careful to change the backing store *only* if we are
639 // playing the animation as otherwise we may be displaying
640 // the inactive bitmap and overwriting the backing store
641 // with the last played frame is wrong in this case
642 if (IsPlaying())
643 {
05a98b6d
RR
644 if (!RebuildBackingStoreUpToFrame(m_currentFrame))
645 Stop(); // in case we are playing
580ca0a4
VZ
646 }
647 }
72045d57
VZ
648}
649
650#endif // wxUSE_ANIMATIONCTRL
651