Extend configuration to cover wifihood view
[wifihood] / wifiscanner / wifiview
1 #!/usr/bin/env python
2
3 import gtk
4 import gobject
5 try :
6     import hildon
7 except :
8     hildon = False
9
10 import urllib2
11 import math
12
13 import os
14
15 import wifimap.config
16
17 class mapWidget ( gtk.Image ) :
18
19   def __init__ ( self , config ) :
20
21     self.conf = config
22     gtk.Image.__init__(self)
23
24     # Maximum width should be 800, but actually gets reduced
25     self.win_x , self.win_y = 800 , 480
26     self.tile_size = 256
27
28     p = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, True, 8, self.win_x, self.win_y)
29     self.set_from_pixbuf(p)
30
31     self.reftile_x , self.refpix_x = self.lon2tilex( self.conf.lon , self.conf.zoom )
32     self.reftile_y , self.refpix_y = self.lat2tiley( self.conf.lat , self.conf.zoom )
33
34     self.composeMap()
35
36   def lon2tilex ( self , lon , zoom ) :
37     number = math.modf( ( lon + 180 ) / 360 * 2 ** zoom )
38     return int( number[1] ) , int( self.tile_size * number[0] )
39
40   def lat2tiley ( self , lat , zoom ) :
41     lat = lat * math.pi / 180
42     number = math.modf( ( 1 - math.log( math.tan( lat ) + 1 / math.cos( lat ) ) / math.pi ) / 2 * 2 ** zoom )
43     return int( number[1] ) , int( self.tile_size * number[0] )
44
45   def tilex2lon ( self , ( tilex , pixx ) , zoom ) :
46     tilex = float(tilex)
47     pixx = float(pixx)
48     return ( tilex + pixx/self.tile_size ) / 2.0 ** zoom * 360.0 - 180.0
49
50   def tiley2lat ( self , ( tiley , pixy ) , zoom ) :
51     tiley = float(tiley)
52     pixy = float(pixy)
53     tiley = math.pi * ( 1 - 2 * ( tiley + pixy/self.tile_size ) / 2.0 ** zoom )
54     return math.degrees( math.atan( math.sinh( tiley ) ) )
55
56   def composeMap( self ) :
57     center_x , center_y = self.win_x / 2 , self.win_y / 2
58
59     # To get the central pixel in the window center, we must shift to the tile origin
60     center_x -= self.refpix_x
61     center_y -= self.refpix_y
62
63     # Ranges should be long enough as to fill the screen
64     # Maybe they should be decided based on self.win_x, self.win_y
65     for i in range(-3,4) :
66       for j in range(-3,4) :
67         file = self.tilename( i , j , self.conf.zoom )
68         if file is None :
69           pixbuf = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, False, 8, self.tile_size, self.tile_size )
70           pixbuf.fill( 0x00000000 )
71         else :
72           try :
73             pixbuf = gtk.gdk.pixbuf_new_from_file( file )
74           except gobject.GError , ex :
75             print "Corrupted file %s" % ( file )
76             os.unlink( file )
77             #file = self.tilename( self.reftile_x + i , self.reftile_y + j , self.conf.zoom )
78             file = self.tilename( i , j , self.conf.zoom )
79             try :
80               pixbuf = gtk.gdk.pixbuf_new_from_file( file )
81             except :
82               print "Total failure for tile for %s,%s" % ( self.reftile_x + i , self.reftile_y + j )
83               pixbuf = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, False, 8, self.tile_size, self.tile_size )
84
85         dest_x = self.tile_size * i + center_x
86         dest_y = self.tile_size * j + center_y
87
88         init_x = 0
89         size_x = self.tile_size
90         if dest_x < 0 :
91            init_x = abs(dest_x)
92            size_x = self.tile_size + dest_x
93            dest_x = 0
94         if dest_x + self.tile_size > self.win_x :
95            size_x = self.win_x - dest_x
96
97         init_y = 0
98         size_y = self.tile_size
99         if dest_y < 0 :
100            init_y = abs(dest_y)
101            size_y = self.tile_size + dest_y
102            dest_y = 0
103         if dest_y + self.tile_size > self.win_y :
104            size_y = self.win_y - dest_y
105
106         if ( size_x > 0 and size_y > 0 ) and ( init_x < self.tile_size and init_y < self.tile_size ) :
107             pixbuf.copy_area( init_x, init_y, size_x, size_y, self.get_pixbuf(), dest_x , dest_y )
108         del(pixbuf)
109
110   def tilename ( self , x , y , zoom ) :
111     file = self.tile2file( self.reftile_x + x , self.reftile_y + y , zoom )
112     try :
113       os.stat(file)
114     except :
115     #  if mapDownload :
116       if False :
117         try :
118           # useful members : response.code, response.headers
119           response = urllib2.urlopen( "http://tile.openstreetmap.org/%s/%s/%s.png" % ( zoom , x , y ) )
120           if response.geturl() == "http://tile.openstreetmap.org/11/0/0.png" :
121               return None
122           fd = open( file , 'w' )
123           fd.write( response.read() )
124           fd.close()
125         except :
126           return None
127       else :
128         return None
129     return file
130
131   def tile2file( self , tilex , tiley , zoom ) :
132     rootdir = "%s/%s/%s" % ( self.conf.mapsdir , self.conf.mapclass , zoom )
133     if not os.path.isdir( rootdir ) :
134       os.mkdir(rootdir)
135     rootsubdir = "%s/%s" % ( rootdir , tilex )
136     if not os.path.isdir( rootsubdir ) :
137       os.mkdir(rootsubdir)
138     return "%s/%s.png" % ( rootsubdir , tiley )
139
140   def Shift( self , dx , dy ) :
141     self.hide()
142
143     tile_x , tile_y = ( self.refpix_x - dx ) / self.tile_size , ( self.refpix_y - dy ) / self.tile_size
144     self.reftile_x += tile_x
145     self.reftile_y += tile_y
146
147     self.refpix_x -= dx + self.tile_size * tile_x
148     self.refpix_y -= dy + self.tile_size * tile_y
149
150     self.composeMap()
151     self.show()
152
153   def ZoomChange ( self , selector ) :
154     model = selector.get_model(0)
155     active = selector.get_active(0)
156     value = model.get( model.get_iter(active) , 0 )
157     self.SetZoom( int(value[0]) )
158
159   def SetZoom( self , zoom ) :
160     self.hide()
161     lat = self.tiley2lat( ( self.reftile_y , self.refpix_y ) , self.conf.zoom )
162     lon = self.tilex2lon( ( self.reftile_x , self.refpix_x ) , self.conf.zoom )
163     self.reftile_x , self.refpix_x = self.lon2tilex( lon , zoom )
164     self.reftile_y , self.refpix_y = self.lat2tiley( lat , zoom )
165     self.conf.zoom = zoom
166     self.composeMap()
167     self.show()
168
169   def Up( self ) :
170     self.hide()
171     self.reftile_y -= 1
172     self.composeMap()
173     self.show()
174
175   def Down( self ) :
176     self.hide()
177     self.reftile_y += 1
178     self.composeMap()
179     self.show()
180
181   def Right( self ) :
182     self.hide()
183     self.reftile_x += 1
184     self.composeMap()
185     self.show()
186
187   def Left( self ) :
188     self.hide()
189     self.reftile_x -= 1
190     self.composeMap()
191     self.show()
192
193 if hildon :
194
195     class ZoomDialog ( hildon.TouchSelector ) :
196
197         def __init__ ( self , widget ) :
198             hildon.TouchSelector.__init__( self )
199
200             zooms = gtk.ListStore(str)
201
202             active = index = 0
203             for zoom in range(8,19) :
204                 iter = zooms.append()
205                 zooms.set( iter , 0 , "%2d" % zoom )
206                 if zoom == widget.zoom :
207                     active = index
208                 index += 1
209
210             column = self.append_text_column( zooms , True )
211             #renderer = gtk.CellRendererText()
212             #column = self.append_column( zooms , renderer )
213             #column.set_property('text-column', 0)
214
215             # NOTE : with text=True, we must use 1 instead of 0
216             self.set_active( 0 , active )
217
218 else :
219
220     class ZoomDialog ( gtk.Dialog ) :
221
222         def __init__ ( self , widget ) :
223             gtk.Dialog.__init__( self , "Select zoom level",
224                                  None,
225                                  gtk.DIALOG_MODAL,
226                                  ( gtk.STOCK_OK, gtk.RESPONSE_ACCEPT,
227                                    gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT
228                                    )
229                                  )
230
231             zooms = gtk.ListStore(int)
232             combo = gtk.ComboBox( zooms )
233
234             for zoom in range(8,19) :
235                 iter = zooms.append()
236                 zooms.set( iter , 0 , zoom )
237                 if zoom == widget.zoom :
238                     combo.set_active_iter( iter )
239
240             cell = gtk.CellRendererText()
241             combo.pack_start(cell, True)
242             combo.add_attribute(cell, 'text', 0)
243
244             self.vbox.pack_start(combo , True, True, 0)
245
246             self.connect_object( "response", self.response , combo , widget )
247
248         def response ( self , combo , response  , widget ) :
249             if response == gtk.RESPONSE_ACCEPT :
250                 item = combo.get_active_iter()
251                 model = combo.get_model()
252                 widget.SetZoom( model.get(item,0)[0] )
253             self.destroy()
254
255
256 class MapWindow:
257
258     def destroy(self, widget, data=None):
259         gtk.main_quit()
260
261     def press_event ( self, widget, event, *args ) :
262       # FIXME : Set only if far enough from borders
263       border_x = 40
264       border_y = 30
265       print "press  ",event.get_coords(),event.get_root_coords()
266       if event.x > border_x and event.y > border_y and event.x < ( self.size_x - border_x ) and event.y < ( self.size_y - border_y ) :
267         self.click_x = event.x
268         self.click_y = event.y
269
270     def release_event ( self, widget, event, *args ) :
271       min_shift = 50
272       print "unpress",event.get_coords(),event.get_root_coords()
273       if self.click_x is not None and self.click_y is not None :
274         delta_x = int( event.x - self.click_x )
275         delta_y = int( event.y - self.click_y )
276         shift = math.sqrt( delta_x * delta_x + delta_y * delta_y )
277         if shift > min_shift :
278           self.map.Shift(delta_x, delta_y)
279         #  if delta_x > 100 :
280         #    self.map.Left()
281         #  elif delta_x < -100 :
282         #    self.map.Right()
283         #  elif delta_y > 100 :
284         #    self.map.Up()
285         #  elif delta_y < -100 :
286         #    self.map.Down()
287       self.click_x , self.click_y = None , None
288
289     def screen_event ( self, widget, event, *args ) :
290       print "REDIOS",event
291       print "      ",widget
292       print "      ",args
293
294
295     def on_button_press ( self, widget, event, *args ) :
296       print "HOLA",event
297
298     def on_key_press ( self, widget, event, *args ) :
299       if event.keyval == gtk.keysyms.Up :
300           self.map.Up()
301       elif event.keyval == gtk.keysyms.Down :
302           self.map.Down()
303       elif event.keyval == gtk.keysyms.Right :
304           self.map.Right()
305       elif event.keyval == gtk.keysyms.Left :
306           self.map.Left()
307       else :
308           print "UNKNOWN",event.keyval
309
310     def __init__(self):
311
312         if hildon :
313             self.window = hildon.Window()
314             program = hildon.Program.get_instance()
315             program.add_window(self.window)
316             gtk.set_application_name( "Wifi View" )
317         else :
318             self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
319
320         self.window.connect("destroy", self.destroy)
321
322         self.window.set_border_width(10)
323
324         self.window.connect("key-press-event", self.on_key_press)
325
326         vbox = gtk.VBox(False, 0)
327         self.window.add( vbox )
328
329         # To get explicit GDK_BUTTON_PRESS instead of paired GDK_LEAVE_NOTIFY & GDK_ENTER_NOTIFY
330 #        self.window.add_events(gtk.gdk.BUTTON_MOTION_MASK | gtk.gdk.BUTTON_PRESS_MASK | gtk.gdk.BUTTON_RELEASE_MASK | gtk.gdk.POINTER_MOTION_MASK)
331         self.window.set_events( gtk.gdk.BUTTON_PRESS_MASK | gtk.gdk.BUTTON_RELEASE_MASK )
332         #
333 #        self.window.connect('motion_notify_event', self.screen_event)
334         self.window.connect('button_press_event', self.press_event)
335         self.window.connect('button_release_event', self.release_event)
336         #
337         self.config = wifimap.config.Configuration()
338         self.map = mapWidget( self.config )
339         vbox.pack_end( self.map , True , True , 5)
340
341         self.create_menu( vbox )
342
343         # and the window
344         self.window.show_all()
345
346         self.size_x , self.size_y = 800 , 480
347         self.click_x , self.click_y = None , None
348
349     def zoomdialog ( self , widget ) :
350         dialog = ZoomDialog( widget )
351         dialog.show_all()
352
353     def create_menu ( self , vbox ) :
354         if hildon :
355
356             self.menubar = menubar = hildon.AppMenu()
357
358             #zoomlevel = hildon.Button(gtk.HILDON_SIZE_AUTO,
359             #                          hildon.BUTTON_ARRANGEMENT_VERTICAL,
360             #                          "Zoom level", None)
361             #zoomlevel.connect_object( "clicked", self.zoomstack, self.map )
362             selector = ZoomDialog( self.map )
363             zoomlevel = hildon.PickerButton(gtk.HILDON_SIZE_AUTO,
364                                           hildon.BUTTON_ARRANGEMENT_VERTICAL)
365             zoomlevel.set_title( "Zoom" )
366             zoomlevel.set_selector( selector )
367             zoomlevel.connect_object( "value-changed", self.map.ZoomChange , selector )
368             menubar.append( zoomlevel )
369
370             menubar.show_all()
371             self.window.set_app_menu( menubar )
372
373         else :
374
375             menubar = gtk.MenuBar()
376
377             zoomlevel = gtk.MenuItem( label="Zoom level" )
378             zoomlevel.connect_object( "activate", self.zoomdialog, self.map )
379             menubar.append( zoomlevel )
380
381             vbox.pack_start(menubar,True,True,5)
382
383     def main(self):
384         gtk.main()
385
386 if __name__ == "__main__":
387     map = MapWindow()
388     map.main()
389