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