v0.7
[drlaunch] / src / icon.py
index f050463..f6a6099 100755 (executable)
@@ -1,6 +1,6 @@
 #!/usr/bin/env python
 # coding=UTF-8
-# 
+#
 # Copyright (C) 2010 Stefanos Harhalakis
 #
 # This file is part of wifieye.
@@ -36,24 +36,44 @@ import launcher
 from xdg.IconTheme import getIconPath
 
 
-import config
+#import config
 import apps
 
-def getIcon(name):
-    ico=getIconPath(name, config.iconsize)
-    ret=gtk.gdk.pixbuf_new_from_file_at_size(ico, config.iconsize,
-       config.iconsize)
+# Background surface for icons
+iconbg=None
+
+# Load an icon
+# Fall-back to default/blue if not found or name==None
+def getIcon(name, iconsize):
+    # Default icon
+    idef='tasklaunch_default_application'
+
+    # If name==None then use the default icon
+    if name==None or name=='':
+       iname=idef
+    else:
+       iname=name
+
+    ico=getIconPath(iname, iconsize)
+
+    # If not found then use the default icon
+    if ico==None:
+       ico=getIconPath(idef, iconsize)
+
+    ret=gtk.gdk.pixbuf_new_from_file_at_size(ico, iconsize, iconsize)
 
     return(ret)
 
 class Icon(gobject.GObject):
-    def __init__(self, isconfig):
+    def __init__(self, isconfig, config):
        self.__gobject_init__()
 
        self.isconfig=isconfig
+       self.config=config
 
        self.name=None
        self.icon=None
+        self.sicon=None
        self.lastpress=0
        self.ispressed=False
 
@@ -66,8 +86,9 @@ class Icon(gobject.GObject):
 
        self.clickcount=0
 
-    def __del__(self):
-       print "del"
+       self.angle=0
+
+       self.cached_icons={}
 
     def timePressed(self):
        """ return how much time a button is pressed """
@@ -76,38 +97,77 @@ class Icon(gobject.GObject):
        return(dt)
 
     def setApp(self, dt):
-       self.name=dt['id']
-       self.icon=dt['icon2']
+       if dt==None:
+           self.name=None
+           self.icon=None
+           self.sicon=None
+       else:
+           self.name=dt['id']
+           self.icon=dt['icon2']
+           self.sicon=None
+       self.clearAnimationCache()
        self.invalidate()
 
+    def clearAnimationCache(self):
+       self.cached_icons={}
+
     def getSize(self):
-       return(config.iconsize+config.iconspace)
+       return(self.config.iconsize+self.config.iconspace)
 
-    def draw(self, cr, x, y, mode):
-       #print "draw", x, y, mode
-       self.x=x
-       self.y=y
+    def setAngle(self, angle):
+       """ Set the angle. Return True if the angle changed or False if it
+       didn't. The caller should invalidate the icon """
 
-       if self.icon==None and not self.isconfig:
-           return
+       # The step in degrees
+       step=9
 
-       cr.save()
-       cr.set_source_rgba(0.1, 0.1, 0.1, 1)
+       angle2=int(angle/step)*step
+
+       if angle2==self.angle:
+           return(False)
+
+       self.angle=angle2
+
+       # The caller should be responsible for redrawing.
+       # If we call invalidate() here there is the risk of having
+       # icons rotate individually using different angles
+#      self.invalidate()
+
+       return(True)
+
+    def mkbg(self, t_pressed):
+       """ Create the background of the icon and cache it as a global
+       variable among all icons and widget instances """
+        global iconbg
+
+        if iconbg!=None and t_pressed<=0.001:
+            return(iconbg)
+
+        w=self.config.iconsize + self.config.iconspace
+       s=cairo.ImageSurface(cairo.FORMAT_ARGB32, w, w)
+       cr0=cairo.Context(s)
+       cr=gtk.gdk.CairoContext(cr0)
+
+       cr.set_source_rgba(0.1, 0.1, 0.1, 1)
        cr.set_line_width(5)
 
-       if self.ispressed:
-           t=1.0 * min(self.timePressed(), self.presstime) / self.presstime
+       #if self.ispressed:
+       if t_pressed>0.001 and \
+            (t_pressed <= self.presstime or self.ispressed):
+           t=1.0 * min(t_pressed, self.presstime) / self.presstime
            g=0.3+0.5*t
            b=0.3+0.7*t
            cr.set_source_rgba(0, g, b, 0.7)
        else:
            cr.set_source_rgba(0.3, 0.3, 0.3, 0.7)
 
-       x3=x + (config.iconspace/6)
-       y3=y + (config.iconspace/6)
+        x=0
+        y=0
+       x3=x + (self.config.iconspace/6)
+       y3=y + (self.config.iconspace/6)
 
        r=10    # Radius
-       w=config.iconsize+(config.iconspace*2/3)
+       w=self.config.iconsize+(self.config.iconspace*2/3)
 
        cr.move_to(x3+r, y3)
        cr.arc(x3+w-r,  y3+r,   r,          pi*1.5, pi*2)
@@ -119,32 +179,112 @@ class Icon(gobject.GObject):
        cr.fill()
        cr.clip()
        cr.paint()
-       cr.restore()
+#      cr.restore()
 
-       if self.icon==None:
-           return
+        if t_pressed<0.001:
+            iconbg=s
+
+        return(s)
+
+    def get_sicon(self):
+       """ Return the icon as a surface. Cache it. """
+       if self.sicon!=None:
+           return(self.sicon)
+
+        w=self.config.iconsize
+       s=cairo.ImageSurface(cairo.FORMAT_ARGB32, w, w)
+       cr0=cairo.Context(s)
+       cr=gtk.gdk.CairoContext(cr0)
+
+       cr.set_source_pixbuf(self.icon, 0, 0)
+       cr.paint()
 
-       icon=self.icon
+       self.sicon=s
 
-       if mode=='l':
-           icon2=icon
+       return(s)
+
+    def get_paint_icon(self):
+       """ Return the icon to paint as a surface. The icon is rotated
+       as needed. The result is cached. """
+       angle=self.angle
+
+       if self.timePressed() <= self.presstime or self.ispressed:
+            t=self.timePressed()
+           pressed=True
        else:
-           icon2=icon.rotate_simple(gdk.PIXBUF_ROTATE_COUNTERCLOCKWISE)
+            t=0
+           pressed=False
+
+       if not pressed and self.cached_icons.has_key(angle):
+           return(self.cached_icons[angle])
+
+        w=self.config.iconsize + self.config.iconspace
+       s=cairo.ImageSurface(cairo.FORMAT_ARGB32, w, w)
+       cr0=cairo.Context(s)
+       cr=gtk.gdk.CairoContext(cr0)
+
+       # Paint the background
+        s2=self.mkbg(t)
+       cr.save()
+       cr.set_source_surface(s2, 0, 0)
+       cr.paint()
+       cr.restore()
+
+       # If there is no icon then don't do anything more
+       if self.icon!=None:
+           # Get the icon as a surface (get_sicon() will cache the surface)
+           sicon=self.get_sicon()
+
+           # Width is the iconsize plus the empty border around the icon
+           #w=self.config.iconsize + self.config.iconspace
+
+           # This is used to locate the center of the surface
+           dx=int(w/2)
+
+           # This is the delta from the center where icons are drawn
+           dx2=int(self.config.iconsize/2)
+
+#          cr.save()
+
+           # A transformation matrix with dx/dy set to point to the center
+           m=cairo.Matrix(1, 0, 0, 1, dx, dx)
+           cr.set_matrix(m)
+           # Transform degrees to rads
+           rot=-1 * pi * 2 * self.angle / 360
+           cr.rotate(rot)
+           # Draw the icon
+           cr.set_source_surface(sicon, -dx2, -dx2)    # Faster than pixbuf
+#      cr.set_source_pixbuf(icon2, -dx2, -dx2)
+           cr.paint()
+
+#          cr.restore()
+
+       if not pressed:
+           self.cached_icons[angle]=s
+
+       return(s)
+
+
+    def draw(self, cr, x, y):
+       self.draw_queued=False
+       self.x=x
+       self.y=y
+
+       if self.icon==None and not self.isconfig:
+           return
 
        cr.save()
-       x3=x + (config.iconspace/2)
-       y3=y + (config.iconspace/2)
-       cr.set_source_pixbuf(icon2, x3, y3)
+       s=self.get_paint_icon()
+       cr.set_source_surface(s, x, y)
        cr.paint()
+
        cr.restore()
 
        return(False)
 
     def timerPressed(self):
-       #print "timer"
-
-       if not self.ispressed:
-           return(False)
+#      if not self.ispressed:
+#          return(False)
 
        self.invalidate()
 
@@ -156,7 +296,6 @@ class Icon(gobject.GObject):
        return(ret)
 
     def doPress(self):
-       #print "doPress()"
        # Double-time: time for pressed and time for not-pressed
        if time.time() - self.lastpress > self.presstime*2:
            self.clickcount=0
@@ -164,45 +303,44 @@ class Icon(gobject.GObject):
        self.lastpress=time.time()
        self.ispressed=True
        gobject.timeout_add(20, self.timerPressed)
-       #print "doPress() end"
 
     def doRelease(self):
-       print "doRelease()"
        dt=time.time() - self.lastpress
        self.ispressed=False
        self.invalidate()
        if dt<=self.presstime:
            self.clickcount+=1
            if self.clickcount==1:
-               print "click"
                self.emit('click')
            elif self.clickcount==2:
-               print "double-click"
                self.emit('double-click')
            if self.clickcount==3:
-               print "tripple-click"
                self.emit('tripple-click')
                self.clickcount=0
        elif dt>self.presstime and dt<2:
-           print "long-press"
            self.emit('long-press')
 
     def doCancel(self):
-       print "doCancel()"
        self.ispressed=False
 
+    def setWindow(self, window):
+       self.window=window
+
     def invalidate(self, window=None):
        if window==None:
            window=self.window
        else:
            self.window=window
-       
+
        if window==None:
            return
 
-       w=config.iconsize + config.iconspace
+       if self.draw_queued:
+           print "queued"
+           return
+       self.draw_queued=True
+       w=self.config.iconsize + self.config.iconspace
        rect=gdk.Rectangle(self.x, self.y, w, w)
-       #print "rect", self.x, self.y, w, w
        gdk.Window.invalidate_rect(window, rect, True)
 
 gobject.type_register(Icon)