6c7e72bebffd19e842e1c27d040a594d5aa3ba8c
[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 sthemebg1=None
45 sthemebg2=None
46
47 # Load an icon
48 # Fall-back to default/blue if not found or name==None
49 def getIcon(name, iconsize):
50     # Default icon
51     idef='tasklaunch_default_application'
52
53     # If name==None then use the default icon
54     if name==None or name=='':
55         iname=idef
56     else:
57         iname=name
58
59     ico=getIconPath(iname, iconsize)
60
61     # If not found then use the default icon
62     if ico==None:
63         ico=getIconPath(idef, iconsize)
64
65     try:
66         ret=gtk.gdk.pixbuf_new_from_file_at_size(ico, iconsize, iconsize)
67     except:
68         # On error use the default icon
69         ico=getIconPath(idef, iconsize)
70         ret=gtk.gdk.pixbuf_new_from_file_at_size(ico, iconsize, iconsize)
71         print "Icon with unhandled format:", iname
72
73     return(ret)
74
75 class Icon(gobject.GObject):
76     def __init__(self, isconfig, config):
77         self.__gobject_init__()
78
79         self.isconfig=isconfig
80         self.config=config
81
82         self.name=None
83         self.icon=None
84         self.sicon=None
85         self.lastpress=0
86         self.ispressed=False
87
88         self.x=0
89         self.y=0
90
91         self.presstime=0.25
92
93         self.window=None
94
95         self.clickcount=0
96
97         self.angle=0
98
99         self.cached_icons={}
100
101         self.draw_queued=False
102
103     def timePressed(self):
104         """ return how much time a button is pressed """
105         dt=time.time() - self.lastpress
106
107         return(dt)
108
109     def reload(self):
110         self.clearAnimationCache()
111         self.clearBgCache()
112         self.invalidate()
113
114     def setApp(self, dt):
115         if dt==None:
116             self.name=None
117             self.icon=None
118             self.sicon=None
119         else:
120             self.name=dt['id']
121             self.icon=dt['icon2']
122             self.sicon=None
123         self.clearAnimationCache()
124         self.clearBgCache()
125         self.invalidate()
126
127     def clearAnimationCache(self):
128         self.cached_icons={}
129
130     def clearBgCache(self):
131         global iconbg, sthemebg1, sthemebg2
132
133         iconbg=None
134         sthemebg1=None
135         sthemebg2=None
136
137     def getSize(self):
138         return(self.config.getIconSizeFull())
139         # return(self.config.iconsize+self.config.iconspace)
140
141     def setAngle(self, angle):
142         """ Set the angle. Return True if the angle changed or False if it
143         didn't. The caller should invalidate the icon """
144
145         # The step in degrees
146         step=9
147
148         angle2=int(angle/step)*step
149
150         if angle2==self.angle:
151             return(False)
152
153         self.angle=angle2
154
155         # The caller should be responsible for redrawing.
156         # If we call invalidate() here there is the risk of having
157         # icons rotate individually using different angles
158 #       self.invalidate()
159
160         return(True)
161
162     def mkbg(self, t_pressed):
163         """ Create the background of the icon and cache it as a global
164         variable among all icons and widget instances """
165         global iconbg
166
167         if iconbg!=None and t_pressed<=0.001:
168             return(iconbg)
169
170         w=self.getSize()
171         s=cairo.ImageSurface(cairo.FORMAT_ARGB32, w, w)
172         cr0=cairo.Context(s)
173         cr=gtk.gdk.CairoContext(cr0)
174
175         cr.set_source_rgba(0.1, 0.1, 0.1, 1)
176         cr.set_line_width(5)
177
178         #if self.ispressed:
179         if t_pressed>0.001 and \
180             (t_pressed <= self.presstime or self.ispressed):
181             t=1.0 * min(t_pressed, self.presstime) / self.presstime
182             g=0.3+0.5*t
183             b=0.3+0.7*t
184             cr.set_source_rgba(0, g, b, 0.7)
185         else:
186             cr.set_source_rgba(0.3, 0.3, 0.3, 0.7)
187
188         x=0
189         y=0
190         x3=x + (self.config.iconmargin)
191         y3=y + (self.config.iconmargin)
192
193         r=10    # Radius
194         w=self.config.iconsize+(self.config.iconpadding*2)
195
196         cr.move_to(x3+r, y3)
197         cr.arc(x3+w-r,  y3+r,   r,          pi*1.5, pi*2)
198         cr.arc(x3+w-r,  y3+w-r, r,          0,      pi*0.5)
199         cr.arc(x3+r,    y3+w-r, r,          pi*0.5, pi)
200         cr.arc(x3+r,    y3+r,   r,          pi,     pi*1.5)
201
202         cr.stroke_preserve()
203         cr.fill()
204         cr.clip()
205         cr.paint()
206 #       cr.restore()
207
208         if t_pressed<0.001:
209             iconbg=s
210
211         return(s)
212
213     def get_sthemebg(self, pressed):
214         """ Return the theme's background icon as a surface. Cache it. """
215         global sthemebg1, sthemebg2
216
217         if not pressed and sthemebg1!=None:
218             return(sthemebg1)
219         if pressed and sthemebg2!=None:
220             return(sthemebg2)
221
222         fn="/etc/hildon/theme/images/"
223         if pressed:
224             fn+="ApplicationShortcutAppletPressed.png"
225         else:
226             fn+="ApplicationShortcutApplet.png"
227
228         w=self.config.iconsize + (self.config.iconpadding*2)
229         buf=gtk.gdk.pixbuf_new_from_file_at_size(fn, w, w)
230         s=cairo.ImageSurface(cairo.FORMAT_ARGB32, w, w)
231         cr0=cairo.Context(s)
232         cr=gtk.gdk.CairoContext(cr0)
233
234         cr.set_source_pixbuf(buf, 0, 0)
235         cr.paint()
236
237         if not pressed:
238             sthemebg1=s
239         else:
240             sthemebg2=s
241
242         return(s)
243
244     def get_sicon(self):
245         """ Return the icon as a surface. Cache it. """
246         if self.sicon!=None:
247             return(self.sicon)
248
249         w=self.config.iconsize
250         s=cairo.ImageSurface(cairo.FORMAT_ARGB32, w, w)
251         cr0=cairo.Context(s)
252         cr=gtk.gdk.CairoContext(cr0)
253
254         cr.set_source_pixbuf(self.icon, 0, 0)
255         cr.paint()
256
257         self.sicon=s
258
259         return(s)
260
261     def get_paint_icon(self):
262         """ Return the icon to paint as a surface. The icon is rotated
263         as needed. The result is cached. """
264         angle=self.angle
265
266         if self.timePressed() <= self.presstime or self.ispressed:
267             t=self.timePressed()
268             pressed=True
269         else:
270             t=0
271             pressed=False
272
273         if not pressed and self.cached_icons.has_key(angle):
274             return(self.cached_icons[angle])
275
276         w=self.config.getIconSizeFull()
277         s=cairo.ImageSurface(cairo.FORMAT_ARGB32, w, w)
278         cr0=cairo.Context(s)
279         cr=gtk.gdk.CairoContext(cr0)
280
281         # Paint the background
282         if self.config.getNoBg():
283             pass
284         elif self.config.getThemeBg():  # Use theme bg
285             s2=self.get_sthemebg(pressed)
286
287             # have in mind the size difference of iconsize+iconspace with
288             # the fixed themebgsize
289             #xy0=int((w-self.config.themebgsize)/2)
290             #xy0=int((w-self.config.iconsize)/2)
291             xy0=self.config.iconmargin
292
293             cr.save()
294             cr.set_source_surface(s2, xy0, xy0)
295             cr.paint()
296             cr.restore()
297         else:
298             s2=self.mkbg(t)
299             cr.save()
300             cr.set_source_surface(s2, 0, 0)
301             cr.paint()
302             cr.restore()
303
304         # If there is no icon then don't do anything more
305         if self.icon!=None:
306             # Get the icon as a surface (get_sicon() will cache the surface)
307             sicon=self.get_sicon()
308
309             # Width is the iconsize plus the empty border around the icon
310             #w=self.config.iconsize + self.config.iconspace
311
312             # This is used to locate the center of the surface
313             dx=int(w/2)
314
315             # This is the delta from the center where icons are drawn
316             dx2=int(self.config.iconsize/2)
317
318 #           cr.save()
319
320             # A transformation matrix with dx/dy set to point to the center
321             m=cairo.Matrix(1, 0, 0, 1, dx, dx)
322             cr.set_matrix(m)
323             # Transform degrees to rads
324             rot=-1 * pi * 2 * self.angle / 360
325             cr.rotate(rot)
326             # Draw the icon
327             cr.set_source_surface(sicon, -dx2, -dx2)    # Faster than pixbuf
328 #       cr.set_source_pixbuf(icon2, -dx2, -dx2)
329             cr.paint()
330
331 #           cr.restore()
332
333         if not pressed:
334             self.cached_icons[angle]=s
335
336         return(s)
337
338
339     def draw(self, cr, x, y):
340         self.draw_queued=False
341         self.x=x
342         self.y=y
343
344         if self.icon==None and not self.isconfig:
345             return
346
347         cr.save()
348         s=self.get_paint_icon()
349         cr.set_source_surface(s, x, y)
350         cr.paint()
351
352         cr.restore()
353
354         return(False)
355
356     def timerPressed(self):
357 #       if not self.ispressed:
358 #           return(False)
359
360         self.invalidate()
361
362         if self.timePressed()>self.presstime:
363             ret=False
364         else:
365             ret=True
366
367         return(ret)
368
369     def doPress(self):
370         # Double-time: time for pressed and time for not-pressed
371         if time.time() - self.lastpress > self.presstime*2:
372             self.clickcount=0
373
374         self.lastpress=time.time()
375         self.ispressed=True
376         gobject.timeout_add(20, self.timerPressed)
377
378     def doRelease(self):
379         dt=time.time() - self.lastpress
380         self.ispressed=False
381         self.invalidate()
382         if dt<=self.presstime:
383             self.clickcount+=1
384             if self.clickcount==1:
385                 self.emit('click')
386             elif self.clickcount==2:
387                 self.emit('double-click')
388             if self.clickcount==3:
389                 self.emit('tripple-click')
390                 self.clickcount=0
391         elif dt>self.presstime and dt<2:
392             self.emit('long-press')
393
394     def doCancel(self):
395         self.ispressed=False
396
397     def setWindow(self, window):
398         self.window=window
399
400     def invalidate(self, window=None):
401         if window==None:
402             window=self.window
403         else:
404             self.window=window
405
406         if window==None:
407             return
408
409         if self.draw_queued:
410 #           print "queued"
411             return
412
413         self.draw_queued=True
414         w=self.getSize()
415         rect=gdk.Rectangle(self.x, self.y, w, w)
416         gdk.Window.invalidate_rect(window, rect, True)
417
418 gobject.type_register(Icon)
419 signals=['click', 'double-click', 'tripple-click', 'long-press']
420 for s in signals:
421     gobject.signal_new(s, Icon, gobject.SIGNAL_RUN_FIRST,
422         gobject.TYPE_NONE, ())
423
424 # vim: set ts=8 sts=4 sw=4 noet formatoptions=r ai nocindent:
425