0.1.7 release
[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 import cairo
9 import pango
10 import time
11 import re
12 import gettext
13 import fcntl
14 import dbus
15 import subprocess
16 import gsmdecode
17 import sys
18 from dbus.mainloop.glib import DBusGMainLoop
19
20 try :
21         t = gettext.translation('ussd-widget', '/usr/share/locale')
22         _ = t.ugettext
23 except IOError:
24         print "Translation file for your language not found"
25         def retme(arg):
26                 return arg
27         _ = retme
28
29 ussd_languages = ["German", "English", "Italian", "French", "Spanish", "Dutch", "Swedish", "Danish", "Portuguese", "Finnish", "Norwegian", "Greek", "Turkish", "Reserved1", "Reserved2", "Unspecified"]
30 ussd_languages_localized = [_("German"), _("English"), _("Italian"), _("French"), _("Spanish"), _("Dutch"), _("Swedish"), _("Danish"), _("Portuguese"), _("Finnish"), _("Norwegian"), _("Greek"), _("Turkish"), _("Reserved1"), _("Reserved2"), _("Unspecified")]
31
32 # TODO Cutt off too long messages and show them in separate dialog
33 # how TODO widget vertical minimum size policy
34 # TODO interface for queryng from scripts. For example query from one widget can change color of another widget 
35
36 class USSD_Controller:
37         def __init__( self, widget ) :
38                 self.widget = widget
39                 # number, parser, chain, interval, regexp, width, execute_at_start, retry pattern, font, name, language, show_message_box, message_box_parser, additional arguments, regexp group, use SMS listener, SMS number, SMS regexp, SMS timeout
40                 self.default_config = ["", "", "", 0, "", 0, True, [], pango.FontDescription("Nokia Sans 18"), _("Click to update"), 15, False, "", "", 1, False, "", "", 60]
41                 self.config = self.default_config
42                 self.timeout_version = 0
43                 self.retry_version = 0
44                 self.retry_state = 0
45                 self.sms_counter = 0
46                 self.sms_reply = ""
47
48         def save_config( self ) :
49                 configname = os.getenv("HOME")+"/.ussdWidget.conf"
50                 # Aquire lock
51                 lockf = open(configname+".lock", 'a')
52                 fcntl.flock(lockf,fcntl.LOCK_EX)
53
54                 oldconfig=""
55                 try:
56                         fconfig = open(configname,"r")
57                         #Read configuration of other instances
58                         my_section = False
59                         for line in fconfig :
60                                 if line[0] == '%':
61                                         my_section = line[1:].strip() == self.id
62                                 if not my_section:
63                                         oldconfig += line
64                         fconfig.close()
65                 except:
66                         print _("Couldn't read previous config")
67
68                 fconfig = open(configname,"w")
69                 fconfig.seek(0)
70                 fconfig.write(oldconfig)
71                 fconfig.write("%"+self.id+"\n");
72                 fconfig.writelines(["# USSD query to be run by widget\n", "number="+self.config[0], "\n"])
73                 fconfig.writelines(["#Parser command for widget\n", "parser="+self.config[1], "\n"])
74                 fconfig.writelines(["#Parser command for banner\n", "parser_box="+self.config[12], "\n"])
75                 fconfig.writelines(["#Chain command\n", "chain="+self.config[2], "\n"])
76                 fconfig.writelines(["#Update interval in minutes\n", "interval="+str(self.config[3]), "\n"])
77                 fconfig.writelines(["#RegExp pattern\n", "regexp="+self.config[4], "\n"])
78                 fconfig.writelines(["#Widget width\n", "width="+str(self.config[5]), "\n"])
79                 fconfig.writelines(["#Execute query at start\n", "query_at_start="+str(self.config[6]), "\n"])
80                 fconfig.writelines(["#Retry pattern\n"])
81                 fconfig.write("retry=")
82                 first = True
83                 for i in self.config[7]:
84                         if not first:
85                                 fconfig.write ("-")
86                         fconfig.write(str(i))
87                         first = False
88                 fconfig.write("\n")
89                 fconfig.writelines(["#Font description\n", "font="+self.config[8].to_string(), "\n"])
90                 fconfig.writelines(["#Font color\n", "text_color="+self.widget.get_text_color().to_string(), "\n"])
91                 fconfig.writelines(["#Background color\n", "bg_color="+self.widget.get_bg_color().to_string(), "\n"])
92                 fconfig.writelines(["#Widget name\n", "name="+self.config[9], "\n"])
93                 fconfig.writelines(["#Show banner\n", "show_box="+str(self.config[11]), "\n"])
94                 fconfig.writelines(["#USSD reply language\n", "language="+str(self.config[10]), "\n"])
95                 fconfig.writelines(["#Additional ussdquery.py arguments\n", "args="+self.config[13], "\n"])
96                 fconfig.writelines(["#Regexp matching group\n", "reggroup="+str(self.config[14]), "\n"])
97                 fconfig.writelines(["#Use SMS listener\n", "listen_sms="+str(self.config[15]), "\n"])
98                 fconfig.writelines(["#Number,from which SMS should come\n", "sms_number="+self.config[16], "\n"])
99                 fconfig.writelines(["#SMS RegExp pattern\n", "sms_regexp="+self.config[17], "\n"])
100                 fconfig.writelines(["#SMS timeout\n", "sms_timeout="+str(self.config[18]), "\n"])
101                 fconfig.close()
102
103                 fcntl.flock(lockf,fcntl.LOCK_UN)
104                 lockf.close()
105
106         def get_config(self):
107                 return self.config
108
109         def read_config( self, id ):
110                 try :
111                         self.id = id
112                         config = open(os.getenv("HOME")+"/.ussdWidget.conf","r")
113
114                         error = False
115                         i = 0
116                         my_section = False
117                         for line in config :
118                                 i += 1 
119                                 if line[0] == '#':
120                                         continue
121                                 if line[0] == '%':
122                                         my_section = line[1:].strip() == id
123                                         continue
124
125                                 if not my_section:
126                                         # This is config for another instace
127                                         continue
128
129                                 line=line.split('=', 1)
130                                 
131                                 if len(line) != 2 :
132                                         error = True
133                                         print _("Error reading config on line %(line)d. = or # expected.")%{"line":i}
134                                         continue
135                                 if line[0] == "number" :
136                                         self.config[0] = line[1].strip()
137                                 elif line[0] == "parser" :
138                                         self.config[1] = line[1].strip()
139                                 elif line[0] == "parser_box" :
140                                         self.config[12] = line[1].strip()
141                                 elif line[0] == "chain" :
142                                         self.config[2] = line[1].strip()
143                                 elif line[0] == "interval" :
144                                         try:
145                                                 self.config[3] = int(line[1].strip())
146                                         except:
147                                                 error = True
148                                                 print _("Error reading config on line %(line)d. Integer expected.")%{"line":i}
149                                                 continue
150                                 elif line[0] == "regexp" :
151                                         self.config[4] = line[1].strip()
152                                 elif line[0] == "width" :
153                                         try:
154                                                 self.config[5] = int(line[1].strip())
155                                         except:
156                                                 error = True
157                                                 print _("Error reading config on line %(line)d. Integer expected.")%{"line":i}
158                                                 continue
159                                 elif line[0] == "query_at_start" :
160                                         if line[1].strip() == "True" :
161                                                 self.config[6] = True
162                                         else :
163                                                 self.config[6] = False
164                                 elif line[0] == "retry" :
165                                         line[1] = line[1].strip()
166                                         if line[1] != "":
167                                                 line[1] = line[1].split("-")
168                                                 i = 0
169                                                 while i < len(line[1]) :
170                                                         try:
171                                                                 line[1][i] = int(line[1][i])
172                                                         except:
173                                                                 error = True
174                                                                 print _("Error reading config on line %(line)d. Integer expected.")%{"line":i}
175                                                         i += 1
176                                                 self.config[7] = line[1]
177                                         else:
178                                                 self.config[7] = []
179                                                 continue
180                                 elif line[0] == "font" :
181                                         try:
182                                                 self.config[8] = pango.FontDescription(line[1].strip())
183                                         except:
184                                                 error = True
185                                                 print _("Error reading config on line %(line)d. Pango font description expected.")%{"line":i}
186                                                 continue
187                                 elif line[0] == "bg_color" :
188                                         try:
189                                                 self.widget.set_bg_color(gtk.gdk.color_parse(line[1].strip()))
190                                         except:
191                                                 error = True
192                                                 print _("Error reading config on line %(line)d. Expected color definition.")%{"line":i}
193                                 elif line[0] == "text_color" :
194                                         try:
195                                                 self.widget.set_text_color(gtk.gdk.color_parse(line[1].strip()))
196                                         except:
197                                                 error = True
198                                                 print _("Error reading config on line %(line)d. Expected color definition.")%{"line":i}
199                                 elif line[0] == "name" :
200                                         self.config[9] = line[1].strip()
201                                 elif line[0] == "show_box" :
202                                         if line[1].strip() == "True" :
203                                                 self.config[11] = True
204                                         else :
205                                                 self.config[11] = False
206                                 elif line[0] == "language" :
207                                         try:
208                                                 if int(line[1].strip()) >=0 and int(line[1].strip()) < len(ussd_languages):
209                                                         self.config[10] = int(line[1].strip())
210                                                 else:
211                                                         error = True
212                                                         print _("Error reading config on line %(line)d. Unknown language code.")%{"line":i}
213                                         except:
214                                                 error = True
215                                                 print _("Error reading config on line %(line)d. Integer expected.")%{"line":i}
216                                 elif line[0] == "args" :
217                                         self.config[13] = line[1].strip()
218                                 elif line[0] == "reggroup" :
219                                         try:
220                                                 self.config[14] = int(line[1].strip())
221                                         except:
222                                                 error = True
223                                                 print _("Error reading config on line %(line)d. Integer expected.")%{"line":i}
224                                                 continue
225                                 elif line[0] == "listen_sms" :
226                                         if line[1].strip() == "True" :
227                                                 self.config[15] = True
228                                         else :
229                                                 self.config[15] = False
230                                 elif line[0] == "sms_number" :
231                                         self.config[16] = line[1].strip()
232                                 elif line[0] == "sms_regexp" :
233                                         self.config[17] = line[1].strip()
234                                 elif line[0] == "sms_timeout" :
235                                         try:
236                                                 self.config[18] = int(line[1].strip())
237                                         except:
238                                                 error = True
239                                                 print _("Error reading config on line %(line)d. Integer expected.")%{"line":i}
240                                                 continue
241                                 else :
242                                         error = True
243                                         print _("Error reading config on line %(line)d. Unexpected variable: ")%{"line":i}+line[0]
244                                         continue 
245
246                         config.close()
247
248                         if error :
249                                 self.widget.error = 1
250                                 self.widget.set_text (_("Config error"), 5000)  
251
252                         return self.config
253                 except  IOError:
254                         self.widget.error = 1
255                         self.widget.set_text (_("Config error"), 0)
256                         print _("IO error while reading config")
257
258                         return self.default_config
259
260         def on_show_settings( self, widget ) :
261                 dialog = UssdConfigDialog(self.config, self.widget.get_bg_color(), self.widget.get_text_color())
262
263                 while True:
264                         if dialog.run() != gtk.RESPONSE_OK :
265                                 dialog.destroy()
266                                 return
267
268                         test = check_regexp(dialog.regexp.get_text()) 
269                         if test :
270                                 dialog.on_error_regexp(test)
271                                 continue
272
273                         # Check, that we have ussd number
274                         if not check_number(dialog.ussdNumber.get_text()):
275                                 dialog.on_error_ussd_number()
276                                 continue
277                 
278                         if not check_number(dialog.sms_number.get_text()):
279                                 dialog.on_error_sms_number()
280                                 continue
281
282                         # Parse retry pattern
283                         retry = dialog.retryEdit.get_text().strip()
284                         if retry != "" :
285                                 retry = retry.split("-")
286                                 i = 0
287                                 while i < len(retry) :
288                                         try:
289                                                 retry[i] = int(retry[i])
290                                         except:
291                                                 dialog.on_error_retry_pattern()
292                                                 break 
293                                         i += 1
294                         
295                                 if i < len(retry):
296                                         continue
297                         else :
298                                 retry = []
299
300                         break
301
302                 self.config = [
303                         dialog.ussdNumber.get_text(), 
304                         dialog.parser.get_text(), 
305                         dialog.chain.get_text(), 
306                         dialog.update_interval.get_value(), 
307                         dialog.regexp.get_text(),
308                         dialog.widthEdit.get_value(),
309                         dialog.query_at_start.get_active(),
310                         retry,
311                         dialog.font,
312                         dialog.wname.get_text(),
313                         dialog.language.get_active(),
314                         dialog.show_box.get_active(),
315                         dialog.b_parser.get_text(),
316                         dialog.args.get_text(),
317                         dialog.reggroup.get_value(),
318                         dialog.sms_listener.get_active(),
319                         dialog.sms_number.get_text(),
320                         dialog.sms_regexp.get_text(),
321                         dialog.sms_timeout.get_value() 
322                 ]
323
324                 widget.set_bg_color(dialog.bg_color)
325                 widget.set_text_color(dialog.text_color)
326
327                 self.save_config()
328
329                 widget.set_width(self.config[5])        
330                 self.reset_timed_renew()
331                 self.widget.label.modify_font(self.config[8])
332
333                 dialog.destroy()
334
335                 # Before running this function widget wasn't configured
336                 if self.config == self.default_config:
337                         self.widget.set_text(_("Click to update"))
338                 return
339
340         def handle_sms(self, pdumsg, msgcenter, message, sendernumber):
341                 # Timeout was recieved first
342                 if self.sms_ready:
343                         return
344
345                 if self.config[16] == "" or self.config[16] == sendernumber:
346                         pdu = gsmdecode.decode_pdu (pdumsg)
347                         if pdu != None :
348                                 self.sms_reply += pdu['user_data']
349                                 if not pdu['part']:
350                                         if self.config[17] == "" or re.search( self.config[17], message, re.MULTILINE | re.UNICODE ):
351                                                 self.sms_ready = True
352                                                 self.sms_signal.remove()
353                                                 self.process_reply()
354
355         def callback_ussd_data( self, source, condition ):
356                 if condition == gobject.IO_IN or condition == gobject.IO_PRI :
357                         data = source.read( )
358                         if len( data ) > 0 :
359                                 self.cb_reply += data
360                                 return True
361                         else :
362                                 self.cb_ready = 1
363                                 return False
364
365                 elif condition == gobject.IO_HUP or condition == gobject.IO_ERR :
366                         self.cb_ready = 1
367                         self.ussd_ready = True
368                         self.process_reply()
369                         return False
370
371                 print (_("serious problems in program logic"))
372                 # This will force widget to show error message
373                 self.cb_reply = ""
374                 self.cb_ready = 1
375                 self.process_reply()
376                 return False
377
378
379         def call_external_script( self, ussd_code, language ):
380                 self.cb_ready = 0
381                 self.cb_reply = "";
382                 p = subprocess.Popen(['/usr/bin/ussdquery.py', ussd_code, "-l", ussd_languages[language]] + smart_split_string(self.config[13],"%","&"), stdout=subprocess.PIPE)
383                 gobject.io_add_watch( p.stdout, gobject.IO_IN | gobject.IO_PRI | gobject.IO_HUP | gobject.IO_ERR , self.callback_ussd_data )
384
385         def ussd_renew(self, widget, event):
386                 if self.widget.processing == 0:
387                         if self.config :
388                                 widget.processing = 1
389                                 widget.set_text(_("Processing"), 0)
390
391                                 self.ussd_ready = False
392                                 self.sms_ready = False
393                                 self.sms_reply = ""
394
395                                 if self.config[15]:
396                                         self.sms_counter += 1
397                                         self.retry_timer = gobject.timeout_add (1000*self.config[18], self.sms_timeout, self.sms_counter)
398                                         
399                                         DBusGMainLoop(set_as_default=True)
400                                         self.bus = dbus.SystemBus()
401                                         self.sms_signal = self.bus.add_signal_receiver(self.handle_sms, path='/com/nokia/phone/SMS',   dbus_interface='Phone.SMS', signal_name='IncomingSegment')
402
403                                 self.call_external_script( self.config[0], self.config[10] )
404                         else :
405                                 widget.processing = 0
406                                 widget.error = 1
407                                 widget.set_text(_("No config"), 0)
408
409         def process_reply( self ):
410                 if not self.ussd_ready or not self.sms_ready and self.config[15]:
411                         return
412
413                 reply = self.cb_reply.strip()
414                 sms_reply = self.sms_reply.strip()
415                 
416                 if reply == "" or self.config[15] and sms_reply == "" :
417                         self.widget.error = 1
418                         self.widget.set_text (_("Error"), 5000)
419                         if self.retry_state == len(self.config[7]):
420                                 self.retry_version += 1
421                                 self.retry_state = 0
422                         else :
423                                 self.retry_timer = gobject.timeout_add (1000*self.config[7][self.retry_state], self.retry_renew, self.retry_version)
424                                 self.retry_state += 1
425                 else :
426                         self.widget.error = 0
427                         # Apply regexp
428                         reresult1 = reresult2 = None
429                         if self.config[4] != "":
430                                 reresult1 = re.search( self.config[4], reply, re.MULTILINE | re.UNICODE )
431                         if self.config[17] != "":
432                                 reresult2 = re.search( self.config[17], sms_reply, re.MULTILINE | re.UNICODE )
433                         w_reply = b_reply = reply
434                         if self.widget.error == 0:
435                                 # Pass to box parser
436                                 if self.config[12] != "" and self.config[11]:
437                                         try:
438                                                 p = subprocess.Popen(smart_split_string(self.config[12], reply, sms_reply, reresult1, reresult2), stdout=subprocess.PIPE)
439                                                 b_reply = p.communicate()[0].strip()
440                                         except Exception, e:
441                                                 print _("Couldn't exec banner parser:")+str(e)
442                                                 self.widget.error = 1
443                                 else:
444                                         if self.config[4] != "":
445                                                 try :
446                                                         b_reply = reresult1.group( self.config[14] )
447                                                 except Exception, e:
448                                                         self.widget.error = 1
449                                                         b_reply = _("Group not found: \n") + reply
450                                          
451                                 # Pass to widget parser
452                                 if self.config[1] != "":
453                                         try:
454                                                 p = subprocess.Popen(smart_split_string(self.config[1], reply, sms_reply, reresult1, reresult2), stdout=subprocess.PIPE)
455                                                 w_reply = p.communicate()[0].strip()
456                                         except Exception, e:
457                                                 print _("Couldn't exec widget parser:")+str(e)
458                                                 self.widget.error = 1
459                                 else:
460                                         if self.config[4] != "":
461                                                 try :
462                                                         w_reply = reresult1.group( self.config[14] )
463                                                 except Exception, e:
464                                                         self.widget.error = 1
465                                                         w_reply = _("Group not found: \n") + reply
466                                 # Pass to chain
467                                 if self.config[2] != "":
468                                         try:
469                                                 p = subprocess.Popen(smart_split_string(self.config[2], reply, sms_reply, reresult1, reresult2))
470                                         except Exception, e:
471                                                 print _("Couldn't exec chain:")+str(e)
472                                                 self.widget.error = 1
473                         if self.config[11]:
474                                 banner = hildon.hildon_banner_show_information (self.widget, "", b_reply)
475                                 banner.set_timeout (5000)
476                                 b_reply
477                         self.widget.set_text(w_reply)
478                 self.widget.processing = 0
479
480         def sms_timeout(self, version):
481                 if version == self.sms_counter :
482                         self.sms_reply = ""
483                         self.sms_ready = True
484                         self.sms_signal.remove()
485                         self.process_reply()
486                 return False
487
488         def timed_renew(self, version):
489                 if version < self.timeout_version :
490                         return False
491                 self.ussd_renew(self.widget, None)
492                 return True
493
494         def retry_renew(self,version):
495                 if self.widget.error == 0 or self.widget.processing == 1 or version < self.retry_version :
496                         return False
497                 self.ussd_renew(self.widget, None)
498                 return False
499
500         def reset_timed_renew (self) :
501                 self.timeout_version += 1
502                 if self.config[3] != 0 :
503                         self.timer = gobject.timeout_add (60000*self.config[3], self.timed_renew, self.timeout_version)
504
505 class pHelpDialog(gtk.Dialog):
506         def __init__(self, heading, text):
507                 gtk.Dialog.__init__(self, heading, None,
508                         gtk.DIALOG_DESTROY_WITH_PARENT | gtk.DIALOG_NO_SEPARATOR,
509                         (_("OK").encode("utf-8"), gtk.RESPONSE_OK))
510                 label = gtk.Label(text)
511                 label.set_line_wrap (True)
512                 self.vbox.add(label)
513                 self.show_all()
514                 self.parent
515
516 class UssdConfigDialog(gtk.Dialog):
517         def __init__(self, config, bg_color, text_color):
518                 gtk.Dialog.__init__(self, _("USSD widget"), None, 
519                         gtk.DIALOG_DESTROY_WITH_PARENT | gtk.DIALOG_NO_SEPARATOR,
520                         (_("Save").encode("utf-8"), gtk.RESPONSE_OK))
521
522                 self.font = config[8]
523                 self.bg_color = bg_color
524                 self.text_color = text_color
525
526                 self.set_size_request(-1, 400)
527                 self.ussdNumber = hildon.Entry(gtk.HILDON_SIZE_AUTO)
528                 self.ussdNumber.set_text(config[0])
529                 self.parser = hildon.Entry(gtk.HILDON_SIZE_AUTO)
530                 self.parser.set_text(config[1])
531                 self.b_parser = hildon.Entry(gtk.HILDON_SIZE_AUTO)
532                 self.b_parser.set_text(config[12])
533
534                 self.chain = hildon.Entry(gtk.HILDON_SIZE_AUTO)
535                 self.chain.set_text(config[2])
536                 self.update_interval = hildon.NumberEditor(0, 9999)
537                 self.update_interval.set_value(config[3])
538                 self.regexp = hildon.Entry(gtk.HILDON_SIZE_AUTO)
539                 self.regexp.set_text(config[4])
540                 self.widthEdit = hildon.NumberEditor(0, 1000)
541                 self.widthEdit.set_value(config[5])
542                 self.retryEdit = hildon.Entry(gtk.HILDON_SIZE_AUTO)
543                 self.args = hildon.Entry(gtk.HILDON_SIZE_AUTO)
544                 self.args.set_text(config[13])
545                 self.reggroup = hildon.NumberEditor(0, 255)
546                 self.reggroup.set_value(config[14])
547                 
548                 selector = hildon.TouchSelector(text=True)
549                 for i in ussd_languages_localized:
550                         selector.append_text(i)
551                 self.language = hildon.PickerButton(gtk.HILDON_SIZE_AUTO, hildon.BUTTON_ARRANGEMENT_HORIZONTAL)
552                 self.language.set_selector(selector)
553                 self.language.set_active(config[10])
554                 self.language.set_title(_("USSD reply language"))
555                 self.language.set_size_request(-1, -1)
556
557                 self.wname = hildon.Entry(gtk.HILDON_SIZE_AUTO)
558                 self.wname.set_text(config[9])
559                 self.show_box = gtk.CheckButton(_("Enable banner. Parser:"))
560                 self.show_box.connect("toggled", self.show_box_changed)
561                 self.show_box.set_active(config[11])
562
563                 text = ""
564                 for i in config[7]:
565                         if text != "":
566                                 text += "-"
567                         text += str(i)
568                 self.retryEdit.set_text(text)
569
570                 self.query_at_start = gtk.CheckButton(_("Execute query on start"))
571                 self.query_at_start.set_active(config[6])
572
573                 self.fontButton = gtk.Button(_("Font"))
574                 self.fontButton.connect("clicked", self.on_show_font_selection)
575
576                 self.colorButton = gtk.Button(_("Background color"))
577                 self.colorButton.connect("clicked", self.on_show_color_selection)
578                 self.textColorButton = gtk.Button(_("Text color"))
579                 self.textColorButton.connect("clicked", self.on_show_text_color_selection)
580                 
581                 phelp = gtk.Button("?")
582                 phelp.connect("clicked", self.on_show_phelp)
583                 
584                 bphelp = gtk.Button("?")
585                 bphelp.connect("clicked", self.on_show_bphelp)
586
587                 chelp = gtk.Button("?")
588                 chelp.connect("clicked", self.on_show_chelp)
589
590                 reghelp = gtk.Button("?")
591                 reghelp.connect("clicked", self.on_show_reghelp)
592
593                 retryhelp = gtk.Button("?")
594                 retryhelp.connect("clicked", self.on_show_retryhelp)
595                 
596                 numberhelp = gtk.Button("?")
597                 numberhelp.connect("clicked", self.on_show_number_help)
598
599                 area = hildon.PannableArea()
600                 self.vbox.add(area)
601                 vbox = gtk.VBox()
602                 area.add_with_viewport(vbox)
603                 
604                 numberBox = gtk.HBox()
605                 numberLabel = gtk.Label(_("USSD number"))
606                 numberLabel.set_alignment(0,0.6)
607                 numberLabel.set_size_request(100, -1)
608                 numberhelp.set_size_request(1, -1)
609                 self.ussdNumber.set_size_request(200, -1)
610                 numberBox.add(numberLabel)
611                 numberBox.add(numberhelp)
612                 numberBox.add(self.ussdNumber)
613                 vbox.add(numberBox)
614
615                 vbox.add(self.query_at_start)
616
617                 nameBox = gtk.HBox()
618                 nameLabel = gtk.Label(_("Name"))
619                 nameLabel.set_alignment(0,0.6)
620                 nameLabel.set_size_request(100, -1)
621                 self.wname.set_size_request(200, -1)
622                 nameBox.add(nameLabel)
623                 nameBox.add(self.wname)
624                 vbox.add(nameBox)
625
626                 parserBox = gtk.HBox()
627                 parserLabel = gtk.Label(_("Parser for widget"))
628                 parserLabel.set_alignment(0,0.6)
629                 parserLabel.set_size_request(200, -1)
630                 phelp.set_size_request(10, -1)
631                 parserBox.add(parserLabel)
632                 parserBox.add(phelp)
633                 vbox.add(parserBox)
634                 vbox.add(self.parser)
635                 
636                 b_parserBox = gtk.HBox()
637                 self.show_box.set_size_request(200, -1)
638                 bphelp.set_size_request(10, -1)
639                 b_parserBox.add(self.show_box)
640                 b_parserBox.add(bphelp)
641                 vbox.add(b_parserBox)
642                 vbox.add(self.b_parser)
643                 
644                 chainBox = gtk.HBox()
645                 chainLabel = gtk.Label(_("Chain"))
646                 chainLabel.set_alignment(0,0.6)
647                 chainLabel.set_size_request(200, -1)
648                 chelp.set_size_request(10, -1)
649                 chainBox.add(chainLabel)
650                 chainBox.add(chelp)
651                 vbox.add(chainBox)
652                 vbox.add(self.chain)
653
654                 regexpBox = gtk.HBox()
655                 regexpLabel = gtk.Label(_("Regular expression"))
656                 regexpLabel.set_alignment(0,0.6)
657                 regexpLabel.set_size_request(200, -1)
658                 regexpGroupLabel = gtk.Label(_("Group"))
659                 regexpGroupLabel.set_size_request(1, -1)
660                 reghelp.set_size_request(10, -1)
661                 regexpBox.add(regexpLabel)
662                 regexpBox.add(reghelp)
663                 regexpBox.add(regexpGroupLabel)
664                 vbox.add(regexpBox)
665                 self.reggroup.set_size_request(1,-1);
666                 self.regexp.set_size_request(250,-1);
667                 regexpInputBox = gtk.HBox()
668                 regexpInputBox.add(self.regexp)
669                 regexpInputBox.add(self.reggroup)
670                 vbox.add(regexpInputBox)                
671
672                 widthBox = gtk.HBox()
673                 widthLabel = gtk.Label(_("Max. width"))
674                 widthLabel.set_alignment(0,0.6)
675                 symbolsLabel = gtk.Label(_("symbols"))
676                 widthLabel.set_size_request(140, -1)
677                 self.widthEdit.set_size_request(50, -1)
678                 symbolsLabel.set_size_request(40,-1)
679                 widthBox.add(widthLabel)
680                 widthBox.add(self.widthEdit)
681                 widthBox.add(symbolsLabel)
682                 vbox.add(widthBox)
683
684                 updateBox = gtk.HBox()
685                 updateLabel = gtk.Label(_("Update every"))
686                 updateLabel.set_alignment(0,0.6)
687                 minutesLabel = gtk.Label(_("minutes"))
688                 updateLabel.set_size_request(140, -1)
689                 self.update_interval.set_size_request(50, -1)
690                 minutesLabel.set_size_request(40, -1)
691                 updateBox.add(updateLabel)
692                 updateBox.add(self.update_interval)
693                 updateBox.add(minutesLabel)
694                 vbox.add(updateBox)
695
696                 retryBox = gtk.HBox()
697                 retryLabel = gtk.Label(_("Retry pattern"))
698                 retryLabel.set_alignment(0,0.6)
699                 retryLabel.set_size_request(200, -1)
700                 retryhelp.set_size_request(10, -1)
701                 retryBox.add(retryLabel)
702                 retryBox.add(retryhelp)
703                 vbox.add(retryBox)
704                 vbox.add(self.retryEdit)                
705                 
706                 argsLabel = gtk.Label(_("Additional ussdquery.py options"))
707                 argsLabel.set_alignment(0,0.6)
708                 vbox.add(argsLabel)
709                 vbox.add(self.args)             
710                 
711                 viewBox = gtk.HBox()
712                 viewBox.add(self.fontButton)
713                 viewBox.add(self.textColorButton)
714                 viewBox.add(self.colorButton)
715                 vbox.add(viewBox)
716         
717                 self.sms_box = gtk.VBox()       
718                 self.sms_listener = gtk.CheckButton(_("Enable SMS listener."))
719                 self.sms_listener.connect("toggled", self.sms_box_changed)
720                 self.sms_listener.set_active(config[15])
721                 vbox.add (self.sms_listener)
722
723                 self.sms_number = hildon.Entry(gtk.HILDON_SIZE_AUTO)
724                 self.sms_number.set_text(config[16])
725                 smsNumberBox = gtk.HBox()
726                 smsNumberLabel = gtk.Label(_("SMS number"))
727                 smsNumberLabel.set_alignment(0,0.6)
728                 smsNumberLabel.set_size_request(100, -1)
729                 self.sms_number.set_size_request(200, -1)
730                 smsNumberBox.add(smsNumberLabel)
731                 smsNumberBox.add(self.sms_number)
732                 self.sms_box.add(smsNumberBox)
733                 
734                 smsRegexpLabel = gtk.Label(_("Regular expression"))
735                 smsRegexpLabel.set_alignment(0,0.6)
736                 self.sms_box.add(smsRegexpLabel)
737                 
738                 self.sms_regexp = hildon.Entry(gtk.HILDON_SIZE_AUTO)
739                 self.sms_regexp.set_text(config[17])
740                 self.sms_box.add(self.sms_regexp)
741
742                 self.sms_timeout = hildon.NumberEditor(0, 9999)
743                 self.sms_timeout.set_value(config[18])
744                 sms_timeout_box = gtk.HBox()
745                 timeoutLabel = gtk.Label(_("Timeout"))
746                 timeoutLabel.set_alignment(0,0.6)
747                 secondsLabel = gtk.Label(_("seconds"))
748                 timeoutLabel.set_size_request(140, -1)
749                 self.sms_timeout.set_size_request(50, -1)
750                 secondsLabel.set_size_request(40, -1)
751                 sms_timeout_box.add(timeoutLabel)
752                 sms_timeout_box.add(self.sms_timeout)
753                 sms_timeout_box.add(secondsLabel)
754                 self.sms_box.add(sms_timeout_box)
755                 
756                 vbox.add(self.sms_box)
757
758                 vbox.add(gtk.Label(_("DO NOT CHANGE. Unspecified is what you want.")))
759                 vbox.add(self.language)
760
761                 self.show_all()
762                 self.show_box_changed(None)
763                 self.sms_box_changed(None)
764                 self.parent
765
766         #============ Dialog helper functions =============
767         def on_show_phelp(self, widget):
768                 dialog = pHelpDialog(_("Format help"), _("Reply would be passed to specified utility, output of utility would be shown to you on widget.\n       Format:\n% would be replaced by reply\n%N with N'th regexp matching group\n& would be replaced with sms content\n&N with N'th sms regexp group\n\\ invalidates special meaming of following symbol\n\" and ' work as usual\nspace delimits command line parameters of utility\n      Hint: use echo \"Your string %\" to prepend your string to reply."))
769                 dialog.run()
770                 dialog.destroy()
771
772         def on_show_bphelp(self, widget):
773                 dialog = pHelpDialog(_("Format help"), _("Reply would be passed to specified utility, output of utility would be shown to you on banner.\n       Format:\n% would be replaced by reply\n%N with N'th regexp matching group\n& would be replaced with sms content\n&N with N'th sms regexp group\n\\ invalidates special meaming of following symbol\n\" and ' work as usual\nspace delimits command line parameters of utility\n      Hint: use echo \"Your string %\" to prepend your string to reply."))
774                 dialog.run()
775                 dialog.destroy()
776         
777         def on_show_chelp(self, widget):
778                 dialog = pHelpDialog(_("Format help"), _("Reply would be passed to specified utility after parser utility. May be used for logging, statistics etc.\n       Format:\n% would be replaced by reply\n%N with N'th regexp matching group\n& would be replaced with sms content\n&N with N'th sms regexp group\n\\ invalidates special meaming of following symbol\n\" and ' work as usual\nspace delimits command line parameters of utility\n"))
779                 dialog.run()
780                 dialog.destroy()
781
782         def on_show_reghelp(self, widget):
783                 dialog = pHelpDialog(_("Format help"), _("Standard python regexps. Use\n (.+?[\d\,\.]+)\n to delete everything after first number."))
784                 dialog.run()
785                 dialog.destroy()
786
787         def on_show_retryhelp(self, widget):
788                 dialog = pHelpDialog(_("Format help"), _("Pauses between attemps (in seconds), delimited by -. For example 15-15-300 means \"In case of failure wait 15 seconds, try again, on failure wait 15 more secodns and try again, on failure make last attempt after 5 minutes\""))
789                 dialog.run()
790                 dialog.destroy()
791         
792         def on_show_number_help(self, widget):
793                 dialog = pHelpDialog(_("Format help"), _("USSD number. To perform USSD menu navigation divide queries vith spacebars. For xample '*100# 1' means 1st entry in *100# menu."))
794                 dialog.run()
795                 dialog.destroy()
796         
797         def on_error_regexp(self, error):
798                 dialog = pHelpDialog(_("Regexp syntax error"), error )
799                 dialog.run()
800                 dialog.destroy()
801
802         def on_error_ussd_number(self):
803                 dialog = pHelpDialog(_("Incorrect USSD number"), _("USSD number should contain only digits, +, * or #") )
804                 dialog.run()
805                 dialog.destroy()
806
807         def on_error_retry_pattern(self):
808                 dialog = pHelpDialog(_("Incorrect retry pattern"), _("Retry pattern should contain only numbers, delimited by -") )
809                 dialog.run()
810                 dialog.destroy()
811
812         def on_show_color_selection (self, event):
813                 colorDialog = gtk.ColorSelectionDialog(_("Choose background color"))
814                 colorDialog.colorsel.set_current_color(self.bg_color)
815                 if colorDialog.run() == gtk.RESPONSE_OK :
816                         self.bg_color = colorDialog.colorsel.get_current_color()
817                 colorDialog.destroy()
818
819         def on_show_text_color_selection (self, event):
820                 colorDialog = gtk.ColorSelectionDialog(_("Choose text color"))
821                 colorDialog.colorsel.set_current_color(self.text_color)
822                 if colorDialog.run() == gtk.RESPONSE_OK :
823                         self.text_color = colorDialog.colorsel.get_current_color()
824                 colorDialog.destroy()
825         
826         def on_show_font_selection (self, event):
827                 fontDialog = gtk.FontSelectionDialog(_("Choose a font"))
828                 fontDialog.set_font_name(self.font.to_string())
829
830                 if fontDialog.run() != gtk.RESPONSE_OK :
831                         fontDialog.destroy()
832                         return
833
834                 self.font = pango.FontDescription (fontDialog.get_font_name())
835                 fontDialog.destroy()
836
837         def show_box_changed (self, event):
838                 if self.show_box.get_active():
839                         self.b_parser.show()
840                 else:
841                         self.b_parser.hide()
842
843         def sms_box_changed (self, event):
844                 if self.sms_listener.get_active():
845                         self.sms_box.show()
846                 else:
847                         self.sms_box.hide()
848
849 def smart_split_string (str, reply1, reply2, reres1 = None, reres2 = None) :
850         word = ""
851         result = []
852         # Is simbol backslashed?
853         bs = 0
854         # Quotes: 1 - ", 2 - ', 0 - no quotes
855         qs = 0
856         # Read out number
857         num = -1
858         # Current substitution simbol
859         subst = ''
860
861         for i in range(len(str)) :
862                 if num>= 0:
863                         if str[i] in ["1", "2", "3", "4", "5", "6", "7", "8", "9", "0"] :
864                                 num *= 10
865                                 num += int(str[i])
866                                 continue
867                         else:
868                                 if subst == '&':
869                                         if reres2 != None and num != 0:
870                                                 word += reres2.group(num)
871                                         else:
872                                                 word += reply2
873                                 else:
874                                         if reres1 != None and num != 0:
875                                                 word += reres1.group(num)
876                                         else:
877                                                 word += reply1
878                                 ws = 0
879                                 num = -1
880                                 subst = ''
881                                 # Delete backslash if it delimites usual numbers from % or &
882                                 if str[i] == '\\' and i < len(str)-1 and str[i+1] in ["1", "2", "3", "4", "5", "6", "7", "8", "9", "0"] :
883                                         continue
884                 if bs == 0 and (str[i] == '"' and qs == 1 or str[i] == "'" and qs == 2) :
885                         qs = 0
886                 elif bs == 0 and qs == 0 and (str[i] == '"' or str[i] == "'") :
887                         if str[i] == '"':
888                                 qs = 1
889                         else :
890                                 qs = 2
891                 elif bs == 0 and str[i] == '\\' :
892                         bs = 1
893                 elif bs == 0 and (str[i] == '%' or str[i] == '&') :
894                         subst = str[i]
895                         num = 0
896                 else :
897                         if bs == 1 and str[i] != '\\' and str[i] != '"' and str[i] != "'" :
898                                 word += "\\"
899                         if qs == 0 and (str[i] == " " or str[i] == "\t") :
900                                 if word != "" :
901                                         result.append(word)
902                                         word = ""
903                         else :
904                                 word += str[i]
905                         bs = 0 
906         
907         if subst == '&':
908                 if reres2 != None and num != 0 and num != -1:
909                         word += reres2.group(num)
910                 else:
911                         word += reply2
912         elif subst == '%':
913                 if reres1 != None and num != 0 and num != -1:
914                         word += reres1.group(num)
915                 else:
916                         word += reply1
917         if word != "" :
918                 result.append(word)
919         return result 
920
921 def check_regexp(regexp):
922         try :
923                 re.compile( regexp )
924         except Exception, e:
925                         return str(e)
926         return False
927
928 def check_number(number):
929         for s in number :
930                 if not (s in ["1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "+", "*", "#", " "]) :
931                         return False
932         return True
933
934 #=============== The widget itself ================
935
936 def get_color(logicalcolorname):
937         settings = gtk.settings_get_default()
938         color_style = gtk.rc_get_style_by_paths(settings, 'GtkButton', 'osso-logical-colors', gtk.Button)
939         return color_style.lookup_color(logicalcolorname)
940
941 class UssdWidgetPlugin(hildondesktop.HomePluginItem):
942         def __init__(self):
943                 hildondesktop.HomePluginItem.__init__(self)
944         
945                 self.processing = 0
946                 self.bg_color=gtk.gdk.color_parse('#000000')
947                 self.text_color=gtk.gdk.color_parse('#ffffff')
948                 self.error = 0
949                 self.timeout_version = 0
950
951                 colormap = self.get_screen().get_rgba_colormap()
952                 self.set_colormap (colormap)
953
954                 self.controller = USSD_Controller(self)
955                  
956 # TODO Click event would be better
957                 self.connect("button-press-event", self.controller.ussd_renew)
958
959                 self.vbox = gtk.HBox()
960                 self.add(self.vbox)
961
962                 self.set_settings(True)
963                 self.connect("show-settings", self.controller.on_show_settings)
964                 self.label = gtk.Label("")
965                 
966                 self.vbox.add(self.label)
967                 self.vbox.set_child_packing(self.label, False, False, 0, gtk.PACK_START)
968                 self.label.set_padding(15, 10)
969                 self.label.set_size_request(-1,-1)
970                 self.set_size_request(-1,-1)
971                 self.label.set_line_wrap (True)
972
973                 self.vbox.show_all()
974
975         def do_show(self):
976                 config = self.controller.read_config(self.get_applet_id())
977                 self.set_width(config[5])
978                 self.set_text(config[9])                
979                 if config[6]:
980                         self.controller.ussd_renew(self, None)
981
982                 self.label.modify_font(config[8])
983                 self.controller.reset_timed_renew()
984                 hildondesktop.HomePluginItem.do_show(self)
985         
986         def error_return (self):
987                 if self.error == 1 and self.processing == 0:
988                         self.set_text(self.text)
989                 return False
990
991         # showfor =
992         #       -1 - This is a permanent text message
993         #       0  - This is service message, but it shouldn't be hidden automatically
994         #       >0 - This is service message, show permament message after showfor milliseconds
995         def set_text(self, text, showfor=-1):
996                 if showfor > 0 :
997                         # Show previous text after 5 seconds
998                         gobject.timeout_add (showfor, self.error_return)
999                 else :
1000                         if showfor == -1 :
1001                                 self.text = text
1002                 
1003                 config = self.controller.get_config()
1004                 self.label.set_text(text)
1005
1006         def get_text(self):
1007                 return self.text
1008
1009         def set_width(self, width):
1010                 if width != 0:
1011                         self.label.set_width_chars (width)
1012                 else :
1013                         self.label.set_width_chars(-1)
1014
1015         def set_bg_color(self, color):
1016                 self.bg_color = color
1017
1018         def get_bg_color(self):
1019                 return self.bg_color
1020
1021         def set_text_color(self, color):
1022                 self.label.modify_fg(gtk.STATE_NORMAL, color)           
1023                 self.text_color = color
1024
1025         def get_text_color(self):
1026                 return self.text_color
1027
1028         def _expose(self, event):
1029                 cr = self.window.cairo_create()
1030
1031                 # draw rounded rect
1032                 width, height = self.label.allocation[2], self.label.allocation[3]
1033
1034                 #/* a custom shape, that could be wrapped in a function */
1035                 x0 = 0   #/*< parameters like cairo_rectangle */
1036                 y0 = 0
1037
1038                 radius = min(15, width/2, height/2)  #/*< and an approximate curvature radius */
1039
1040                 x1 = x0 + width
1041                 y1 = y0 + height
1042
1043                 cr.move_to  (x0, y0 + radius)
1044                 cr.arc (x0 + radius, y0 + radius, radius, 3.14, 1.5 * 3.14)
1045                 cr.line_to (x1 - radius, y0)
1046                 cr.arc (x1 - radius, y0 + radius, radius, 1.5 * 3.14, 0.0)
1047                 cr.line_to (x1 , y1 - radius)
1048                 cr.arc (x1 - radius, y1 - radius, radius, 0.0, 0.5 * 3.14)
1049                 cr.line_to (x0 + radius, y1)
1050                 cr.arc (x0 + radius, y1 - radius, radius, 0.5 * 3.14, 3.14)
1051
1052                 cr.close_path ()
1053
1054                 fg_color = get_color("ActiveTextColor")
1055
1056                 if self.processing :
1057                         bg_color=fg_color
1058                 else :
1059                         bg_color=self.bg_color
1060
1061                 cr.set_source_rgba (bg_color.red / 65535.0, bg_color.green/65535.0, bg_color.blue/65535.0, 0.7)
1062                 cr.fill_preserve ()
1063
1064                 if self.error :
1065                         cr.set_source_rgba (1.0, 0.0, 0.0, 0.5)
1066                 else :
1067                         cr.set_source_rgba (fg_color.red / 65535.0, fg_color.green / 65535.0, fg_color.blue / 65535.0, 0.7)
1068                 cr.stroke ()
1069
1070         def do_expose_event(self, event):
1071                 self.chain(event)
1072                 self._expose (event)
1073                 self.vbox.do_expose_event (self, event)
1074
1075 hd_plugin_type = UssdWidgetPlugin
1076
1077 # The code below is just for testing purposes.
1078 # It allows to run the widget as a standalone process.
1079 if __name__ == "__main__":
1080         plugin_id = "ussd-widget.console"
1081         if len(sys.argv) == 2:
1082                 try:
1083                         plugin_id = "ussd-widget.desktop-"+str(int(sys.argv[1]))
1084                 except:
1085                         print "Plugin id must be integer"
1086                         sys.exit(-1)
1087
1088         import gobject
1089         gobject.type_register(hd_plugin_type)
1090         obj = gobject.new(hd_plugin_type, plugin_id=plugin_id)
1091         obj.show_all()
1092         gtk.main()