CSSU's portrait mode support - WIP
[drlaunch] / drlaunch / src / icongrid.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 #from xdg.IconTheme import getIconPath
36
37 from config import dump
38 import apps
39 import icon
40 from icon import Icon
41 from icons import Icons
42
43 #def getIcon(name, iconsize):
44 #    ico=getIconPath(name, iconsize)
45 #    ret=gtk.gdk.pixbuf_new_from_file_at_size(ico, iconsize, iconsize)
46 #
47 #    return(ret)
48
49 # IconGrid is the main class that implements tha drawing of the grid
50 # However, since it will be used both by the desktop plugin and the
51 # configuration window, it cannot derive either gtk.Widget or HomePluginItem.
52 # It is created here in a way that will allow it to work on both cases
53 # and it is inherited by appropriate classes (one for the plugin and one
54 # for the config widget)
55
56 #class IconGrid(gtk.Widget, FremantleRotation):
57 class IconGrid:
58     def __init__(self, isconfig=False):
59 #       self.__gobject_init__()
60 #       gtk.Widget.__init__(self)
61
62         self.init_done=False
63
64         self.size=(0,0)
65
66         self.isconfig=isconfig
67
68         self.icons=None
69         self.lasticon=None  # The last icon that got selected
70
71         self.draw_pending=False
72
73         self.mode=None
74
75         # If this is False then animations are forcefully disabled
76         self.do_animations=True
77
78         self.angle_timer_start=0
79
80         # Duration of the rotation effect
81         self.rotation_time=0.8
82
83         # Whether we're in CSSU's h-d portrait mode
84         self.cssuportrait=False
85
86         # Force modechange, even when mode doesn't change
87         self.force_modechange=False
88
89 #       print "ig-init"
90
91 #    def __del__(self):
92 #       print "ig-del"
93
94     def do_realize(self, config):
95 #       print "ig-realize"
96         self.config=config
97
98         if self.icons!=None:
99             print
100             print
101             print
102             print "WTF??????????????????????"
103             print
104             print
105             print
106
107         self.icons=Icons(self.isconfig, self.config)
108         #print "self:", self
109         #self.icons.set_parent(self)
110         self.setMode('l')
111         self.setSize(config.getMaxSize())
112         self.reloadIcons()
113
114     def do_unrealize(self):
115 #       print "ig-unrealize"
116         self.config=None
117         self.icons.finish()
118         self.icons=None
119         self.lasticon=None
120
121     def connect(self, what, *args):
122         if what in Icon.gsignals.keys():
123             ret=self.icons.connect(what, *args)
124         else:
125             ret=gobject.GObject.connect(self, what, *args)
126             #ret=super(IconGrid, self).connect(what, *args)
127
128         return(ret)
129
130     def setSize(self, size):
131         self.size=size
132         self.icons.setSize(size)
133
134     def getSize(self):
135         ret=self.icons.getSize()
136         return(ret)
137
138     def setCSSUPortrait(self, cssuportrait):
139         if self.cssuportrait==cssuportrait:
140             # print "same portrait"
141             return
142
143         self.cssuportrait=cssuportrait
144         self.force_modechange=True
145
146     # TODO: Remove default value from cssuportrait
147     def setMode(self, mode):
148         if self.mode==mode and not self.force_modechange:
149             # print "same mode"
150             return
151
152         self.mode=mode
153         self.force_modechange=False
154
155         if not isinstance(self, gtk.Widget):
156             return
157
158         do_draw=False
159
160         try:
161             v=self.get_property('is-on-current-desktop')
162             if v:
163                 do_draw=True
164             else:
165                 self.draw_pending=True
166         except TypeError:
167             do_draw=True
168
169         if do_draw and self.config.getAnimate() and self.do_animations \
170             and not self.cssuportrait:
171             #self.queue_draw()
172             # Don't start another timer
173             # Instead adjust the time start to produce a nice effect ;-)
174             if self.angle_timer_start==0:
175                 self.angle_timer_start=time.time()
176                 gobject.timeout_add(20, self.timerAngle)
177             else:
178                 dt=time.time()-self.angle_timer_start
179                 da=90.0*dt/self.rotation_time
180
181                 da2=90.0-da
182                 dt2=da2*self.rotation_time/90.0
183                 self.angle_timer_start=time.time()-dt2
184         else:
185             if self.mode=='l' or self.cssuportrait:
186                 self.setAngle(0)
187             else:
188                 self.setAngle(90)
189
190             if do_draw:
191                 self.queue_draw()
192
193     def disableAnimation(self):
194         self.do_animations=False
195
196     def enableAnimation(self):
197         self.do_animations=True
198
199     def setAnimationEnable(self, value):
200         if value:
201             self.enableAnimation()
202         else:
203             self.disableAnimation()
204
205     def timerAngle(self):
206         if self.angle_timer_start==0:
207             self.angle_timer_start=time.time()-0.05
208
209         dt=time.time()-self.angle_timer_start
210
211         da=90.0*dt/self.rotation_time
212
213         if self.mode=='l':
214             angle=90-da
215         else:
216             angle=da
217
218         if angle>=90:
219             angle=90
220             ret=False
221         elif angle<0:
222             angle=0
223             ret=False
224         else:
225             ret=True
226
227         if self.setAngle(angle):
228             self.queue_draw()
229
230         if ret==False:
231             self.angle_timer_start=0
232
233         return(ret)
234
235     def iconAt(self, x, y):
236         """ Get icon at coordinates x,y. X and Y are in pixels """
237
238         w=self.config.getIconSizeFull()
239
240         if self.cssuportrait:
241             x2=int(y / w)
242             y2=self.size[1] - int(x/w) - 1
243         elif self.mode=='l' or self.config.getIndiv():
244             x2=int(x / w)
245             y2=int(y / w)
246         else:
247             x2=self.size[1] - int(y/w) - 1
248             y2=int(x/w)
249
250 #       print "%d,%d -> %d,%d" % (int(x/w), int(y/w), x2, y2), \
251 #           self.cssuportrait, self.mode
252         ret=self.get(x2,y2)
253
254         return(ret)
255
256     def get(self, x, y):
257         ret=self.icons.get(x,y)
258
259         return(ret)
260
261     def _draw(self, cr, event):
262         self.draw_pending=False
263
264         w=self.config.getIconSizeFull()
265         for x,y in self.icons:
266             x2=(self.size[1]-y-1) * self.config.getIconSizeFull()
267             y2=x * self.config.getIconSizeFull()
268             if self.cssuportrait:
269                 # What we do here is:
270                 # We map the x,y of icons to x,y on the screen.
271                 # x,y of icons is the x,y as if there is no desktop rotation
272                 # and we're in landscape mode
273                 #
274                 # After that, iconAt() will work properly. It's a kind of
275                 # double effort but it works nice
276                 x2=(self.size[1]-y-1) * self.config.getIconSizeFull()
277                 y2=x * self.config.getIconSizeFull()
278             elif not self.cssuportrait and \
279                 (self.mode=='l' or self.config.getIndiv()):
280                 #x2=x * (self.config.iconsize + self.config.iconspace)
281                 #y2=y * (self.config.iconsize + self.config.iconspace)
282                 x2=x * self.config.getIconSizeFull()
283                 y2=y * self.config.getIconSizeFull()
284             else:
285                 #x2=y * (self.config.iconsize + self.config.iconspace)
286                 #y2=(self.size[1]-x-1) * \
287                 #       (self.config.iconsize + self.config.iconspace)
288                 x2=y * self.config.getIconSizeFull()
289                 y2=(self.size[1]-x-1) * self.config.getIconSizeFull()
290
291             # Only repaint the needed icons
292             rect=gdk.Rectangle(x2, y2, w, w)
293             t=rect.intersect(event.area)
294             if t.width==0 and t.height==0 and False:
295                 continue
296
297             ico=self.icons.get(x,y)
298             if ico!=None:
299                 ico.draw(cr, x2, y2)
300             else:
301                 print "No icon for %d, %d (%d, %d)!" % (x,y, x2, y2)
302
303     def setAngle(self, angle):
304         """ Return True/False indicating that angle has changed """
305         ret=False
306
307         #print "angle:", angle, self.mode
308
309         for x,y in self.icons:
310             ic=self.icons.get(x,y)
311             #ic=self.iconAt(x,y)
312             if ic.setAngle(angle):
313                 ret=True
314
315         return(ret)
316
317     def clearAnimationCache(self):
318         """ Clear animation cache, freeing memory """
319         for x,y in self.icons:
320             ic=self.icons.get(x,y)
321             #ic=self.iconAt(x,y)
322             ic.clearAnimationCache()
323
324     def clearBgCache(self):
325         """ Clear backgrounds cache """
326         for x,y in self.icons:
327             ic=self.icons.get(x,y)
328             #ic=self.iconAt(x,y)
329             ic.clearBgCache()
330
331     def do_expose_event(self, event):
332         cr=self.window.cairo_create()
333
334         cr.rectangle(event.area.x, event.area.y,
335             event.area.width, event.area.height)
336
337         cr.clip()
338
339         if not self.init_done:
340             self.icons.setWindow(self.window)
341             self.init_done=True
342
343 #       print "expose:", event.area.x, event.area.y, event.area.width, \
344 #           event.area.height
345
346         self._draw(cr, event)
347
348     def setLastIcon(self, icon):
349         if icon==self.lasticon:
350             return
351
352         if self.lasticon!=None:
353             self.lasticon.doCancel()
354             self.lasticon.invalidate(self.window)
355         self.lasticon=icon
356
357     def do_button_press_event(self, event):
358         icon=self.iconAt(event.x, event.y)
359         if icon==None:
360             return
361 #       rect=gdk.Rectangle(event.x,event.y,1,1)
362 #       rect=gdk.Rectangle(0, 0, 100, 100)
363         icon.doPress()
364         icon.invalidate(self.window)
365         self.setLastIcon(icon)
366
367 #       gdk.Window.invalidate_rect(self.window, rect, True)
368
369         return(True)
370
371     def do_button_release_event(self, event):
372         if self.lasticon!=None:
373             self.lasticon.invalidate(self.window)
374             self.lasticon.doRelease()
375
376         self.setLastIcon(None)
377
378         return(True)
379
380     def do_leave_notify_event(self, event):
381         self.setLastIcon(None)
382         return(True)
383
384     def do_pproperty_notify_event(self, event):
385         icon=self.iconAt(event.x, event.y)
386         if icon==None:
387             return
388         icon.doCancel()
389         icon.invalidate(self.window)
390         return(True)
391
392     def do_motion_notify_event(self, event):
393         icon=self.iconAt(event.x, event.y)
394         if self.lasticon==icon:
395             return(True)
396
397         self.setLastIcon(None)
398         icon.doCancel()
399         icon.invalidate(self.window)
400         return(True)
401
402     def do_button_press_event_old(self, event):
403         if event.type==gdk.BUTTON_PRESS:
404             if self.mode=='p':
405                 self.setMode('l')
406             else:
407                 self.setMode('p')
408             self.queue_draw()
409         return True
410
411     # For debugging
412     def do_event1(self, event):
413         print "event:", event, event.type
414
415     def reloadIcons(self):
416         self.icons.load()
417         self.lasticon=None
418
419 #    def on_orientation_changed(self, orientation):
420 #       print "orch:", orientation
421 #       o=orientation[0]
422 #       self.setMode(o)
423
424 class IconGridWidget(IconGrid, gtk.Widget):
425     def __init__(self, isconfig, config, animation=True):
426         IconGrid.__init__(self, isconfig)
427         gtk.Widget.__init__(self)
428
429         # This must be called before do_realize
430         self.setAnimationEnable(animation)
431
432         self.config=config
433
434         IconGrid.do_realize(self, self.config)
435
436         self.setSize(self.size)
437
438 #       print "igw-init"
439
440 #    def __del__(self):
441 #       print "igw-del"
442
443     def setSize(self, size):
444         IconGrid.setSize(self, size)
445
446         w=self.size[0] * self.config.getIconSizeFull()
447         h=self.size[1] * self.config.getIconSizeFull()
448
449         self.set_size_request(w, h)
450
451     def reconfig(self):
452         self.clearBgCache()
453         self.clearAnimationCache()
454         self.reloadIcons()
455         self.setSize(self.size)
456         self.icons.resizeMax()
457         self.queue_draw()
458
459     def do_realize(self):
460 #       print "igw-realize"
461         screen=self.get_screen()
462         self.set_colormap(screen.get_rgba_colormap())
463         self.set_app_paintable(True)
464
465         self.set_flags(self.flags() | gtk.REALIZED)
466
467         self.window=gdk.Window(
468             self.get_parent_window(),
469             width=self.allocation.width,
470             height=self.allocation.height,
471             window_type=gdk.WINDOW_CHILD,
472             wclass=gdk.INPUT_OUTPUT,
473             event_mask=self.get_events() | gdk.EXPOSURE_MASK
474                 | gdk.BUTTON_PRESS_MASK 
475                 | gdk.BUTTON_RELEASE_MASK 
476                 | gdk.BUTTON_MOTION_MASK
477                 | gdk.POINTER_MOTION_MASK
478                 | gdk.POINTER_MOTION_HINT_MASK 
479                 | gdk.ENTER_NOTIFY_MASK
480                 | gdk.LEAVE_NOTIFY_MASK )
481
482         self.window.set_user_data(self)
483
484         # No matter what the pygtk widget demo says, this is NOT CORRECT!!!
485         # Don't call style.attach(self.window) or else the program will crash
486         # after some time! It seems that there is a style already.
487         # If we want to use the style the we use get_style() instead.
488         # This one was very hard to solve. Thanks to gnome2-globalmenu guys.
489         # It was solved by looking the commit 2666:
490         # see:
491         # svn diff -r2665:2666 http://gnome2-globalmenu.googlecode.com/svn/trunk
492         # which solved cse 490:
493         # http://code.google.com/p/gnome2-globalmenu/issues/detail?id=490
494 #       self.style.attach(self.window)
495         style=self.get_style()
496
497 #       style.set_background(self.window, gtk.STATE_NORMAL)
498         self.window.move_resize(*self.allocation)
499
500 #       self.pixmap, mask = gtk.gdk.pixmap_create_from_xpm_d(
501 #             self.window, self.style.bg[gtk.STATE_NORMAL], STAR_PIXMAP)
502         
503 #       self.gc = self.style.fg_gc[gtk.STATE_NORMAL]
504
505         #HomePluginItem.do_realize(self)
506
507 #       screen=self.get_screen()
508 #       self.set_colormap(screen.get_rgba_colormap())
509 #       self.set_app_paintable(True)
510
511     def do_unrealize(self):
512 #       print "igw-unrealize", self
513         IconGrid.do_unrealize(self)
514         self.window.destroy()
515         self.window.set_user_data(None)
516
517     def do_expose_event(self, event):
518         cr=self.window.cairo_create()
519
520         cr.rectangle(event.area.x, event.area.y,
521             event.area.width, event.area.height)
522         cr.clip()
523
524         style=self.get_style()
525         col=style.bg[gtk.STATE_NORMAL]
526         cr.set_source_color(col)
527         cr.paint()
528
529         IconGrid.do_expose_event(self, event)
530
531 #gobject.type_register(IconGrid)
532 gobject.type_register(IconGridWidget)
533
534
535 # vim: set ts=8 sts=4 sw=4 noet formatoptions=r ai nocindent:
536