]> git.saurik.com Git - wxWidgets.git/blame_incremental - src/generic/animateg.cpp
Patch for context menu event for generic/Windows code
[wxWidgets.git] / src / generic / animateg.cpp
... / ...
CommitLineData
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"
35WX_DEFINE_LIST(wxAnimationDecoderList)
36
37wxAnimationDecoderList wxAnimation::sm_handlers;
38
39
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
54unsigned int wxAnimation::GetFrameCount() const
55{
56 wxCHECK_MSG( IsOk(), 0, wxT("invalid animation") );
57
58 return M_ANIMDATA->GetFrameCount();
59}
60
61wxImage wxAnimation::GetFrame(unsigned int 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(unsigned int i) const
72{
73 wxCHECK_MSG( IsOk(), 0, wxT("invalid animation") );
74
75 return M_ANIMDATA->GetDelay(i);
76}
77
78wxPoint wxAnimation::GetFramePosition(unsigned int frame) const
79{
80 wxCHECK_MSG( IsOk(), wxDefaultPosition, wxT("invalid animation") );
81
82 return M_ANIMDATA->GetFramePosition(frame);
83}
84
85wxSize wxAnimation::GetFrameSize(unsigned int frame) const
86{
87 wxCHECK_MSG( IsOk(), wxDefaultSize, wxT("invalid animation") );
88
89 return M_ANIMDATA->GetFrameSize(frame);
90}
91
92wxAnimationDisposal wxAnimation::GetDisposalMethod(unsigned int frame) const
93{
94 wxCHECK_MSG( IsOk(), wxANIM_UNSPECIFIED, wxT("invalid animation") );
95
96 return M_ANIMDATA->GetDisposalMethod(frame);
97}
98
99wxColour wxAnimation::GetTransparentColour(unsigned int frame) const
100{
101 wxCHECK_MSG( IsOk(), wxNullColour, wxT("invalid animation") );
102
103 return M_ANIMDATA->GetTransparentColour(frame);
104}
105
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);
116 if ( !stream.IsOk() )
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 {
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 if (handler == NULL)
151 {
152 wxLogWarning( _("No animation handler for type %ld defined."), type );
153
154 return false;
155 }
156
157
158 // do a copy of the handler from the static list which we will own
159 // as our reference data
160 m_refData = handler->Clone();
161
162 if (stream.IsSeekable() && !M_ANIMDATA->CanRead(stream))
163 {
164 wxLogError(_("Animation file is not of type %ld."), type);
165 return false;
166 }
167 else
168 return M_ANIMDATA->Load(stream);
169}
170
171
172// ----------------------------------------------------------------------------
173// animation decoders
174// ----------------------------------------------------------------------------
175
176void wxAnimation::AddHandler( wxAnimationDecoder *handler )
177{
178 // Check for an existing handler of the type being added.
179 if (FindHandler( handler->GetType() ) == 0)
180 {
181 sm_handlers.Append( handler );
182 }
183 else
184 {
185 // This is not documented behaviour, merely the simplest 'fix'
186 // for preventing duplicate additions. If someone ever has
187 // a good reason to add and remove duplicate handlers (and they
188 // may) we should probably refcount the duplicates.
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{
226#if wxUSE_GIF
227 AddHandler(new wxGIFDecoder);
228#endif // wxUSE_GIF
229#if wxUSE_ICO_CUR
230 AddHandler(new wxANIDecoder);
231#endif // wxUSE_ICO_CUR
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
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
276void wxAnimationCtrl::Init()
277{
278 m_currentFrame = 0;
279 m_looped = false;
280 m_isPlaying = false;
281
282 // use the window background colour by default to be consistent
283 // with the GTK+ native version
284 m_useWinBackgroundColour = true;
285}
286
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_timer.SetOwner(this);
292
293 if (!base_type::Create(parent, id, pos, size, style, wxDefaultValidator, name))
294 return false;
295
296 // by default we get the same background colour of our parent
297 SetBackgroundColour(parent->GetBackgroundColour());
298
299 SetAnimation(animation);
300
301 return true;
302}
303
304wxAnimationCtrl::~wxAnimationCtrl()
305{
306 Stop();
307}
308
309bool wxAnimationCtrl::LoadFile(const wxString& filename, wxAnimationType type)
310{
311 wxAnimation anim;
312 if (!anim.LoadFile(filename, type) ||
313 !anim.IsOk())
314 return false;
315
316 SetAnimation(anim);
317 return true;
318}
319
320wxSize wxAnimationCtrl::DoGetBestSize() const
321{
322 if (m_animation.IsOk() && !this->HasFlag(wxAC_NO_AUTORESIZE))
323 return m_animation.GetSize();
324
325 return wxSize(100, 100);
326}
327
328void wxAnimationCtrl::SetAnimation(const wxAnimation& animation)
329{
330 if (IsPlaying())
331 Stop();
332
333 // set new animation even if it's wxNullAnimation
334 m_animation = animation;
335 if (!m_animation.IsOk())
336 {
337 DisplayStaticImage();
338 return;
339 }
340
341 if (m_animation.GetBackgroundColour() == wxNullColour)
342 SetUseWindowBackgroundColour();
343 if (!this->HasFlag(wxAC_NO_AUTORESIZE))
344 FitToAnimation();
345
346 DisplayStaticImage();
347}
348
349void wxAnimationCtrl::SetInactiveBitmap(const wxBitmap &bmp)
350{
351 // if the bitmap has an associated mask, we need to set our background to
352 // the colour of our parent otherwise when calling DrawCurrentFrame()
353 // (which uses the bitmap's mask), our background colour would be used for
354 // transparent areas - and that's not what we want (at least for
355 // consistency with the GTK version)
356 if ( bmp.GetMask() != NULL && GetParent() != NULL )
357 SetBackgroundColour(GetParent()->GetBackgroundColour());
358
359 wxAnimationCtrlBase::SetInactiveBitmap(bmp);
360}
361
362void wxAnimationCtrl::FitToAnimation()
363{
364 SetSize(m_animation.GetSize());
365}
366
367bool wxAnimationCtrl::SetBackgroundColour(const wxColour& colour)
368{
369 if ( !wxWindow::SetBackgroundColour(colour) )
370 return false;
371
372 // if not playing, then this change must be seen immediately (unless
373 // there's an inactive bitmap set which has higher priority than bg colour)
374 if ( !IsPlaying() )
375 DisplayStaticImage();
376
377 return true;
378}
379
380
381// ----------------------------------------------------------------------------
382// wxAnimationCtrl - stop/play methods
383// ----------------------------------------------------------------------------
384
385void wxAnimationCtrl::Stop()
386{
387 m_timer.Stop();
388 m_isPlaying = false;
389
390 // reset frame counter
391 m_currentFrame = 0;
392
393 DisplayStaticImage();
394}
395
396bool wxAnimationCtrl::Play(bool looped)
397{
398 if (!m_animation.IsOk())
399 return false;
400
401 m_looped = looped;
402 m_currentFrame = 0;
403
404 if (!RebuildBackingStoreUpToFrame(0))
405 return false;
406
407 m_isPlaying = true;
408
409 // do a ClearBackground() to avoid that e.g. the custom static bitmap which
410 // was eventually shown previously remains partially drawn
411 ClearBackground();
412
413 // DrawCurrentFrame() will use our updated backing store
414 wxClientDC clientDC(this);
415 DrawCurrentFrame(clientDC);
416
417 // start the timer
418 int delay = m_animation.GetDelay(0);
419 if (delay == 0)
420 delay = 1; // 0 is invalid timeout for wxTimer.
421 m_timer.Start(delay, true);
422
423 return true;
424}
425
426
427
428// ----------------------------------------------------------------------------
429// wxAnimationCtrl - rendering methods
430// ----------------------------------------------------------------------------
431
432bool wxAnimationCtrl::RebuildBackingStoreUpToFrame(unsigned int frame)
433{
434 // if we've not created the backing store yet or it's too
435 // small, then recreate it
436 wxSize sz = m_animation.GetSize(),
437 winsz = GetClientSize();
438 int w = wxMin(sz.GetWidth(), winsz.GetWidth());
439 int h = wxMin(sz.GetHeight(), winsz.GetHeight());
440
441 if ( !m_backingStore.IsOk() ||
442 m_backingStore.GetWidth() < w || m_backingStore.GetHeight() < h )
443 {
444 if (!m_backingStore.Create(w, h))
445 return false;
446 }
447
448 wxMemoryDC dc;
449 dc.SelectObject(m_backingStore);
450
451 // Draw the background
452 DisposeToBackground(dc);
453
454 // Draw all intermediate frames that haven't been removed from the animation
455 for (unsigned int i = 0; i < frame; i++)
456 {
457 if (m_animation.GetDisposalMethod(i) == wxANIM_DONOTREMOVE ||
458 m_animation.GetDisposalMethod(i) == wxANIM_UNSPECIFIED)
459 {
460 DrawFrame(dc, i);
461 }
462 else if (m_animation.GetDisposalMethod(i) == wxANIM_TOBACKGROUND)
463 DisposeToBackground(dc, m_animation.GetFramePosition(i),
464 m_animation.GetFrameSize(i));
465 }
466
467 // finally draw this frame
468 DrawFrame(dc, frame);
469 dc.SelectObject(wxNullBitmap);
470
471 return true;
472}
473
474void wxAnimationCtrl::IncrementalUpdateBackingStore()
475{
476 wxMemoryDC dc;
477 dc.SelectObject(m_backingStore);
478
479 // OPTIMIZATION:
480 // since wxAnimationCtrl can only play animations forward, without skipping
481 // frames, we can be sure that m_backingStore contains the m_currentFrame-1
482 // frame and thus we just need to dispose the m_currentFrame-1 frame and
483 // render the m_currentFrame-th one.
484
485 if (m_currentFrame == 0)
486 {
487 // before drawing the first frame always dispose to bg colour
488 DisposeToBackground(dc);
489 }
490 else
491 {
492 switch (m_animation.GetDisposalMethod(m_currentFrame-1))
493 {
494 case wxANIM_TOBACKGROUND:
495 DisposeToBackground(dc, m_animation.GetFramePosition(m_currentFrame-1),
496 m_animation.GetFrameSize(m_currentFrame-1));
497 break;
498
499 case wxANIM_TOPREVIOUS:
500 // this disposal should never be used too often.
501 // E.g. GIF specification explicitely say to keep the usage of this
502 // disposal limited to the minimum.
503 // In fact it may require a lot of time to restore
504 if (m_currentFrame == 1)
505 {
506 // if 0-th frame disposal is to restore to previous frame,
507 // the best we can do is to restore to background
508 DisposeToBackground(dc);
509 }
510 else
511 if (!RebuildBackingStoreUpToFrame(m_currentFrame-2))
512 Stop();
513 break;
514
515 case wxANIM_DONOTREMOVE:
516 case wxANIM_UNSPECIFIED:
517 break;
518 }
519 }
520
521 // now just draw the current frame on the top of the backing store
522 DrawFrame(dc, m_currentFrame);
523 dc.SelectObject(wxNullBitmap);
524}
525
526void wxAnimationCtrl::DisplayStaticImage()
527{
528 wxASSERT(!IsPlaying());
529
530 // m_bmpStaticReal will be updated only if necessary...
531 UpdateStaticImage();
532
533 if (m_bmpStaticReal.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_bmpStaticReal.GetMask() )
538 {
539 wxMemoryDC temp;
540 temp.SelectObject(m_backingStore);
541 DisposeToBackground(temp);
542 temp.DrawBitmap(m_bmpStaticReal, 0, 0, true /* use mask */);
543 }
544 else
545 m_backingStore = m_bmpStaticReal;
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
561void wxAnimationCtrl::DrawFrame(wxDC &dc, unsigned int 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
574void 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
582void wxAnimationCtrl::DisposeToBackground()
583{
584 // clear the backing store
585 wxMemoryDC dc;
586 dc.SelectObject(m_backingStore);
587 if ( dc.IsOk() )
588 DisposeToBackground(dc);
589}
590
591void wxAnimationCtrl::DisposeToBackground(wxDC& dc)
592{
593 wxColour col = IsUsingWindowBackgroundColour()
594 ? GetBackgroundColour()
595 : m_animation.GetBackgroundColour();
596
597 wxBrush brush(col);
598 dc.SetBackground(brush);
599 dc.Clear();
600}
601
602void wxAnimationCtrl::DisposeToBackground(wxDC& dc, const wxPoint &pos, const wxSize &sz)
603{
604 wxColour col = IsUsingWindowBackgroundColour()
605 ? GetBackgroundColour()
606 : m_animation.GetBackgroundColour();
607 wxBrush brush(col);
608 dc.SetBrush(brush); // SetBrush and not SetBackground !!
609 dc.SetPen(*wxTRANSPARENT_PEN);
610 dc.DrawRectangle(pos, sz);
611}
612
613// ----------------------------------------------------------------------------
614// wxAnimationCtrl - event handlers
615// ----------------------------------------------------------------------------
616
617void wxAnimationCtrl::OnPaint(wxPaintEvent& WXUNUSED(event))
618{
619 // VERY IMPORTANT: the wxPaintDC *must* be created in any case
620 wxPaintDC dc(this);
621
622 if ( m_backingStore.IsOk() )
623 {
624 // NOTE: we draw the bitmap explicitely ignoring the mask (if any);
625 // i.e. we don't want to combine the backing store with the
626 // possibly wrong preexisting contents of the window!
627 dc.DrawBitmap(m_backingStore, 0, 0, false /* no mask */);
628 }
629 else
630 {
631 // m_animation is not valid and thus we don't have a valid backing store...
632 // clear then our area to the background colour
633 DisposeToBackground(dc);
634 }
635}
636
637void wxAnimationCtrl::OnTimer(wxTimerEvent &WXUNUSED(event))
638{
639 m_currentFrame++;
640 if (m_currentFrame == m_animation.GetFrameCount())
641 {
642 // Should a non-looped animation display the last frame?
643 if (!m_looped)
644 {
645 Stop();
646 return;
647 }
648 else
649 m_currentFrame = 0; // let's restart
650 }
651
652 IncrementalUpdateBackingStore();
653
654 wxClientDC dc(this);
655 DrawCurrentFrame(dc);
656
657#ifdef __WXMAC__
658 // without this, the animation currently doesn't redraw under Mac
659 Refresh();
660#endif // __WXMAC__
661
662 // Set the timer for the next frame
663 int delay = m_animation.GetDelay(m_currentFrame);
664 if (delay == 0)
665 delay = 1; // 0 is invalid timeout for wxTimer.
666 m_timer.Start(delay, true);
667}
668
669void wxAnimationCtrl::OnSize(wxSizeEvent &WXUNUSED(event))
670{
671 // NB: resizing an animation control may take a lot of time
672 // for big animations as the backing store must be
673 // extended and rebuilt. Try to avoid it e.g. using
674 // a null proportion value for your wxAnimationCtrls
675 // when using them inside sizers.
676 if (m_animation.IsOk())
677 {
678 // be careful to change the backing store *only* if we are
679 // playing the animation as otherwise we may be displaying
680 // the inactive bitmap and overwriting the backing store
681 // with the last played frame is wrong in this case
682 if (IsPlaying())
683 {
684 if (!RebuildBackingStoreUpToFrame(m_currentFrame))
685 Stop(); // in case we are playing
686 }
687 }
688}
689
690#endif // wxUSE_ANIMATIONCTRL
691