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