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