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