Build files now don't include .svn in deb, fixed spelling in ussd-common and added...
[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(), self.id)
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                                         self.bus = dbus.SystemBus()
400                                         self.sms_signal = self.bus.add_signal_receiver(self.handle_sms, path='/com/nokia/phone/SMS',   dbus_interface='Phone.SMS', signal_name='IncomingSegment')
401
402                                 self.call_external_script( self.config[0], self.config[10] )
403                         else :
404                                 widget.processing = 0
405                                 widget.error = 1
406                                 widget.set_text(_("No config"), 0)
407
408         def process_reply( self ):
409                 if not self.ussd_ready or not self.sms_ready and self.config[15]:
410                         return
411
412                 reply = self.cb_reply.strip()
413                 sms_reply = self.sms_reply.strip()
414                 
415                 if reply == "" or self.config[15] and sms_reply == "" :
416                         self.widget.error = 1
417                         self.widget.set_text (_("Error"), 5000)
418                         if self.retry_state == len(self.config[7]):
419                                 self.retry_version += 1
420                                 self.retry_state = 0
421                         else :
422                                 self.retry_timer = gobject.timeout_add (1000*self.config[7][self.retry_state], self.retry_renew, self.retry_version)
423                                 self.retry_state += 1
424                 else :
425                         self.widget.error = 0
426                         # Apply regexp
427                         reresult1 = reresult2 = None
428                         if self.config[4] != "":
429                                 reresult1 = re.search( self.config[4], reply, re.MULTILINE | re.UNICODE )
430                         if self.config[17] != "":
431                                 reresult2 = re.search( self.config[17], sms_reply, re.MULTILINE | re.UNICODE )
432                         w_reply = b_reply = reply
433                         if self.widget.error == 0:
434                                 # Pass to box parser
435                                 if self.config[12] != "" and self.config[11]:
436                                         try:
437                                                 p = subprocess.Popen(smart_split_string(self.config[12], reply, sms_reply, reresult1, reresult2), stdout=subprocess.PIPE)
438                                                 b_reply = p.communicate()[0].strip()
439                                         except Exception, e:
440                                                 print _("Couldn't exec banner parser:")+str(e)
441                                                 self.widget.error = 1
442                                 else:
443                                         if self.config[4] != "":
444                                                 try :
445                                                         b_reply = reresult1.group( self.config[14] )
446                                                 except Exception, e:
447                                                         self.widget.error = 1
448                                                         b_reply = _("Group not found: \n") + reply
449                                          
450                                 # Pass to widget parser
451                                 if self.config[1] != "":
452                                         try:
453                                                 p = subprocess.Popen(smart_split_string(self.config[1], reply, sms_reply, reresult1, reresult2), stdout=subprocess.PIPE)
454                                                 w_reply = p.communicate()[0].strip()
455                                         except Exception, e:
456                                                 print _("Couldn't exec widget parser:")+str(e)
457                                                 self.widget.error = 1
458                                 else:
459                                         if self.config[4] != "":
460                                                 try :
461                                                         w_reply = reresult1.group( self.config[14] )
462                                                 except Exception, e:
463                                                         self.widget.error = 1
464                                                         w_reply = _("Group not found: \n") + reply
465                                 # Pass to chain
466                                 if self.config[2] != "":
467                                         try:
468                                                 p = subprocess.Popen(smart_split_string(self.config[2], reply, sms_reply, reresult1, reresult2))
469                                         except Exception, e:
470                                                 print _("Couldn't exec chain:")+str(e)
471                                                 self.widget.error = 1
472                         if self.config[11]:
473                                 banner = hildon.hildon_banner_show_information (self.widget, "", b_reply)
474                                 banner.set_timeout (5000)
475                                 b_reply
476                         self.widget.set_text(w_reply)
477                 self.widget.processing = 0
478
479         def sms_timeout(self, version):
480                 if version == self.sms_counter :
481                         self.sms_reply = ""
482                         self.sms_ready = True
483                         self.sms_signal.remove()
484                         self.process_reply()
485                 return False
486
487         def timed_renew(self, version):
488                 if version < self.timeout_version :
489                         return False
490                 self.ussd_renew(self.widget, None)
491                 return True
492
493         def retry_renew(self,version):
494                 if self.widget.error == 0 or self.widget.processing == 1 or version < self.retry_version :
495                         return False
496                 self.ussd_renew(self.widget, None)
497                 return False
498
499         def reset_timed_renew (self) :
500                 self.timeout_version += 1
501                 if self.config[3] != 0 :
502                         self.timer = gobject.timeout_add (60000*self.config[3], self.timed_renew, self.timeout_version)
503
504 class pHelpDialog(gtk.Dialog):
505         def __init__(self, heading, text):
506                 gtk.Dialog.__init__(self, heading, None,
507                         gtk.DIALOG_DESTROY_WITH_PARENT | gtk.DIALOG_NO_SEPARATOR,
508                         (_("OK").encode("utf-8"), gtk.RESPONSE_OK))
509                 label = gtk.Label(text)
510                 label.set_line_wrap (True)
511                 self.vbox.add(label)
512                 self.show_all()
513                 self.parent
514
515 class UssdConfigDialog(gtk.Dialog):
516         def __init__(self, config, bg_color, text_color, id):
517                 gtk.Dialog.__init__(self, _("USSD widget : "+id), None, 
518                         gtk.DIALOG_DESTROY_WITH_PARENT | gtk.DIALOG_NO_SEPARATOR,
519                         (_("Save").encode("utf-8"), gtk.RESPONSE_OK))
520
521                 self.font = config[8]
522                 self.bg_color = bg_color
523                 self.text_color = text_color
524
525                 self.set_size_request(-1, 400)
526                 self.ussdNumber = hildon.Entry(gtk.HILDON_SIZE_AUTO)
527                 self.ussdNumber.set_text(config[0])
528                 self.parser = hildon.Entry(gtk.HILDON_SIZE_AUTO)
529                 self.parser.set_text(config[1])
530                 self.b_parser = hildon.Entry(gtk.HILDON_SIZE_AUTO)
531                 self.b_parser.set_text(config[12])
532
533                 self.chain = hildon.Entry(gtk.HILDON_SIZE_AUTO)
534                 self.chain.set_text(config[2])
535                 self.update_interval = hildon.NumberEditor(0, 9999)
536                 self.update_interval.set_value(config[3])
537                 self.regexp = hildon.Entry(gtk.HILDON_SIZE_AUTO)
538                 self.regexp.set_text(config[4])
539                 self.widthEdit = hildon.NumberEditor(0, 1000)
540                 self.widthEdit.set_value(config[5])
541                 self.retryEdit = hildon.Entry(gtk.HILDON_SIZE_AUTO)
542                 self.args = hildon.Entry(gtk.HILDON_SIZE_AUTO)
543                 self.args.set_text(config[13])
544                 self.reggroup = hildon.NumberEditor(0, 255)
545                 self.reggroup.set_value(config[14])
546                 
547                 selector = hildon.TouchSelector(text=True)
548                 for i in ussd_languages_localized:
549                         selector.append_text(i)
550                 self.language = hildon.PickerButton(gtk.HILDON_SIZE_AUTO, hildon.BUTTON_ARRANGEMENT_HORIZONTAL)
551                 self.language.set_selector(selector)
552                 self.language.set_active(config[10])
553                 self.language.set_title(_("USSD reply language"))
554                 self.language.set_size_request(-1, -1)
555
556                 self.wname = hildon.Entry(gtk.HILDON_SIZE_AUTO)
557                 self.wname.set_text(config[9])
558                 self.show_box = gtk.CheckButton(_("Enable banner. Parser:"))
559                 self.show_box.connect("toggled", self.show_box_changed)
560                 self.show_box.set_active(config[11])
561
562                 text = ""
563                 for i in config[7]:
564                         if text != "":
565                                 text += "-"
566                         text += str(i)
567                 self.retryEdit.set_text(text)
568
569                 self.query_at_start = gtk.CheckButton(_("Execute query on start"))
570                 self.query_at_start.set_active(config[6])
571
572                 self.fontButton = gtk.Button(_("Font"))
573                 self.fontButton.connect("clicked", self.on_show_font_selection)
574
575                 self.colorButton = gtk.Button(_("Background color"))
576                 self.colorButton.connect("clicked", self.on_show_color_selection)
577                 self.textColorButton = gtk.Button(_("Text color"))
578                 self.textColorButton.connect("clicked", self.on_show_text_color_selection)
579                 
580                 phelp = gtk.Button("?")
581                 phelp.connect("clicked", self.on_show_phelp)
582                 
583                 bphelp = gtk.Button("?")
584                 bphelp.connect("clicked", self.on_show_bphelp)
585
586                 chelp = gtk.Button("?")
587                 chelp.connect("clicked", self.on_show_chelp)
588
589                 reghelp = gtk.Button("?")
590                 reghelp.connect("clicked", self.on_show_reghelp)
591
592                 retryhelp = gtk.Button("?")
593                 retryhelp.connect("clicked", self.on_show_retryhelp)
594                 
595                 numberhelp = gtk.Button("?")
596                 numberhelp.connect("clicked", self.on_show_number_help)
597
598                 area = hildon.PannableArea()
599                 self.vbox.add(area)
600                 vbox = gtk.VBox()
601                 area.add_with_viewport(vbox)
602                 
603                 numberBox = gtk.HBox()
604                 numberLabel = gtk.Label(_("USSD number"))
605                 numberLabel.set_alignment(0,0.6)
606                 numberLabel.set_size_request(100, -1)
607                 numberhelp.set_size_request(1, -1)
608                 self.ussdNumber.set_size_request(200, -1)
609                 numberBox.add(numberLabel)
610                 numberBox.add(numberhelp)
611                 numberBox.add(self.ussdNumber)
612                 vbox.add(numberBox)
613
614                 vbox.add(self.query_at_start)
615
616                 nameBox = gtk.HBox()
617                 nameLabel = gtk.Label(_("Name"))
618                 nameLabel.set_alignment(0,0.6)
619                 nameLabel.set_size_request(100, -1)
620                 self.wname.set_size_request(200, -1)
621                 nameBox.add(nameLabel)
622                 nameBox.add(self.wname)
623                 vbox.add(nameBox)
624
625                 parserBox = gtk.HBox()
626                 parserLabel = gtk.Label(_("Parser for widget"))
627                 parserLabel.set_alignment(0,0.6)
628                 parserLabel.set_size_request(200, -1)
629                 phelp.set_size_request(10, -1)
630                 parserBox.add(parserLabel)
631                 parserBox.add(phelp)
632                 vbox.add(parserBox)
633                 vbox.add(self.parser)
634                 
635                 b_parserBox = gtk.HBox()
636                 self.show_box.set_size_request(200, -1)
637                 bphelp.set_size_request(10, -1)
638                 b_parserBox.add(self.show_box)
639                 b_parserBox.add(bphelp)
640                 vbox.add(b_parserBox)
641                 vbox.add(self.b_parser)
642                 
643                 chainBox = gtk.HBox()
644                 chainLabel = gtk.Label(_("Chain"))
645                 chainLabel.set_alignment(0,0.6)
646                 chainLabel.set_size_request(200, -1)
647                 chelp.set_size_request(10, -1)
648                 chainBox.add(chainLabel)
649                 chainBox.add(chelp)
650                 vbox.add(chainBox)
651                 vbox.add(self.chain)
652
653                 regexpBox = gtk.HBox()
654                 regexpLabel = gtk.Label(_("Regular expression"))
655                 regexpLabel.set_alignment(0,0.6)
656                 regexpLabel.set_size_request(200, -1)
657                 regexpGroupLabel = gtk.Label(_("Group"))
658                 regexpGroupLabel.set_size_request(1, -1)
659                 reghelp.set_size_request(10, -1)
660                 regexpBox.add(regexpLabel)
661                 regexpBox.add(reghelp)
662                 regexpBox.add(regexpGroupLabel)
663                 vbox.add(regexpBox)
664                 self.reggroup.set_size_request(1,-1);
665                 self.regexp.set_size_request(250,-1);
666                 regexpInputBox = gtk.HBox()
667                 regexpInputBox.add(self.regexp)
668                 regexpInputBox.add(self.reggroup)
669                 vbox.add(regexpInputBox)                
670
671                 widthBox = gtk.HBox()
672                 widthLabel = gtk.Label(_("Max. width"))
673                 widthLabel.set_alignment(0,0.6)
674                 symbolsLabel = gtk.Label(_("symbols"))
675                 widthLabel.set_size_request(140, -1)
676                 self.widthEdit.set_size_request(50, -1)
677                 symbolsLabel.set_size_request(40,-1)
678                 widthBox.add(widthLabel)
679                 widthBox.add(self.widthEdit)
680                 widthBox.add(symbolsLabel)
681                 vbox.add(widthBox)
682
683                 updateBox = gtk.HBox()
684                 updateLabel = gtk.Label(_("Update every"))
685                 updateLabel.set_alignment(0,0.6)
686                 minutesLabel = gtk.Label(_("minutes"))
687                 updateLabel.set_size_request(140, -1)
688                 self.update_interval.set_size_request(50, -1)
689                 minutesLabel.set_size_request(40, -1)
690                 updateBox.add(updateLabel)
691                 updateBox.add(self.update_interval)
692                 updateBox.add(minutesLabel)
693                 vbox.add(updateBox)
694
695                 retryBox = gtk.HBox()
696                 retryLabel = gtk.Label(_("Retry pattern"))
697                 retryLabel.set_alignment(0,0.6)
698                 retryLabel.set_size_request(200, -1)
699                 retryhelp.set_size_request(10, -1)
700                 retryBox.add(retryLabel)
701                 retryBox.add(retryhelp)
702                 vbox.add(retryBox)
703                 vbox.add(self.retryEdit)                
704                 
705                 argsLabel = gtk.Label(_("Additional ussdquery.py options"))
706                 argsLabel.set_alignment(0,0.6)
707                 vbox.add(argsLabel)
708                 vbox.add(self.args)             
709                 
710                 viewBox = gtk.HBox()
711                 viewBox.add(self.fontButton)
712                 viewBox.add(self.textColorButton)
713                 viewBox.add(self.colorButton)
714                 vbox.add(viewBox)
715         
716                 self.sms_box = gtk.VBox()       
717                 self.sms_listener = gtk.CheckButton(_("Enable SMS listener."))
718                 self.sms_listener.connect("toggled", self.sms_box_changed)
719                 self.sms_listener.set_active(config[15])
720                 vbox.add (self.sms_listener)
721
722                 self.sms_number = hildon.Entry(gtk.HILDON_SIZE_AUTO)
723                 self.sms_number.set_text(config[16])
724                 smsNumberBox = gtk.HBox()
725                 smsNumberLabel = gtk.Label(_("SMS number"))
726                 smsNumberLabel.set_alignment(0,0.6)
727                 smsNumberLabel.set_size_request(100, -1)
728                 self.sms_number.set_size_request(200, -1)
729                 smsNumberBox.add(smsNumberLabel)
730                 smsNumberBox.add(self.sms_number)
731                 self.sms_box.add(smsNumberBox)
732                 
733                 smsRegexpLabel = gtk.Label(_("Regular expression"))
734                 smsRegexpLabel.set_alignment(0,0.6)
735                 self.sms_box.add(smsRegexpLabel)
736                 
737                 self.sms_regexp = hildon.Entry(gtk.HILDON_SIZE_AUTO)
738                 self.sms_regexp.set_text(config[17])
739                 self.sms_box.add(self.sms_regexp)
740
741                 self.sms_timeout = hildon.NumberEditor(0, 9999)
742                 self.sms_timeout.set_value(config[18])
743                 sms_timeout_box = gtk.HBox()
744                 timeoutLabel = gtk.Label(_("Timeout"))
745                 timeoutLabel.set_alignment(0,0.6)
746                 secondsLabel = gtk.Label(_("seconds"))
747                 timeoutLabel.set_size_request(140, -1)
748                 self.sms_timeout.set_size_request(50, -1)
749                 secondsLabel.set_size_request(40, -1)
750                 sms_timeout_box.add(timeoutLabel)
751                 sms_timeout_box.add(self.sms_timeout)
752                 sms_timeout_box.add(secondsLabel)
753                 self.sms_box.add(sms_timeout_box)
754                 
755                 vbox.add(self.sms_box)
756
757                 vbox.add(gtk.Label(_("DO NOT CHANGE. Unspecified is what you want.")))
758                 vbox.add(self.language)
759
760                 self.show_all()
761                 self.show_box_changed(None)
762                 self.sms_box_changed(None)
763                 self.parent
764
765         #============ Dialog helper functions =============
766         def on_show_phelp(self, widget):
767                 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."))
768                 dialog.run()
769                 dialog.destroy()
770
771         def on_show_bphelp(self, widget):
772                 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."))
773                 dialog.run()
774                 dialog.destroy()
775         
776         def on_show_chelp(self, widget):
777                 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"))
778                 dialog.run()
779                 dialog.destroy()
780
781         def on_show_reghelp(self, widget):
782                 dialog = pHelpDialog(_("Format help"), _("Standard python regexps. Use\n (.+?[\d\,\.]+)\n to delete everything after first number."))
783                 dialog.run()
784                 dialog.destroy()
785
786         def on_show_retryhelp(self, widget):
787                 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\""))
788                 dialog.run()
789                 dialog.destroy()
790         
791         def on_show_number_help(self, widget):
792                 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."))
793                 dialog.run()
794                 dialog.destroy()
795         
796         def on_error_regexp(self, error):
797                 dialog = pHelpDialog(_("Regexp syntax error"), error )
798                 dialog.run()
799                 dialog.destroy()
800
801         def on_error_ussd_number(self):
802                 dialog = pHelpDialog(_("Incorrect USSD number"), _("USSD number should contain only digits, +, * or #") )
803                 dialog.run()
804                 dialog.destroy()
805
806         def on_error_retry_pattern(self):
807                 dialog = pHelpDialog(_("Incorrect retry pattern"), _("Retry pattern should contain only numbers, delimited by -") )
808                 dialog.run()
809                 dialog.destroy()
810
811         def on_show_color_selection (self, event):
812                 colorDialog = gtk.ColorSelectionDialog(_("Choose background color"))
813                 colorDialog.colorsel.set_current_color(self.bg_color)
814                 if colorDialog.run() == gtk.RESPONSE_OK :
815                         self.bg_color = colorDialog.colorsel.get_current_color()
816                 colorDialog.destroy()
817
818         def on_show_text_color_selection (self, event):
819                 colorDialog = gtk.ColorSelectionDialog(_("Choose text color"))
820                 colorDialog.colorsel.set_current_color(self.text_color)
821                 if colorDialog.run() == gtk.RESPONSE_OK :
822                         self.text_color = colorDialog.colorsel.get_current_color()
823                 colorDialog.destroy()
824         
825         def on_show_font_selection (self, event):
826                 fontDialog = gtk.FontSelectionDialog(_("Choose a font"))
827                 fontDialog.set_font_name(self.font.to_string())
828
829                 if fontDialog.run() != gtk.RESPONSE_OK :
830                         fontDialog.destroy()
831                         return
832
833                 self.font = pango.FontDescription (fontDialog.get_font_name())
834                 fontDialog.destroy()
835
836         def show_box_changed (self, event):
837                 if self.show_box.get_active():
838                         self.b_parser.show()
839                 else:
840                         self.b_parser.hide()
841
842         def sms_box_changed (self, event):
843                 if self.sms_listener.get_active():
844                         self.sms_box.show()
845                 else:
846                         self.sms_box.hide()
847
848 def smart_split_string (str, reply1, reply2, reres1 = None, reres2 = None) :
849         word = ""
850         result = []
851         # Is simbol backslashed?
852         bs = 0
853         # Quotes: 1 - ", 2 - ', 0 - no quotes
854         qs = 0
855         # Read out number
856         num = -1
857         # Current substitution simbol
858         subst = ''
859
860         for i in range(len(str)) :
861                 if num>= 0:
862                         if str[i] in ["1", "2", "3", "4", "5", "6", "7", "8", "9", "0"] :
863                                 num *= 10
864                                 num += int(str[i])
865                                 continue
866                         else:
867                                 if subst == '&':
868                                         if reres2 != None and num != 0:
869                                                 word += reres2.group(num)
870                                         else:
871                                                 word += reply2
872                                 else:
873                                         if reres1 != None and num != 0:
874                                                 word += reres1.group(num)
875                                         else:
876                                                 word += reply1
877                                 ws = 0
878                                 num = -1
879                                 subst = ''
880                                 # Delete backslash if it delimites usual numbers from % or &
881                                 if str[i] == '\\' and i < len(str)-1 and str[i+1] in ["1", "2", "3", "4", "5", "6", "7", "8", "9", "0"] :
882                                         continue
883                 if bs == 0 and (str[i] == '"' and qs == 1 or str[i] == "'" and qs == 2) :
884                         qs = 0
885                 elif bs == 0 and qs == 0 and (str[i] == '"' or str[i] == "'") :
886                         if str[i] == '"':
887                                 qs = 1
888                         else :
889                                 qs = 2
890                 elif bs == 0 and str[i] == '\\' :
891                         bs = 1
892                 elif bs == 0 and (str[i] == '%' or str[i] == '&') :
893                         subst = str[i]
894                         num = 0
895                 else :
896                         if bs == 1 and str[i] != '\\' and str[i] != '"' and str[i] != "'" :
897                                 word += "\\"
898                         if qs == 0 and (str[i] == " " or str[i] == "\t") :
899                                 if word != "" :
900                                         result.append(word)
901                                         word = ""
902                         else :
903                                 word += str[i]
904                         bs = 0 
905         
906         if subst == '&':
907                 if reres2 != None and num != 0 and num != -1:
908                         word += reres2.group(num)
909                 else:
910                         word += reply2
911         elif subst == '%':
912                 if reres1 != None and num != 0 and num != -1:
913                         word += reres1.group(num)
914                 else:
915                         word += reply1
916         if word != "" :
917                 result.append(word)
918         return result 
919
920 def check_regexp(regexp):
921         try :
922                 re.compile( regexp )
923         except Exception, e:
924                         return str(e)
925         return False
926
927 def check_number(number):
928         for s in number :
929                 if not (s in ["1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "+", "*", "#", " "]) :
930                         return False
931         return True
932
933 #=============== The widget itself ================
934
935 def get_color(logicalcolorname):
936         settings = gtk.settings_get_default()
937         color_style = gtk.rc_get_style_by_paths(settings, 'GtkButton', 'osso-logical-colors', gtk.Button)
938         return color_style.lookup_color(logicalcolorname)
939
940 class UssdWidgetPlugin(hildondesktop.HomePluginItem):
941         def __init__(self):
942                 hildondesktop.HomePluginItem.__init__(self)
943         
944                 self.processing = 0
945                 self.bg_color=gtk.gdk.color_parse('#000000')
946                 self.text_color=gtk.gdk.color_parse('#ffffff')
947                 self.error = 0
948                 self.timeout_version = 0
949
950                 colormap = self.get_screen().get_rgba_colormap()
951                 self.set_colormap (colormap)
952
953                 self.controller = USSD_Controller(self)
954                  
955 # TODO Click event would be better
956                 self.connect("button-press-event", self.controller.ussd_renew)
957
958                 self.vbox = gtk.HBox()
959                 self.add(self.vbox)
960
961                 self.set_settings(True)
962                 self.connect("show-settings", self.controller.on_show_settings)
963                 self.label = gtk.Label("")
964                 
965                 self.vbox.add(self.label)
966                 self.vbox.set_child_packing(self.label, False, False, 0, gtk.PACK_START)
967                 self.label.set_padding(15, 10)
968                 self.label.set_size_request(-1,-1)
969                 self.set_size_request(-1,-1)
970                 self.label.set_line_wrap (True)
971
972                 self.vbox.show_all()
973
974                 DBusGMainLoop(set_as_default=True)
975                 bus = dbus.SystemBus()
976                 signal = bus.add_signal_receiver(self.set_bg_color_text, path='/su/kibergus/ussd_widget',   dbus_interface='su.kibergus.ussd_widget', signal_name='set_bg_color')
977                 signal = bus.add_signal_receiver(self.set_text_color_text, path='/su/kibergus/ussd_widget',   dbus_interface='su.kibergus.ussd_widget', signal_name='set_text_color')
978                 signal = bus.add_signal_receiver(self.ussd_renew, path='/su/kibergus/ussd_widget',   dbus_interface='su.kibergus.ussd_widget', signal_name='renew')
979
980         def do_show(self):
981                 config = self.controller.read_config(self.get_applet_id())
982                 self.set_width(config[5])
983                 self.set_text(config[9])                
984                 if config[6]:
985                         self.controller.ussd_renew(self, None)
986
987                 self.label.modify_font(config[8])
988                 self.controller.reset_timed_renew()
989                 hildondesktop.HomePluginItem.do_show(self)
990         
991         def error_return (self):
992                 if self.error == 1 and self.processing == 0:
993                         self.set_text(self.text)
994                 return False
995
996         # showfor =
997         #       -1 - This is a permanent text message
998         #       0  - This is service message, but it shouldn't be hidden automatically
999         #       >0 - This is service message, show permament message after showfor milliseconds
1000         def set_text(self, text, showfor=-1):
1001                 if showfor > 0 :
1002                         # Show previous text after 5 seconds
1003                         gobject.timeout_add (showfor, self.error_return)
1004                 else :
1005                         if showfor == -1 :
1006                                 self.text = text
1007                 
1008                 config = self.controller.get_config()
1009                 self.label.set_text(text)
1010
1011         def get_text(self):
1012                 return self.text
1013
1014         def set_width(self, width):
1015                 if width != 0:
1016                         self.label.set_width_chars (width)
1017                 else :
1018                         self.label.set_width_chars(-1)
1019
1020         def ussd_renew(self, id):
1021                 if id == self.get_applet_id():
1022                         self.controller.ussd_renew(self, None)
1023
1024         def set_bg_color_text(self, id, color):
1025                 if id == self.get_applet_id():
1026                         try :
1027                                 self.set_bg_color(gtk.gdk.color_parse(color.strip()))
1028                         except:
1029                                 print _("Unable to parse colour specification")
1030                         self.queue_draw()
1031
1032         def set_text_color_text(self, id, color):
1033                 if id == self.get_applet_id():
1034                         try:
1035                                 self.set_text_color(gtk.gdk.color_parse(color.strip()))
1036                         except:
1037                                 print _("Unable to parse colour specification")
1038                         self.queue_draw()
1039
1040         def set_bg_color(self, color):
1041                 self.bg_color = color
1042
1043         def get_bg_color(self):
1044                 return self.bg_color
1045
1046         def set_text_color(self, color):
1047                 self.label.modify_fg(gtk.STATE_NORMAL, color)           
1048                 self.text_color = color
1049
1050         def get_text_color(self):
1051                 return self.text_color
1052
1053         def _expose(self, event):
1054                 cr = self.window.cairo_create()
1055
1056                 # draw rounded rect
1057                 width, height = self.label.allocation[2], self.label.allocation[3]
1058
1059                 #/* a custom shape, that could be wrapped in a function */
1060                 x0 = 0   #/*< parameters like cairo_rectangle */
1061                 y0 = 0
1062
1063                 radius = min(15, width/2, height/2)  #/*< and an approximate curvature radius */
1064
1065                 x1 = x0 + width
1066                 y1 = y0 + height
1067
1068                 cr.move_to  (x0, y0 + radius)
1069                 cr.arc (x0 + radius, y0 + radius, radius, 3.14, 1.5 * 3.14)
1070                 cr.line_to (x1 - radius, y0)
1071                 cr.arc (x1 - radius, y0 + radius, radius, 1.5 * 3.14, 0.0)
1072                 cr.line_to (x1 , y1 - radius)
1073                 cr.arc (x1 - radius, y1 - radius, radius, 0.0, 0.5 * 3.14)
1074                 cr.line_to (x0 + radius, y1)
1075                 cr.arc (x0 + radius, y1 - radius, radius, 0.5 * 3.14, 3.14)
1076
1077                 cr.close_path ()
1078
1079                 fg_color = get_color("ActiveTextColor")
1080
1081                 if self.processing :
1082                         bg_color=fg_color
1083                 else :
1084                         bg_color=self.bg_color
1085
1086                 cr.set_source_rgba (bg_color.red / 65535.0, bg_color.green/65535.0, bg_color.blue/65535.0, 0.7)
1087                 cr.fill_preserve ()
1088
1089                 if self.error :
1090                         cr.set_source_rgba (1.0, 0.0, 0.0, 0.5)
1091                 else :
1092                         cr.set_source_rgba (fg_color.red / 65535.0, fg_color.green / 65535.0, fg_color.blue / 65535.0, 0.7)
1093                 cr.stroke ()
1094
1095         def do_expose_event(self, event):
1096                 self.chain(event)
1097                 self._expose (event)
1098                 self.vbox.do_expose_event (self, event)
1099
1100 hd_plugin_type = UssdWidgetPlugin
1101
1102 # The code below is just for testing purposes.
1103 # It allows to run the widget as a standalone process.
1104 if __name__ == "__main__":
1105         plugin_id = "ussd-widget.console"
1106         if len(sys.argv) == 2:
1107                 try:
1108                         plugin_id = "ussd-widget.desktop-"+str(int(sys.argv[1]))
1109                 except:
1110                         print "Plugin id must be integer"
1111                         sys.exit(-1)
1112
1113         import gobject
1114         gobject.type_register(hd_plugin_type)
1115         obj = gobject.new(hd_plugin_type, plugin_id=plugin_id)
1116         obj.show_all()
1117         gtk.main()