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