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