Skip to content

Instantly share code, notes, and snippets.

@nzjrs
Created June 27, 2009 04:55
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nzjrs/136900 to your computer and use it in GitHub Desktop.
Save nzjrs/136900 to your computer and use it in GitHub Desktop.
Animated Gtk+ StatusIcon
#!/usr/bin/env python
"""
Animated Gtk+ StatusIcon
Given an icon name or path, animates the icon in a number of ways, rotating,
shrinking, saturating it, etc.
Inspiration from:
http://groups.google.com/group/sage-devel/browse_thread/thread/f9aeba22ac171082
"""
import math
import os
import sys
import gobject
import gtk
class AnimatedTrayIcon(gtk.StatusIcon):
__gsignals__ = {
"animation-finished" : (
gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, []),
}
DEFAULT_TIME = 2
def __init__(self, icon_path=None, icon_name=None):
gtk.StatusIcon.__init__(self)
if icon_name:
#always set from path, it seems to get better results.....
try:
icon_path = gtk.icon_theme_get_default().lookup_icon(icon_name, 48, gtk.ICON_LOOKUP_FORCE_SVG).get_filename()
except AttributeError:
icon_path = gtk.icon_theme_get_default().lookup_icon("image-missing", 48, gtk.ICON_LOOKUP_FORCE_SVG).get_filename()
if not icon_path:
raise Exception("Invalid Icon")
self._icon_path = icon_path
self._repeat = False
def _animation_finished(self):
self.emit("animation-finished")
def _update_animating_pixbuf(self):
self.set_from_file(self._icon_path)
if self.props.storage_type == gtk.IMAGE_PIXBUF:
self._pixbuf = self.get_pixbuf()
else:
raise Exception("Not Supported")
self._w = self._pixbuf.get_width()
self._h = self._pixbuf.get_height()
# (re)Set various animation parameters.
def _initialize_animation(self, seconds=DEFAULT_TIME):
self._update_animating_pixbuf()
if seconds < 0:
seconds=self.DEFAULT_TIME
self._repeat = True
else:
self._repeat = False
self._t = seconds
self._fps = 24.0
self._dt_ms = int(1000 / self._fps)
self._frames = int(self._t * self._fps)
# Transform the icon via self.* and a given function. Afterwards,
# restore the original icon. Note: This is not re-entrant, so
# multiple simultaneous calls may be entertaining.
def _start_animation(self, func):
self.i = 0
def advance_frame():
self.set_from_pixbuf(func(self.i))
if self.i < self._frames:
self.i += 1
return True
elif self._repeat:
self.i = 0
return True
else:
self._animation_finished()
return False
gobject.timeout_add(self._dt_ms, advance_frame)
def set_from_first_frame(self):
self.set_from_file(self._icon_path)
def is_animating(self):
return self.i < self._frames or self._repeat
def stop_animation(self):
self._repeat = False
def toggle_blinking(self):
if self.get_blinking():
self.set_blinking(False)
else:
self.set_blinking(True)
def blink(self, seconds=DEFAULT_TIME):
if not self.get_blinking():
self.set_blinking(True)
gobject.timeout_add(int(seconds * 1000), self.toggle_blinking)
def squeeze_vertically(self, seconds=DEFAULT_TIME):
self._initialize_animation(seconds)
def func(f):
h = max( 1, int( self._h * ( 1.0 - math.sin( f / (self._fps / math.pi) )**2 ) ) )
return self._pixbuf.scale_simple(self._w, h, gtk.gdk.INTERP_BILINEAR)
self._start_animation(func)
def squeeze_horizontally(self, seconds=DEFAULT_TIME):
self._initialize_animation(seconds)
def func(f):
w = max( 1, int( self._w * ( 1.0 - math.sin( f / (self._fps / math.pi) )**2 ) ) )
return self._pixbuf.scale_simple(w, self._h, gtk.gdk.INTERP_BILINEAR)
self._start_animation(func)
def rotate_horizontally(self, seconds=DEFAULT_TIME):
self._initialize_animation(seconds)
self._flipped = False
self._last_w = self._w
def func(f):
scale = ( 1.0 - math.sin( f / (self._fps / math.pi) )**2 )
new_w = max( 1, int( self._w * scale ) )
new = self._pixbuf.copy()
new.fill(0)
if self._last_w != 1 and new_w == 1:
self._flipped = not self._flipped
if self._flipped:
source = self._pixbuf.flip(horizontal=True)
else:
source = self._pixbuf
source.composite(
dest=new,
dest_x=0,
dest_y=0,
dest_width=self._w,
dest_height=self._h,
offset_x=(self._w/2) - (new_w/2),
offset_y=0,
scale_x=scale,
scale_y=1.0,
interp_type=gtk.gdk.INTERP_BILINEAR,
overall_alpha=255)
self._last_w = new_w
return new
self._start_animation(func)
def shrink(self, seconds=DEFAULT_TIME):
self._initialize_animation(seconds)
def func(f):
x = max( 1, int( self._w * ( 1.0 - math.sin( f / (self._fps / math.pi) )**2 ) ) )
return self._pixbuf.scale_simple(x, x, gtk.gdk.INTERP_BILINEAR)
self._start_animation(func)
def saturate(self, seconds=DEFAULT_TIME, amplitude=0.8):
self._initialize_animation(seconds)
self._pixbuf_mod = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, True, 8, self._w, self._h)
phase = math.asin( 1.0 / amplitude - 1.0 )
def func(f):
sat = amplitude * ( 1.0 + math.sin( 1.0 * f / (self._fps / math.pi) + phase ) )
self._pixbuf.saturate_and_pixelate(self._pixbuf_mod, sat, False)
return self._pixbuf_mod
self._start_animation(func)
if __name__ == '__main__':
def on_stop_clicked(btn, _ti):
_ti.stop_animation()
def on_animation_finished(_ti):
_ti.set_from_icon_name("dialog-error")
_ti.blink()
#ti = AnimatedTrayIcon(icon_path="/home/john/Desktop/conduit.svg")
ti = AnimatedTrayIcon(icon_name="go-home")
ti.set_from_icon_name("dialog-information")
ti.connect("animation-finished", on_animation_finished)
w = gtk.Window()
w.connect('delete-event', lambda *a: gtk.main_quit() )
hb = gtk.HBox()
for name in ("blink", "rotate_horizontally", "squeeze_horizontally", "squeeze_vertically", "shrink", "saturate"):
func = getattr(ti, name)
btn = gtk.Button(name)
btn.connect("clicked", lambda b,f: f(-1), func)
hb.pack_start(btn)
btn = gtk.Button("STOP")
btn.connect("clicked", on_stop_clicked, ti)
hb.pack_start(btn)
w.add(hb)
w.show_all()
gtk.main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment