1 /////////////////////////////////////////////////////////////////////////////
2 // Name:        src/gtk/animate.cpp
3 // Purpose:     wxAnimation and wxAnimationCtrl
4 // Author:      Francesco Montorsi
5 // Modified By:
6 // Created:     24/09/2006
7 // Copyright:   (c) Francesco Montorsi
8 // Licence:     wxWindows licence
9 /////////////////////////////////////////////////////////////////////////////
10 
11 // For compilers that support precompilation, includes "wx.h".
12 #include "wx/wxprec.h"
13 
14 #if wxUSE_ANIMATIONCTRL
15 #include "wx/animate.h"
16 #include "wx/gtk/private/animate.h"
17 #include "wx/generic/private/animate.h"
18 
19 #ifndef WX_PRECOMP
20     #include "wx/image.h"
21     #include "wx/log.h"
22     #include "wx/stream.h"
23 #endif
24 
25 #include "wx/wfstream.h"
26 #include "wx/gtk/private.h"
27 #include "wx/gtk/private/object.h"
28 
29 
30 // ============================================================================
31 // implementation
32 // ============================================================================
33 
34 extern "C" {
35 static
gdk_pixbuf_area_updated(GdkPixbufLoader * loader,gint WXUNUSED (x),gint WXUNUSED (y),gint WXUNUSED (width),gint WXUNUSED (height),wxAnimationGTKImpl * anim)36 void gdk_pixbuf_area_updated(GdkPixbufLoader    *loader,
37                              gint               WXUNUSED(x),
38                              gint               WXUNUSED(y),
39                              gint               WXUNUSED(width),
40                              gint               WXUNUSED(height),
41                              wxAnimationGTKImpl *anim)
42 {
43     if (anim && anim->GetPixbuf() == NULL)
44     {
45         // we need to set the pixbuf only if this is the first time this signal
46         // has been called!
47         anim->SetPixbuf(gdk_pixbuf_loader_get_animation(loader));
48     }
49 }
50 }
51 
52 //-----------------------------------------------------------------------------
53 // wxAnimationGTKImpl
54 //-----------------------------------------------------------------------------
55 
56 #ifdef wxHAS_NATIVE_ANIMATIONCTRL
57 
58 /* static */
CreateDefault()59 wxAnimationImpl *wxAnimationImpl::CreateDefault()
60 {
61     return new wxAnimationGTKImpl();
62 }
63 
64 #endif // wxHAS_NATIVE_ANIMATIONCTRL
65 
IsCompatibleWith(wxClassInfo * ci) const66 bool wxAnimationGTKImpl::IsCompatibleWith(wxClassInfo* ci) const
67 {
68     return ci->IsKindOf(&wxAnimationCtrl::ms_classInfo);
69 }
70 
LoadFile(const wxString & name,wxAnimationType WXUNUSED (type))71 bool wxAnimationGTKImpl::LoadFile(const wxString &name, wxAnimationType WXUNUSED(type))
72 {
73     UnRef();
74     m_pixbuf = gdk_pixbuf_animation_new_from_file(wxGTK_CONV_FN(name), NULL);
75     return IsOk();
76 }
77 
Load(wxInputStream & stream,wxAnimationType type)78 bool wxAnimationGTKImpl::Load(wxInputStream &stream, wxAnimationType type)
79 {
80     UnRef();
81 
82     char anim_type[12];
83     switch (type)
84     {
85     case wxANIMATION_TYPE_GIF:
86         strcpy(anim_type, "gif");
87         break;
88 
89     case wxANIMATION_TYPE_ANI:
90         strcpy(anim_type, "ani");
91         break;
92 
93     default:
94         anim_type[0] = '\0';
95         break;
96     }
97 
98     // create a GdkPixbufLoader
99     GError *error = NULL;
100     GdkPixbufLoader *loader;
101     if (type != wxANIMATION_TYPE_INVALID && type != wxANIMATION_TYPE_ANY)
102         loader = gdk_pixbuf_loader_new_with_type(anim_type, &error);
103     else
104         loader = gdk_pixbuf_loader_new();
105 
106     wxGtkObject<GdkPixbufLoader> ensureUnrefLoader(loader);
107 
108     if (!loader ||
109         error != NULL)  // even if the loader was allocated, an error could have happened
110     {
111         wxLogDebug(wxT("Could not create the loader for '%s' animation type: %s"),
112                    anim_type, error->message);
113         return false;
114     }
115 
116     // connect to loader signals
117     g_signal_connect(loader, "area-updated", G_CALLBACK(gdk_pixbuf_area_updated), this);
118 
119     guchar buf[2048];
120     bool data_written = false;
121     while (stream.IsOk())
122     {
123         // read a chunk of data
124         if (!stream.Read(buf, sizeof(buf)) &&
125             stream.GetLastError() != wxSTREAM_EOF)   // EOF is OK for now
126         {
127             // gdk_pixbuf_loader_close wants the GError == NULL
128             gdk_pixbuf_loader_close(loader, NULL);
129             return false;
130         }
131 
132         // fetch all data into the loader
133         if (!gdk_pixbuf_loader_write(loader, buf, stream.LastRead(), &error))
134         {
135             wxLogDebug(wxT("Could not write to the loader: %s"), error->message);
136 
137             // gdk_pixbuf_loader_close wants the GError == NULL
138             gdk_pixbuf_loader_close(loader, NULL);
139             return false;
140         }
141 
142         data_written = true;
143     }
144 
145     if (!data_written)
146     {
147         wxLogDebug("Could not read data from the stream...");
148         gdk_pixbuf_loader_close(loader, NULL);
149         return false;
150     }
151 
152     // load complete: gdk_pixbuf_loader_close will now check if the data we
153     // wrote inside the pixbuf loader does make sense and will give an error
154     // if it doesn't (because of a truncated file, corrupted data or whatelse)
155     if (!gdk_pixbuf_loader_close(loader, &error))
156     {
157         wxLogDebug(wxT("Could not close the loader: %s"), error->message);
158         return false;
159     }
160 
161     // wait until we get the last area_updated signal
162     return data_written;
163 }
164 
GetFrame(unsigned int WXUNUSED (frame)) const165 wxImage wxAnimationGTKImpl::GetFrame(unsigned int WXUNUSED(frame)) const
166 {
167     return wxNullImage;
168 }
169 
GetSize() const170 wxSize wxAnimationGTKImpl::GetSize() const
171 {
172     return wxSize(gdk_pixbuf_animation_get_width(m_pixbuf),
173                   gdk_pixbuf_animation_get_height(m_pixbuf));
174 }
175 
UnRef()176 void wxAnimationGTKImpl::UnRef()
177 {
178     if (m_pixbuf)
179         g_object_unref(m_pixbuf);
180     m_pixbuf = NULL;
181 }
182 
SetPixbuf(GdkPixbufAnimation * p)183 void wxAnimationGTKImpl::SetPixbuf(GdkPixbufAnimation* p)
184 {
185     UnRef();
186     m_pixbuf = p;
187     if (m_pixbuf)
188         g_object_ref(m_pixbuf);
189 }
190 
191 //-----------------------------------------------------------------------------
192 // wxAnimationCtrl
193 //-----------------------------------------------------------------------------
194 
195 wxIMPLEMENT_DYNAMIC_CLASS(wxAnimationCtrl, wxControl);
196 
wxBEGIN_EVENT_TABLE(wxAnimationCtrl,wxAnimationCtrlBase)197 wxBEGIN_EVENT_TABLE(wxAnimationCtrl, wxAnimationCtrlBase)
198     EVT_TIMER(wxID_ANY, wxAnimationCtrl::OnTimer)
199 wxEND_EVENT_TABLE()
200 
201 void wxAnimationCtrl::Init()
202 {
203     m_anim = NULL;
204     m_iter = NULL;
205     m_bPlaying = false;
206 }
207 
Create(wxWindow * parent,wxWindowID id,const wxAnimation & anim,const wxPoint & pos,const wxSize & size,long style,const wxString & name)208 bool wxAnimationCtrl::Create( wxWindow *parent, wxWindowID id,
209                               const wxAnimation& anim,
210                               const wxPoint& pos,
211                               const wxSize& size,
212                               long style,
213                               const wxString& name)
214 {
215     if (!PreCreation( parent, pos, size ) ||
216         !base_type::CreateBase(parent, id, pos, size, style & wxWINDOW_STYLE_MASK,
217                                wxDefaultValidator, name))
218     {
219         wxFAIL_MSG( wxT("wxAnimationCtrl creation failed") );
220         return false;
221     }
222 
223     SetWindowStyle(style);
224 
225     m_widget = gtk_image_new();
226     g_object_ref(m_widget);
227 
228     m_parent->DoAddChild( this );
229 
230     PostCreation(size);
231     SetInitialSize(size);
232 
233     if (anim.IsOk())
234         SetAnimation(anim);
235 
236     // init the timer used for animation
237     m_timer.SetOwner(this);
238 
239     return true;
240 }
241 
~wxAnimationCtrl()242 wxAnimationCtrl::~wxAnimationCtrl()
243 {
244     if (IsPlaying())
245         Stop();
246     ResetAnim();
247     ResetIter();
248 }
249 
LoadFile(const wxString & filename,wxAnimationType type)250 bool wxAnimationCtrl::LoadFile(const wxString &filename, wxAnimationType type)
251 {
252     wxFileInputStream fis(filename);
253     if (!fis.IsOk())
254         return false;
255     return Load(fis, type);
256 }
257 
Load(wxInputStream & stream,wxAnimationType type)258 bool wxAnimationCtrl::Load(wxInputStream& stream, wxAnimationType type)
259 {
260     wxAnimation anim(CreateAnimation());
261     if ( !anim.Load(stream, type) || !anim.IsOk() )
262         return false;
263 
264     SetAnimation(anim);
265     return true;
266 }
267 
CreateCompatibleAnimation()268 wxAnimation wxAnimationCtrl::CreateCompatibleAnimation()
269 {
270     return MakeAnimFromImpl(new wxAnimationGTKImpl());
271 }
272 
DoCreateAnimationImpl() const273 wxAnimationImpl* wxAnimationCtrl::DoCreateAnimationImpl() const
274 {
275     return new wxAnimationGTKImpl();
276 }
277 
SetAnimation(const wxAnimation & anim)278 void wxAnimationCtrl::SetAnimation(const wxAnimation &anim)
279 {
280     if (IsPlaying())
281         Stop();
282 
283     ResetAnim();
284     ResetIter();
285 
286     m_animation = anim;
287     if (!m_animation.IsOk())
288     {
289         m_anim = NULL;
290         DisplayStaticImage();
291         return;
292     }
293 
294     wxCHECK_RET(anim.IsCompatibleWith(GetClassInfo()),
295                 wxT("incompatible animation") );
296 
297     // copy underlying GdkPixbuf object
298     m_anim = AnimationImplGetPixbuf();
299 
300     // m_anim may be null in case wxNullAnimation has been passed
301     if (m_anim)
302     {
303         // add a reference to the GdkPixbufAnimation
304         g_object_ref(m_anim);
305 
306         if (!this->HasFlag(wxAC_NO_AUTORESIZE))
307             FitToAnimation();
308     }
309 
310     DisplayStaticImage();
311 }
312 
FitToAnimation()313 void wxAnimationCtrl::FitToAnimation()
314 {
315     if (!m_anim)
316         return;
317 
318     int w = gdk_pixbuf_animation_get_width(m_anim),
319         h = gdk_pixbuf_animation_get_height(m_anim);
320 
321     // update our size to fit animation
322     SetSize(w, h);
323 }
324 
ResetAnim()325 void wxAnimationCtrl::ResetAnim()
326 {
327     if (m_anim)
328         g_object_unref(m_anim);
329     m_anim = NULL;
330 }
331 
ResetIter()332 void wxAnimationCtrl::ResetIter()
333 {
334     if (m_iter)
335         g_object_unref(m_iter);
336     m_iter = NULL;
337 }
338 
Play()339 bool wxAnimationCtrl::Play()
340 {
341     if (m_anim == NULL)
342         return false;
343 
344     // init the iterator and start a one-shot timer
345     ResetIter();
346     m_iter = gdk_pixbuf_animation_get_iter (m_anim, NULL);
347     m_bPlaying = true;
348 
349     // gdk_pixbuf_animation_iter_get_delay_time() may return -1 which means
350     // that the timer should not start
351     int n = gdk_pixbuf_animation_iter_get_delay_time(m_iter);
352     if (n >= 0)
353         m_timer.Start(n, true);
354 
355     return true;
356 }
357 
Stop()358 void wxAnimationCtrl::Stop()
359 {
360     // leave current frame displayed until Play() is called again
361     if (IsPlaying())
362         m_timer.Stop();
363     m_bPlaying = false;
364 
365     ResetIter();
366     DisplayStaticImage();
367 }
368 
DisplayStaticImage()369 void wxAnimationCtrl::DisplayStaticImage()
370 {
371     wxASSERT(!IsPlaying());
372 
373     // m_bmpStaticReal will be updated only if necessary...
374     UpdateStaticImage();
375 
376     if (m_bmpStaticReal.IsOk())
377     {
378         // show inactive bitmap
379         gtk_image_set_from_pixbuf(GTK_IMAGE(m_widget),
380                                       m_bmpStaticReal.GetPixbuf());
381     }
382     else
383     {
384         if (m_anim)
385         {
386             // even if not clearly documented, gdk_pixbuf_animation_get_static_image()
387             // always returns the first frame of the animation
388             gtk_image_set_from_pixbuf(GTK_IMAGE(m_widget),
389                                         gdk_pixbuf_animation_get_static_image(m_anim));
390         }
391         else
392         {
393             ClearToBackgroundColour();
394         }
395     }
396 }
397 
IsPlaying() const398 bool wxAnimationCtrl::IsPlaying() const
399 {
400     // NB: we cannot just return m_timer.IsRunning() as this would not
401     //     be safe as e.g. if we are displaying a frame forever,
402     //     then we are "officially" still playing the animation, but
403     //     the timer is not running anymore...
404     return m_bPlaying;
405 }
406 
DoGetBestSize() const407 wxSize wxAnimationCtrl::DoGetBestSize() const
408 {
409     if (m_anim && !this->HasFlag(wxAC_NO_AUTORESIZE))
410     {
411         return wxSize(gdk_pixbuf_animation_get_width(m_anim),
412                       gdk_pixbuf_animation_get_height(m_anim));
413     }
414 
415     return wxSize(100,100);
416 }
417 
ClearToBackgroundColour()418 void wxAnimationCtrl::ClearToBackgroundColour()
419 {
420     wxSize sz = GetClientSize();
421     GdkPixbuf *newpix = gdk_pixbuf_new(GDK_COLORSPACE_RGB, false, 8,
422                                        sz.GetWidth(), sz.GetHeight());
423     if (!newpix)
424         return;
425 
426     wxColour clr = GetBackgroundColour();
427     guint32 col = (clr.Red() << 24) | (clr.Green() << 16) | (clr.Blue() << 8);
428     gdk_pixbuf_fill(newpix, col);
429 
430     gtk_image_set_from_pixbuf(GTK_IMAGE(m_widget), newpix);
431     g_object_unref(newpix);
432 }
433 
SetBackgroundColour(const wxColour & colour)434 bool wxAnimationCtrl::SetBackgroundColour( const wxColour &colour )
435 {
436     // wxWindowGTK::SetBackgroundColour works but since our m_widget is a GtkImage
437     // it won't show the background colour unlike the user would expect.
438     // Thus we clear the GtkImage contents to the background colour...
439     if (!wxControl::SetBackgroundColour(colour))
440         return false;
441 
442     // if not playing the change must take place immediately but
443     // remember that the inactive bitmap has higher priority over the background
444     // colour; DisplayStaticImage() will handle that
445     if ( !IsPlaying() )
446         DisplayStaticImage();
447 
448     return true;
449 }
450 
451 
452 //-----------------------------------------------------------------------------
453 // wxAnimationCtrl - event handlers
454 //-----------------------------------------------------------------------------
455 
OnTimer(wxTimerEvent & WXUNUSED (ev))456 void wxAnimationCtrl::OnTimer(wxTimerEvent& WXUNUSED(ev))
457 {
458     wxASSERT(m_iter != NULL);
459 
460     // gdk_pixbuf_animation_iter_advance() will automatically restart
461     // the animation, if necessary and we have no way to know !!
462     if (gdk_pixbuf_animation_iter_advance(m_iter, NULL))
463     {
464         // start a new one-shot timer
465         int n = gdk_pixbuf_animation_iter_get_delay_time(m_iter);
466         if (n >= 0)
467             m_timer.Start(n, true);
468 
469         gtk_image_set_from_pixbuf(GTK_IMAGE(m_widget),
470                                   gdk_pixbuf_animation_iter_get_pixbuf(m_iter));
471     }
472     else
473     {
474         // no need to update the m_widget yet
475         m_timer.Start(10, true);
476     }
477 }
478 
479 
480 // helpers to safely access wxAnimationGTKImpl methods
481 #define ANIMATION (static_cast<wxAnimationGTKImpl*>(GetAnimImpl()))
482 
AnimationImplGetPixbuf() const483 GdkPixbufAnimation* wxAnimationCtrl::AnimationImplGetPixbuf() const
484 {
485     wxCHECK_MSG( m_animation.IsOk(), NULL, wxT("invalid animation") );
486     return ANIMATION->GetPixbuf();
487 }
488 
AnimationImplSetPixbuf(GdkPixbufAnimation * p)489 void wxAnimationCtrl::AnimationImplSetPixbuf(GdkPixbufAnimation* p)
490 {
491     wxCHECK_RET( m_animation.IsOk(), wxT("invalid animation") );
492     ANIMATION->SetPixbuf(p);
493 }
494 
495 #endif      // wxUSE_ANIMATIONCTRL
496