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: animate.cpp 43898 2006-12-10 14:18:37Z VZ $
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
32void 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
52IMPLEMENT_DYNAMIC_CLASS(wxAnimation, wxAnimationBase)
53
54wxAnimation::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
62wxAnimation::wxAnimation(GdkPixbufAnimation *p)
63{
64    m_pixbuf = p;
65    if ( m_pixbuf )
66        g_object_ref(m_pixbuf);
67}
68
69wxAnimation& 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
82bool 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
90bool 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
153wxImage wxAnimation::GetFrame(unsigned int WXUNUSED(frame)) const
154{
155    return wxNullImage;
156}
157
158wxSize wxAnimation::GetSize() const
159{
160    return wxSize(gdk_pixbuf_animation_get_width(m_pixbuf),
161                  gdk_pixbuf_animation_get_height(m_pixbuf));
162}
163
164void wxAnimation::UnRef()
165{
166    if (m_pixbuf)
167        g_object_unref(m_pixbuf);
168    m_pixbuf = NULL;
169}
170
171void 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
183IMPLEMENT_DYNAMIC_CLASS(wxAnimationCtrl, wxAnimationCtrlBase)
184BEGIN_EVENT_TABLE(wxAnimationCtrl, wxAnimationCtrlBase)
185    EVT_TIMER(wxID_ANY, wxAnimationCtrl::OnTimer)
186END_EVENT_TABLE()
187
188void wxAnimationCtrl::Init()
189{
190    m_anim = NULL;
191    m_iter = NULL;
192    m_bPlaying = false;
193}
194
195bool 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
232wxAnimationCtrl::~wxAnimationCtrl()
233{
234    ResetAnim();
235    ResetIter();
236}
237
238bool 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
248void 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
272void 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
284void wxAnimationCtrl::ResetAnim()
285{
286    if (m_anim)
287        g_object_unref(m_anim);
288    m_anim = NULL;
289}
290
291void wxAnimationCtrl::ResetIter()
292{
293    if (m_iter)
294        g_object_unref(m_iter);
295    m_iter = NULL;
296}
297
298bool 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
317void 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
328void wxAnimationCtrl::DisplayStaticImage()
329{
330    wxASSERT(!IsPlaying());
331
332    // m_bmpStaticReal will be updated only if necessary...
333    UpdateStaticImage();
334
335    if (m_bmpStaticReal.IsOk())
336    {
337        // show inactive bitmap
338        GdkBitmap *mask = (GdkBitmap *) NULL;
339        if (m_bmpStaticReal.GetMask())
340            mask = m_bmpStaticReal.GetMask()->GetBitmap();
341
342        if (m_bmpStaticReal.HasPixbuf())
343        {
344            gtk_image_set_from_pixbuf(GTK_IMAGE(m_widget),
345                                      m_bmpStaticReal.GetPixbuf());
346        }
347        else
348        {
349            gtk_image_set_from_pixmap(GTK_IMAGE(m_widget),
350                                      m_bmpStaticReal.GetPixmap(), mask);
351        }
352    }
353    else
354    {
355        if (m_anim)
356        {
357            // even if not clearly documented, gdk_pixbuf_animation_get_static_image()
358            // always returns the first frame of the animation
359            gtk_image_set_from_pixbuf(GTK_IMAGE(m_widget),
360                                        gdk_pixbuf_animation_get_static_image(m_anim));
361        }
362        else
363        {
364            ClearToBackgroundColour();
365        }
366    }
367}
368
369bool wxAnimationCtrl::IsPlaying() const
370{
371    // NB: we cannot just return m_timer.IsRunning() as this would not
372    //     be safe as e.g. if we are displaying a frame forever,
373    //     then we are "officially" still playing the animation, but
374    //     the timer is not running anymore...
375    return m_bPlaying;
376}
377
378wxSize wxAnimationCtrl::DoGetBestSize() const
379{
380    if (m_anim && !this->HasFlag(wxAC_NO_AUTORESIZE))
381    {
382        return wxSize(gdk_pixbuf_animation_get_width(m_anim),
383                      gdk_pixbuf_animation_get_height(m_anim));
384    }
385
386    return wxSize(100,100);
387}
388
389void wxAnimationCtrl::ClearToBackgroundColour()
390{
391    wxSize sz = GetClientSize();
392    GdkPixbuf *newpix = gdk_pixbuf_new(GDK_COLORSPACE_RGB, false, 8,
393                                       sz.GetWidth(), sz.GetHeight());
394    if (!newpix)
395        return;
396
397    wxColour clr = GetBackgroundColour();
398    guint32 col = (clr.Red() << 24) | (clr.Green() << 16) | (clr.Blue() << 8);
399    gdk_pixbuf_fill(newpix, col);
400
401    gtk_image_set_from_pixbuf(GTK_IMAGE(m_widget), newpix);
402    g_object_unref(newpix);
403}
404
405bool wxAnimationCtrl::SetBackgroundColour( const wxColour &colour )
406{
407    // wxWindowGTK::SetBackgroundColour works but since our m_widget is a GtkImage
408    // it won't show the background colour unlike the user would expect.
409    // Thus we clear the GtkImage contents to the background colour...
410    if (!wxControl::SetBackgroundColour(colour))
411        return false;
412
413    // if not playing the change must take place immediately but
414    // remember that the inactive bitmap has higher priority over the background
415    // colour; DisplayStaticImage() will handle that
416    if ( !IsPlaying() )
417        DisplayStaticImage();
418
419    return true;
420}
421
422
423//-----------------------------------------------------------------------------
424// wxAnimationCtrl - event handlers
425//-----------------------------------------------------------------------------
426
427void wxAnimationCtrl::OnTimer(wxTimerEvent &ev)
428{
429    wxASSERT(m_iter != NULL);
430
431    // gdk_pixbuf_animation_iter_advance() will automatically restart
432    // the animation, if necessary and we have no way to know !!
433    if (gdk_pixbuf_animation_iter_advance(m_iter, NULL))
434    {
435        // start a new one-shot timer
436        int n = gdk_pixbuf_animation_iter_get_delay_time(m_iter);
437        if (n >= 0)
438            m_timer.Start(n, true);
439
440        gtk_image_set_from_pixbuf(GTK_IMAGE(m_widget),
441                                  gdk_pixbuf_animation_iter_get_pixbuf(m_iter));
442    }
443    else
444    {
445        // no need to update the m_widget yet
446        m_timer.Start(10, true);
447    }
448}
449
450#endif      // wxUSE_ANIMATIONCTRL
451