Change defaults to 'no longpress' and 'rotate icons individually'.
[drlaunch] / src / icon.py
1 #!/usr/bin/env python
2 # coding=UTF-8
3 #
4 # Copyright (C) 2010 Stefanos Harhalakis
5 #
6 # This file is part of wifieye.
7 #
8 # wifieye is free software: you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by
10 # the Free Software Foundation, either version 3 of the License, or
11 # (at your option) any later version.
12 #
13 # wifieye is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 # GNU General Public License for more details.
17 #
18 # You should have received a copy of the GNU General Public License
19 # along with wifieye.  If not, see <http://www.gnu.org/licenses/>.
20 #
21 # $Id: 0.py 2265 2010-02-21 19:16:26Z v13 $
22
23 __version__ = "$Id: 0.py 2265 2010-02-21 19:16:26Z v13 $"
24
25 import gtk
26 import gobject
27 import hildon
28 from hildondesktop import *
29 from gtk import gdk
30 from math import pi
31 import cairo
32 import time
33
34 from portrait import FremantleRotation
35 import launcher
36 from xdg.IconTheme import getIconPath
37
38
39 #import config
40 import apps
41
42 # Background surface for icons
43 iconbg=None
44
45 # Load an icon
46 # Fall-back to default/blue if not found or name==None
47 def getIcon(name, iconsize):
48     # Default icon
49     idef='tasklaunch_default_application'
50
51     # If name==None then use the default icon
52     if name==None or name=='':
53         iname=idef
54     else:
55         iname=name
56
57     ico=getIconPath(iname, iconsize)
58
59     # If not found then use the default icon
60     if ico==None:
61         ico=getIconPath(idef, iconsize)
62
63     ret=gtk.gdk.pixbuf_new_from_file_at_size(ico, iconsize, iconsize)
64
65     return(ret)
66
67 class Icon(gobject.GObject):
68     def __init__(self, isconfig, config):
69         self.__gobject_init__()
70
71         self.isconfig=isconfig
72         self.config=config
73
74         self.name=None
75         self.icon=None
76         self.sicon=None
77         self.lastpress=0
78         self.ispressed=False
79
80         self.x=0
81         self.y=0
82
83         self.presstime=0.25
84
85         self.window=None
86
87         self.clickcount=0
88
89         self.angle=0
90
91         self.cached_icons={}
92
93     def timePressed(self):
94         """ return how much time a button is pressed """
95         dt=time.time() - self.lastpress
96
97         return(dt)
98
99     def setApp(self, dt):
100         if dt==None:
101             self.name=None
102             self.icon=None
103             self.sicon=None
104         else:
105             self.name=dt['id']
106             self.icon=dt['icon2']
107             self.sicon=None
108         self.clearAnimationCache()
109         self.invalidate()
110
111     def clearAnimationCache(self):
112         self.cached_icons={}
113
114     def getSize(self):
115         return(self.config.iconsize+self.config.iconspace)
116
117     def setAngle(self, angle):
118         """ Set the angle. Return True if the angle changed or False if it
119         didn't. The caller should invalidate the icon """
120
121         # The step in degrees
122         step=9
123
124         angle2=int(angle/step)*step
125
126         if angle2==self.angle:
127             return(False)
128
129         self.angle=angle2
130
131         # The caller should be responsible for redrawing.
132         # If we call invalidate() here there is the risk of having
133         # icons rotate individually using different angles
134 #       self.invalidate()
135
136         return(True)
137
138     def mkbg(self, t_pressed):
139         """ Create the background of the icon and cache it as a global
140         variable among all icons and widget instances """
141         global iconbg
142
143         if iconbg!=None and t_pressed<=0.001:
144             return(iconbg)
145
146         w=self.config.iconsize + self.config.iconspace
147         s=cairo.ImageSurface(cairo.FORMAT_ARGB32, w, w)
148         cr0=cairo.Context(s)
149         cr=gtk.gdk.CairoContext(cr0)
150
151         cr.set_source_rgba(0.1, 0.1, 0.1, 1)
152         cr.set_line_width(5)
153
154         #if self.ispressed:
155         if t_pressed>0.001 and \
156             (t_pressed <= self.presstime or self.ispressed):
157             t=1.0 * min(t_pressed, self.presstime) / self.presstime
158             g=0.3+0.5*t
159             b=0.3+0.7*t
160             cr.set_source_rgba(0, g, b, 0.7)
161         else:
162             cr.set_source_rgba(0.3, 0.3, 0.3, 0.7)
163
164         x=0
165         y=0
166         x3=x + (self.config.iconspace/6)
167         y3=y + (self.config.iconspace/6)
168
169         r=10    # Radius
170         w=self.config.iconsize+(self.config.iconspace*2/3)
171
172         cr.move_to(x3+r, y3)
173         cr.arc(x3+w-r,  y3+r,   r,          pi*1.5, pi*2)
174         cr.arc(x3+w-r,  y3+w-r, r,          0,      pi*0.5)
175         cr.arc(x3+r,    y3+w-r, r,          pi*0.5, pi)
176         cr.arc(x3+r,    y3+r,   r,          pi,     pi*1.5)
177
178         cr.stroke_preserve()
179         cr.fill()
180         cr.clip()
181         cr.paint()
182 #       cr.restore()
183
184         if t_pressed<0.001:
185             iconbg=s
186
187         return(s)
188
189     def get_sicon(self):
190         """ Return the icon as a surface. Cache it. """
191         if self.sicon!=None:
192             return(self.sicon)
193
194         w=self.config.iconsize
195         s=cairo.ImageSurface(cairo.FORMAT_ARGB32, w, w)
196         cr0=cairo.Context(s)
197         cr=gtk.gdk.CairoContext(cr0)
198
199         cr.set_source_pixbuf(self.icon, 0, 0)
200         cr.paint()
201
202         self.sicon=s
203
204         return(s)
205
206     def get_paint_icon(self):
207         """ Return the icon to paint as a surface. The icon is rotated
208         as needed. The result is cached. """
209         angle=self.angle
210
211         if self.timePressed() <= self.presstime or self.ispressed:
212             t=self.timePressed()
213             pressed=True
214         else:
215             t=0
216             pressed=False
217
218         if not pressed and self.cached_icons.has_key(angle):
219             return(self.cached_icons[angle])
220
221         w=self.config.iconsize + self.config.iconspace
222         s=cairo.ImageSurface(cairo.FORMAT_ARGB32, w, w)
223         cr0=cairo.Context(s)
224         cr=gtk.gdk.CairoContext(cr0)
225
226         # Paint the background
227         s2=self.mkbg(t)
228         cr.save()
229         cr.set_source_surface(s2, 0, 0)
230         cr.paint()
231         cr.restore()
232
233         # If there is no icon then don't do anything more
234         if self.icon!=None:
235             # Get the icon as a surface (get_sicon() will cache the surface)
236             sicon=self.get_sicon()
237
238             # Width is the iconsize plus the empty border around the icon
239             #w=self.config.iconsize + self.config.iconspace
240
241             # This is used to locate the center of the surface
242             dx=int(w/2)
243
244             # This is the delta from the center where icons are drawn
245             dx2=int(self.config.iconsize/2)
246
247 #           cr.save()
248
249             # A transformation matrix with dx/dy set to point to the center
250             m=cairo.Matrix(1, 0, 0, 1, dx, dx)
251             cr.set_matrix(m)
252             # Transform degrees to rads
253             rot=-1 * pi * 2 * self.angle / 360
254             cr.rotate(rot)
255             # Draw the icon
256             cr.set_source_surface(sicon, -dx2, -dx2)    # Faster than pixbuf
257 #       cr.set_source_pixbuf(icon2, -dx2, -dx2)
258             cr.paint()
259
260 #           cr.restore()
261
262         if not pressed:
263             self.cached_icons[angle]=s
264
265         return(s)
266
267
268     def draw(self, cr, x, y):
269         self.draw_queued=False
270         self.x=x
271         self.y=y
272
273         if self.icon==None and not self.isconfig:
274             return
275
276         cr.save()
277         s=self.get_paint_icon()
278         cr.set_source_surface(s, x, y)
279         cr.paint()
280
281         cr.restore()
282
283         return(False)
284
285     def timerPressed(self):
286 #       if not self.ispressed:
287 #           return(False)
288
289         self.invalidate()
290
291         if self.timePressed()>self.presstime:
292             ret=False
293         else:
294             ret=True
295
296         return(ret)
297
298     def doPress(self):
299         # Double-time: time for pressed and time for not-pressed
300         if time.time() - self.lastpress > self.presstime*2:
301             self.clickcount=0
302
303         self.lastpress=time.time()
304         self.ispressed=True
305         gobject.timeout_add(20, self.timerPressed)
306
307     def doRelease(self):
308         dt=time.time() - self.lastpress
309         self.ispressed=False
310         self.invalidate()
311         if dt<=self.presstime:
312             self.clickcount+=1
313             if self.clickcount==1:
314                 self.emit('click')
315             elif self.clickcount==2:
316                 self.emit('double-click')
317             if self.clickcount==3:
318                 self.emit('tripple-click')
319                 self.clickcount=0
320         elif dt>self.presstime and dt<2:
321             self.emit('long-press')
322
323     def doCancel(self):
324         self.ispressed=False
325
326     def setWindow(self, window):
327         self.window=window
328
329     def invalidate(self, window=None):
330         if window==None:
331             window=self.window
332         else:
333             self.window=window
334
335         if window==None:
336             return
337
338         if self.draw_queued:
339 #           print "queued"
340             return
341
342         self.draw_queued=True
343         w=self.config.iconsize + self.config.iconspace
344         rect=gdk.Rectangle(self.x, self.y, w, w)
345         gdk.Window.invalidate_rect(window, rect, True)
346
347 gobject.type_register(Icon)
348 signals=['click', 'double-click', 'tripple-click', 'long-press']
349 for s in signals:
350     gobject.signal_new(s, Icon, gobject.SIGNAL_RUN_FIRST,
351         gobject.TYPE_NONE, ())
352
353 # vim: set ts=8 sts=4 sw=4 noet formatoptions=r ai nocindent:
354