Perl regexp from irmin. Code adoped for threads.
[ussd-widget] / ussd-widget / src / usr / lib / hildon-desktop / ussd-widget.py
1 #!/usr/bin/python
2 # -*- coding: utf-8 -*-
3 import gobject
4 import gtk
5 import hildon
6 import hildondesktop
7 import os
8 from subprocess import *
9 import cairo
10 import time
11 from threading import *
12 import re
13
14 class pHelpDialog(gtk.Dialog):
15     def __init__(self, heading, text):
16         gtk.Dialog.__init__(self, heading, None,
17                             gtk.DIALOG_DESTROY_WITH_PARENT | gtk.DIALOG_NO_SEPARATOR,
18                             ("OK", gtk.RESPONSE_OK))
19         self.vbox.add(gtk.Label(text))
20         self.show_all()
21         self.parent
22
23 class UssdConfigDialog(gtk.Dialog):
24     def __init__(self, config):
25         gtk.Dialog.__init__(self, "USSD widget", None,
26                             gtk.DIALOG_DESTROY_WITH_PARENT | gtk.DIALOG_NO_SEPARATOR,
27                             ("Save", gtk.RESPONSE_OK))
28         self.ussdNumber = hildon.Entry(gtk.HILDON_SIZE_AUTO)
29         self.ussdNumber.set_text(config[0])
30         self.parser = hildon.Entry(gtk.HILDON_SIZE_AUTO)
31         self.parser.set_text(config[1])
32         self.chain = hildon.Entry(gtk.HILDON_SIZE_AUTO)
33         self.chain.set_text(config[2])
34         self.update_interval = hildon.Entry(gtk.HILDON_SIZE_AUTO)
35         self.update_interval.set_text(config[3])
36         self.regexp = hildon.Entry(gtk.HILDON_SIZE_AUTO)
37         self.regexp.set_text(config[4])
38
39         phelp = gtk.Button("?")
40         phelp.connect("clicked", on_show_phelp)
41
42         chelp = gtk.Button("?")
43         chelp.connect("clicked", on_show_chelp)
44
45         reghelp = gtk.Button("?")
46         reghelp.connect("clicked", on_show_reghelp)
47
48         numberBox = gtk.HBox()
49         numberBox.add(gtk.Label("USSD number"))
50         numberBox.add(self.ussdNumber)
51         self.vbox.add(numberBox)
52
53         parserBox = gtk.HBox()
54         parserBox.add(gtk.Label("Parser"))
55         parserBox.add(phelp)
56         parserBox.add(self.parser)
57         self.vbox.add(parserBox)
58
59         chainBox = gtk.HBox()
60         chainBox.add(gtk.Label("Chain"))
61         chainBox.add(chelp)
62         chainBox.add(self.chain)
63         self.vbox.add(chainBox)
64
65         updateBox = gtk.HBox()
66         updateBox.add(gtk.Label("Update every "))
67         updateBox.add(self.update_interval)
68         updateBox.add(gtk.Label(" minutes (BROKEN)"))
69         self.vbox.add(updateBox)
70
71         regexpBox = gtk.HBox()
72         regexpBox.add(gtk.Label("RegExp"))
73         regexpBox.add(reghelp)
74         regexpBox.add(self.regexp)
75         self.vbox.add(regexpBox)
76
77         self.show_all()
78         self.parent
79
80 def smart_split_string (str, query) :
81     word = ""
82     result = []
83     # Is simbol backslashed?
84     bs = 0
85     # Quotes: 1 - ", 2 - ', 0 - no quotes
86     qs = 0
87     for i in range(len(str)) :
88       if bs == 0 and (str[i] == '"' and qs == 1 or str[i] == "'" and qs == 2) :
89           qs = 0
90       elif bs == 0 and qs == 0 and (str[i] == '"' or str[i] == "'") :
91           if str[i] == '"':
92               qs = 1
93           else :
94               qs = 2
95       elif bs == 0 and str[i] == '\\' :
96           bs = 1
97       elif bs == 0 and str[i] == '%' :
98           word += query
99           ws = 0
100       else :
101           if bs == 1 and str[i] != '\\' and str[i] != '"' and str[i] != "'" :
102               word += "\\"
103           if qs == 0 and (str[i] == " " or str[i] == "\t") :
104               if word != "" :
105                   result.append(word)
106                   word = ""
107           else :
108               word += str[i]
109           bs = 0 
110     if word != "" :
111         result.append(word)
112     return result 
113
114 def get_config():
115     try :
116         config = open(os.getenv("HOME")+"/.ussdWidget.conf","r")
117         config.readline()
118         config.readline()
119         number = config.readline().strip()
120         config.readline()
121         parser = config.readline().strip()
122         config.readline()
123         chain = config.readline().strip()
124         config.readline()
125         interval = config.readline().strip()
126         config.readline()
127         regexp = config.readline().strip()
128         config.close()
129         return [number, parser, chain, interval, regexp]
130     except  IOError:
131         return None
132
133 def set_config(config):
134     fconfig = open(os.getenv("HOME")+"/.ussdWidget.conf","w")
135     fconfig.writelines(["# Parameters are taken by line number, do not move them\n", "# USSD query to be run by widget\n", config[0], "\n"])
136     fconfig.writelines(["Parser command\n", config[1], "\n"])
137     fconfig.writelines(["Chain command\n", config[2], "\n"])
138     fconfig.writelines(["Update interval in minutes\n", config[3], "\n"])
139     fconfig.writelines(["RegExp pattern\n", config[4], "\n"])
140     fconfig.close()
141
142 def check_regexp(regexp):
143         try :
144             re.compile( regexp )
145         except Exception, e:
146             on_error_regexp( str( e ) )
147             return 1
148         return 0
149
150 #def timed_renewer(Thread):
151 #    def __init__ (self, widget, period):
152 #        self.widget = widget
153 #        self.period = period
154 #        self.version = widget.timerversion
155 #
156 #    def run (self):
157 #        while widget.timerversion == version:
158 #            ussd_renew(widget, None)
159 #            time.sleep(period)
160
161 def ussd_renew(widget, event):
162     if widget.process == None or widget.process.isAlive() == False :
163         widget.process = ussd_renewer (widget)
164         # See bug https://bugs.maemo.org/show_bug.cgi?id=7809
165         widget.process.run()
166
167 class ussd_renewer ():#Thread):
168     def __init__ (self, widget):
169 #        Thread.__init__ (self)
170         self.widget = widget
171         self.daemon = True
172     
173 # stub while thread but is not fixed
174     def isAlive(self):
175         return False
176
177     def run(self):
178         widget = self.widget
179         config = get_config()
180         widget.processing = 1
181         last_text = widget.label.get_text()
182         widget.label.set_text("Processing")
183         widget.queue_draw()
184         gtk.main_iteration ()
185
186         if config :
187             p = Popen(['/usr/bin/ussdquery.py', config[0]], stdout=PIPE)
188             reply = p.communicate()[0].strip()
189             if reply == "" :
190                 reply = "   Error   "
191                 widget.error = 1
192                 # Show previous text in 5 seconds
193                 widget.timer = gobject.timeout_add (5000, error_return, widget, last_text)
194             else :
195                 widget.error = 0
196                 if config[1] != "":
197                     p = Popen(smart_split_string(config[1], reply), stdout=PIPE)
198                     reply = p.communicate(reply+"\n")[0].strip()
199                 if config[2] != "":
200                     p = Popen(smart_split_string(config[2], reply))
201                 if config[4] != "":
202                     try :
203                         r = re.match( config[4], reply ).group( 1 )
204                     except Exception, e:
205                         r = "Regexp Error: " + str( e )
206
207                     if r :
208                         reply = r
209         else :
210             reply = " Bad config "
211         widget.processing = 0
212         widget.label.set_text(reply)
213         widget.queue_draw()
214         gtk.main_iteration ()
215
216 def error_return(widget, text) :
217     if widget.processing == 0 and widget.error == 1:
218         widget.label.set_text(text)
219     return False
220
221 def on_show_settings(widget):
222     config = get_config()
223     if config == None :
224         config = ["", "", "", "", ""]
225
226     dialog = UssdConfigDialog(config)
227     dialog.run()
228
229     if check_regexp( dialog.regexp.get_text() ) :
230         return
231
232     set_config ([dialog.ussdNumber.get_text(), dialog.parser.get_text(), dialog.chain.get_text(), dialog.update_interval.get_text(), dialog.regexp.get_text()])
233
234 # Doesn't work in hildon-home
235 #   widget.timerversion += 1
236 #   if dialog.update_interval.get_text() != "" :
237 #       ussd_renewer (widget, 60000*int(dialog.update_interval.get_text()))
238   
239     dialog.destroy()
240     
241     if config == ["", "", "", "", ""] :
242         widget.label.set_text("Click to update")
243
244 def on_show_phelp(widget):
245     dialog = pHelpDialog("Format help", "Reply would be passed to specified utility, output\n of utility would be shown to you.\n       Format:\n% would be replaced by reply\n\\ invalidates special meaming of following symbol\n\" and ' work as usual\nspace delimits command line parameters of utility")
246     dialog.run()
247     dialog.destroy()
248
249 def on_show_chelp(widget):
250     dialog = pHelpDialog("Format help", "Reply would be passed to specified utility after\nparser utility. May be used for logging, statistics etc.\n       Format:\n% would be replaced by reply\n\\ invalidates special meaming of following symbol\n\" and ' work as usual\nspace delimits command line parameters of utility")
251     dialog.run()
252     dialog.destroy()
253
254 def on_show_reghelp(widget):
255     dialog = pHelpDialog("Format help", "standard python regexps")
256     dialog.run()
257     dialog.destroy()
258
259 def on_error_regexp(error):
260     dialog = pHelpDialog( "Regexp syntax error", error )
261     dialog.run()
262     dialog.destroy()
263
264 def get_color(logicalcolorname):
265     settings = gtk.settings_get_default()
266     color_style = gtk.rc_get_style_by_paths(settings, 'GtkButton', 'osso-logical-colors', gtk.Button)
267     return color_style.lookup_color(logicalcolorname)
268
269 class UssdWidgetPlugin(hildondesktop.HomePluginItem):
270     def __init__(self):
271         hildondesktop.HomePluginItem.__init__(self)
272         
273         self.process = None
274         self.timerversion = 0 # Because threads in pyton are crap
275         self.processing = 0
276         self.error = 0
277
278         colormap = self.get_screen().get_rgba_colormap()
279         self.set_colormap (colormap)
280
281         config = get_config()
282         
283         self.connect("button-press-event", ussd_renew)
284
285         self.label = gtk.Label()
286         self.label.set_padding(15, 10)
287         self.label.set_size_request(-1,-1)
288         self.set_size_request(-1,-1)
289
290         self.vbox = gtk.HBox()
291         self.vbox.add(self.label)
292         self.add(self.vbox)
293
294         self.set_settings(True)
295         self.connect("show-settings", on_show_settings)    
296
297         self.vbox.show_all()
298
299         if config :
300             self.label.set_label("Data not available")
301 # Should be uncommented only after thread bug fixed
302 #            ussd_renew (self, None)
303 # Doesn't work in hildon-home
304 #           if config[3] != "" :
305 #               timed_renewer (self, 60000*int(config[3]))
306         else :
307             self.label.set_label("Configure me")
308
309     def _expose(self, event):
310         cr = self.window.cairo_create()
311
312         # draw rounded rect
313         width, height = self.allocation[2], self.allocation[3]
314
315         #/* a custom shape, that could be wrapped in a function */
316         x0 = 0   #/*< parameters like cairo_rectangle */
317         y0 = 0
318
319         radius = min(15, width/2, height/2)  #/*< and an approximate curvature radius */
320
321         x1 = x0 + width
322         y1 = y0 + height
323
324         cr.move_to  (x0, y0 + radius)
325         cr.arc (x0 + radius, y0 + radius, radius, 3.14, 1.5 * 3.14)
326         cr.line_to (x1 - radius, y0)
327         cr.arc (x1 - radius, y0 + radius, radius, 1.5 * 3.14, 0.0)
328         cr.line_to (x1 , y1 - radius)
329         cr.arc (x1 - radius, y1 - radius, radius, 0.0, 0.5 * 3.14)
330         cr.line_to (x0 + radius, y1)
331         cr.arc (x0 + radius, y1 - radius, radius, 0.5 * 3.14, 3.14)
332
333         cr.close_path ()
334
335         fg_color = get_color("ActiveTextColor")
336
337         if self.processing :
338             bg_color=fg_color
339         else :
340             bg_color=gtk.gdk.color_parse('#000000')
341
342         cr.set_source_rgba (bg_color.red / 65535.0, bg_color.green/65535.0, bg_color.blue/65535.0, 0.7)
343         cr.fill_preserve ()
344
345         if self.error :
346             cr.set_source_rgba (1.0, 0.0, 0.0, 0.5)
347         else :
348             cr.set_source_rgba (fg_color.red / 65535.0, fg_color.green / 65535.0, fg_color.blue / 65535.0, 0.7)
349         cr.stroke ()
350
351     def do_expose_event(self, event):
352         self.chain(event)
353         self._expose (event)
354         self.vbox.do_expose_event (self, event)
355
356 hd_plugin_type = UssdWidgetPlugin
357 gtk.gdk.threads_init()
358
359 # The code below is just for testing purposes.
360 # It allows to run the widget as a standalone process.
361 if __name__ == "__main__":
362     gobject.type_register(hd_plugin_type)
363     obj = gobject.new(hd_plugin_type, plugin_id="plugin_id")
364     obj.show_all()
365     gtk.gdk.threads_init()
366     gtk.main()