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