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