remove extraneous semicolons
[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 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.IsOk() )
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 // wxAnimationCtrl
249 // ----------------------------------------------------------------------------
250
251 IMPLEMENT_CLASS(wxAnimationCtrl, wxAnimationCtrlBase)
252 BEGIN_EVENT_TABLE(wxAnimationCtrl, wxAnimationCtrlBase)
253 EVT_PAINT(wxAnimationCtrl::OnPaint)
254 EVT_SIZE(wxAnimationCtrl::OnSize)
255 EVT_TIMER(wxID_ANY, wxAnimationCtrl::OnTimer)
256 END_EVENT_TABLE()
257
258 wxAnimationCtrl::wxAnimationCtrl()
259 {
260 m_currentFrame = 0;
261 m_looped = false;
262 m_isPlaying = false;
263 m_useWinBackgroundColour = false;
264 }
265
266 bool wxAnimationCtrl::Create(wxWindow *parent, wxWindowID id,
267 const wxAnimation& animation, const wxPoint& pos,
268 const wxSize& size, long style, const wxString& name)
269 {
270 m_animation = animation;
271 m_currentFrame = 0;
272 m_looped = true;
273 m_isPlaying = false;
274 m_useWinBackgroundColour = false;
275 m_timer.SetOwner(this);
276
277 if (!base_type::Create(parent, id, pos, size, style, wxDefaultValidator, name))
278 return false;
279
280 // by default we get the same background colour of our parent
281 SetBackgroundColour(parent->GetBackgroundColour());
282 return true;
283 }
284
285 wxAnimationCtrl::~wxAnimationCtrl()
286 {
287 Stop();
288 }
289
290 bool wxAnimationCtrl::LoadFile(const wxString& filename, wxAnimationType type)
291 {
292 wxAnimation anim;
293 if (!anim.LoadFile(filename, type) ||
294 !anim.IsOk())
295 return false;
296
297 SetAnimation(anim);
298 return true;
299 }
300
301 wxSize wxAnimationCtrl::DoGetBestSize() const
302 {
303 if (m_animation.IsOk() && !this->HasFlag(wxAC_NO_AUTORESIZE))
304 return m_animation.GetSize();
305
306 return wxSize(100, 100);
307 }
308
309 void wxAnimationCtrl::SetAnimation(const wxAnimation& animation)
310 {
311 if (IsPlaying())
312 Stop();
313
314 m_animation = animation;
315
316 if (m_animation.GetBackgroundColour() == wxNullColour)
317 SetUseWindowBackgroundColour();
318 if (!this->HasFlag(wxAC_NO_AUTORESIZE))
319 FitToAnimation();
320
321 // display first frame
322 m_currentFrame = 0;
323 if (m_animation.IsOk())
324 RebuildBackingStoreUpToFrame(0);
325 else
326 {
327 // clear to
328 wxMemoryDC dc;
329 dc.SelectObject(m_backingStore);
330
331 // Draw the background
332 DisposeToBackground(dc);
333 }
334
335 Refresh();
336 }
337
338 void wxAnimationCtrl::FitToAnimation()
339 {
340 SetSize(m_animation.GetSize());
341 }
342
343
344 // ----------------------------------------------------------------------------
345 // wxAnimationCtrl - stop/play methods
346 // ----------------------------------------------------------------------------
347
348 void wxAnimationCtrl::Stop()
349 {
350 // leave current frame displayed until Play() is called again
351 m_timer.Stop();
352 m_isPlaying = false;
353 }
354
355 bool wxAnimationCtrl::Play(bool looped)
356 {
357 if (!m_animation.IsOk())
358 return false;
359
360 int oldframe = m_currentFrame;
361 m_looped = looped;
362 m_currentFrame = 0;
363 m_isPlaying = true;
364
365 // small optimization: if the back store was already updated to the
366 // first frame, don't rebuild it
367 if (oldframe != 0)
368 RebuildBackingStoreUpToFrame(0);
369
370 // DrawCurrentFrame() will use our updated backing store
371 wxClientDC clientDC(this);
372 DrawCurrentFrame(clientDC);
373
374 // start the timer
375 int delay = m_animation.GetDelay(0);
376 if (delay == 0)
377 delay = 1; // 0 is invalid timeout for wxTimer.
378 m_timer.Start(delay);
379
380 return true;
381 }
382
383
384
385 // ----------------------------------------------------------------------------
386 // wxAnimationCtrl - rendering methods
387 // ----------------------------------------------------------------------------
388
389 void wxAnimationCtrl::RebuildBackingStoreUpToFrame(size_t frame)
390 {
391 // if we've not created the backing store yet or it's too
392 // small, then recreate it
393 wxSize sz = m_animation.GetSize(),
394 winsz = GetClientSize();
395 int w = wxMin(sz.GetWidth(), winsz.GetWidth());
396 int h = wxMin(sz.GetHeight(), winsz.GetHeight());
397
398 if ( !m_backingStore.IsOk() ||
399 m_backingStore.GetWidth() < w || m_backingStore.GetHeight() < h )
400 {
401 m_backingStore.Create(w, h);
402 }
403
404 wxMemoryDC dc;
405 dc.SelectObject(m_backingStore);
406
407 // Draw the background
408 DisposeToBackground(dc);
409
410 // Draw all intermediate frames that haven't been removed from the animation
411 for (size_t i = 0; i < frame; i++)
412 {
413 if (m_animation.GetDisposalMethod(i) == wxANIM_DONOTREMOVE ||
414 m_animation.GetDisposalMethod(i) == wxANIM_UNSPECIFIED)
415 {
416 DrawFrame(dc, i);
417 }
418 }
419
420 // finally draw this frame
421 DrawFrame(dc, frame);
422 dc.SelectObject(wxNullBitmap);
423 }
424
425 void wxAnimationCtrl::IncrementalUpdateBackingStore()
426 {
427 wxMemoryDC dc;
428 dc.SelectObject(m_backingStore);
429
430 // OPTIMIZATION:
431 // since wxAnimationCtrl can only play animations forward, without skipping
432 // frames, we can be sure that m_backingStore contains the m_currentFrame-1
433 // frame and thus we just need to dispose the m_currentFrame-1 frame and
434 // render the m_currentFrame-th one.
435
436 if (m_currentFrame == 0)
437 {
438 // before drawing the first frame always dispose to bg colour
439 DisposeToBackground(dc);
440 }
441 else
442 {
443 switch (m_animation.GetDisposalMethod(m_currentFrame-1))
444 {
445 case wxANIM_TOBACKGROUND:
446 DisposeToBackground(dc);
447 break;
448
449 case wxANIM_TOPREVIOUS:
450 // this disposal should never be used too often.
451 // E.g. GIF specification explicitely say to keep the usage of this
452 // disposal limited to the minimum.
453 // In fact it may require a lot of time to restore
454 if (m_currentFrame == 1)
455 {
456 // if 0-th frame disposal is to restore to previous frame,
457 // the best we can do is to restore to background
458 DisposeToBackground(dc);
459 }
460 else
461 RebuildBackingStoreUpToFrame(m_currentFrame-2);
462 break;
463
464 case wxANIM_DONOTREMOVE:
465 case wxANIM_UNSPECIFIED:
466 break;
467 }
468 }
469
470 // now just draw the current frame on the top of the backing store
471 DrawFrame(dc, m_currentFrame);
472 dc.SelectObject(wxNullBitmap);
473 }
474
475 void wxAnimationCtrl::DrawFrame(wxDC &dc, size_t frame)
476 {
477 // PERFORMANCE NOTE:
478 // this draw stuff is not as fast as possible: the wxAnimationDecoder
479 // needs first to convert from its internal format to wxImage RGB24;
480 // the wxImage is then converted as a wxBitmap and finally blitted.
481 // If wxAnimationDecoder had a function to convert directly from its
482 // internal format to a port-specific wxBitmap, it would be somewhat faster.
483 wxBitmap bmp(m_animation.GetFrame(frame));
484 dc.DrawBitmap(bmp, m_animation.GetFramePosition(frame),
485 true /* use mask */);
486 }
487
488 void wxAnimationCtrl::DrawCurrentFrame(wxDC& dc)
489 {
490 wxASSERT( m_backingStore.IsOk() );
491
492 // m_backingStore always contains the current frame
493 dc.DrawBitmap(m_backingStore, 0, 0);
494 }
495
496 void wxAnimationCtrl::DisposeToBackground(wxDC& dc)
497 {
498 wxBrush brush(IsUsingWindowBackgroundColour()
499 ? GetBackgroundColour()
500 : m_animation.GetBackgroundColour());
501 dc.SetBackground(brush);
502 dc.Clear();
503 }
504
505 // ----------------------------------------------------------------------------
506 // wxAnimationCtrl - event handlers
507 // ----------------------------------------------------------------------------
508
509 void wxAnimationCtrl::OnPaint(wxPaintEvent& WXUNUSED(event))
510 {
511 // VERY IMPORTANT: the wxPaintDC *must* be created in any case
512 wxPaintDC dc(this);
513
514 // both if we are playing or not, we need to refresh the current frame
515 if ( m_backingStore.IsOk() )
516 DrawCurrentFrame(dc);
517 //else: m_animation is not valid and thus we don't have a valid backing store...
518 }
519
520 void wxAnimationCtrl::OnTimer(wxTimerEvent &WXUNUSED(event))
521 {
522 m_currentFrame++;
523 if (m_currentFrame == m_animation.GetFrameCount())
524 {
525 // Should a non-looped animation display the last frame?
526 if (!m_looped)
527 {
528 m_timer.Stop();
529 m_isPlaying = false;
530 return;
531 }
532 else
533 m_currentFrame = 0; // let's restart
534 }
535
536 IncrementalUpdateBackingStore();
537
538 wxClientDC dc(this);
539 DrawCurrentFrame(dc);
540
541 #ifdef __WXMAC__
542 // without this, the animation currently doesn't redraw under Mac
543 Refresh();
544 #endif // __WXMAC__
545
546 // Set the timer for the next frame
547 int delay = m_animation.GetDelay(m_currentFrame);
548 if (delay == 0)
549 delay = 1; // 0 is invalid timeout for wxTimer.
550 m_timer.Start(delay);
551 }
552
553 void wxAnimationCtrl::OnSize(wxSizeEvent &WXUNUSED(event))
554 {
555 // NB: resizing an animation control may take a lot of time
556 // for big animations as the backing store must be
557 // extended and rebuilt. Try to avoid it!!
558 if (m_animation.IsOk())
559 RebuildBackingStoreUpToFrame(m_currentFrame);
560 }
561
562 #endif // wxUSE_ANIMATIONCTRL
563