79c92d3c1383ea3e150e4285750167cb39627ab5
[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 from dbus.mainloop.glib import DBusGMainLoop
18
19 try :
20         t = gettext.translation('ussd-widget', '/usr/share/locale')
21         _ = t.ugettext
22 except IOError:
23         print "Translation file for your language not found"
24         def retme(arg):
25                 return arg
26         _ = retme
27
28 ussd_languages = ["German", "English", "Italian", "French", "Spanish", "Dutch", "Swedish", "Danish", "Portuguese", "Finnish", "Norwegian", "Greek", "Turkish", "Reserved1", "Reserved2", "Unspecified"]
29 ussd_languages_localized = [_("German"), _("English"), _("Italian"), _("French"), _("Spanish"), _("Dutch"), _("Swedish"), _("Danish"), _("Portuguese"), _("Finnish"), _("Norwegian"), _("Greek"), _("Turkish"), _("Reserved1"), _("Reserved2"), _("Unspecified")]
30
31 # TODO Cutt off too long messages and show them in separate dialog
32 # how TODO widget vertical minimum size policy
33 # TODO interface for queryng from scripts. For example query from one widget can change color of another widget 
34
35 class USSD_Controller:
36         def __init__( self, widget ) :
37                 self.widget = widget
38                 # 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
39                 self.default_config = ["", "", "", 0, "", 0, True, [], pango.FontDescription("Nokia Sans 18"), _("Click to update"), 15, False, "", "", 1, False, "", "", 60]
40                 self.config = self.default_config
41                 self.timeout_version = 0
42                 self.retry_version = 0
43                 self.retry_state = 0
44                 self.sms_counter = 0
45                 self.sms_reply = ""
46
47         def save_config( self ) :
48                 configname = os.getenv("HOME")+"/.ussdWidget.conf"
49                 # Aquire lock
50                 lockf = open(configname+".lock", 'a')
51                 fcntl.flock(lockf,fcntl.LOCK_EX)
52
53                 oldconfig=""
54                 try:
55                         fconfig = open(configname,"r")
56                         #Read configuration of other instances
57                         my_section = False
58                         for line in fconfig :
59                                 if line[0] == '%':
60                                         my_section = line[1:].strip() == self.id
61                                 if not my_section:
62                                         oldconfig += line
63                         fconfig.close()
64                 except:
65                         print _("Couldn't read previous config")
66
67                 fconfig = open(configname,"w")
68                 fconfig.seek(0)
69                 fconfig.write(oldconfig)
70                 fconfig.write("%"+self.id+"\n");
71                 fconfig.writelines(["# USSD query to be run by widget\n", "number="+self.config[0], "\n"])
72                 fconfig.writelines(["#Parser command for widget\n", "parser="+self.config[1], "\n"])
73                 fconfig.writelines(["#Parser command for banner\n", "parser_box="+self.config[12], "\n"])
74                 fconfig.writelines(["#Chain command\n", "chain="+self.config[2], "\n"])
75                 fconfig.writelines(["#Update interval in minutes\n", "interval="+str(self.config[3]), "\n"])
76                 fconfig.writelines(["#RegExp pattern\n", "regexp="+self.config[4], "\n"])
77                 fconfig.writelines(["#Widget width\n", "width="+str(self.config[5]), "\n"])
78                 fconfig.writelines(["#Execute query at start\n", "query_at_start="+str(self.config[6]), "\n"])
79                 fconfig.writelines(["#Retry pattern\n"])
80                 fconfig.write("retry=")
81                 first = True
82                 for i in self.config[7]:
83                         if not first:
84                                 fconfig.write ("-")
85                         fconfig.write(str(i))
86                         first = False
87                 fconfig.write("\n")
88                 fconfig.writelines(["#Font description\n", "font="+self.config[8].to_string(), "\n"])
89                 fconfig.writelines(["#Font color\n", "text_color="+self.widget.get_text_color().to_string(), "\n"])
90                 fconfig.writelines(["#Background color\n", "bg_color="+self.widget.get_bg_color().to_string(), "\n"])
91                 fconfig.writelines(["#Widget name\n", "name="+self.config[9], "\n"])
92                 fconfig.writelines(["#Show banner\n", "show_box="+str(self.config[11]), "\n"])
93                 fconfig.writelines(["#USSD reply language\n", "language="+str(self.config[10]), "\n"])
94                 fconfig.writelines(["#Additional ussdquery.py arguments\n", "args="+self.config[13], "\n"])
95                 fconfig.writelines(["#Regexp matching group\n", "reggroup="+str(self.config[14]), "\n"])
96                 fconfig.writelines(["#Use SMS listener\n", "listen_sms="+str(self.config[15]), "\n"])
97                 fconfig.writelines(["#Number,from which SMS should come\n", "sms_number="+self.config[16], "\n"])
98                 fconfig.writelines(["#SMS RegExp pattern\n", "sms_regexp="+self.config[17], "\n"])
99                 fconfig.writelines(["#SMS timeout\n", "sms_timeout="+str(self.config[18]), "\n"])
100                 fconfig.close()
101
102                 fcntl.flock(lockf,fcntl.LOCK_UN)
103                 lockf.close()
104
105         def get_config(self):
106                 return self.config
107
108         def read_config( self, id ):
109                 try :
110                         self.id = id
111                         config = open(os.getenv("HOME")+"/.ussdWidget.conf","r")
112
113                         error = False
114                         i = 0
115                         my_section = False
116                         for line in config :
117                                 i += 1 
118                                 if line[0] == '#':
119                                         continue
120                                 if line[0] == '%':
121                                         my_section = line[1:].strip() == id
122                                         continue
123
124                                 if not my_section:
125                                         # This is config for another instace
126                                         continue
127
128                                 line=line.split('=', 1)
129                                 
130                                 if len(line) != 2 :
131                                         error = True
132                                         print _("Error reading config on line %(line)d. = or # expected.")%{"line":i}
133                                         continue
134                                 if line[0] == "number" :
135                                         self.config[0] = line[1].strip()
136                                 elif line[0] == "parser" :
137                                         self.config[1] = line[1].strip()
138                                 elif line[0] == "parser_box" :
139                                         self.config[12] = line[1].strip()
140                                 elif line[0] == "chain" :
141                                         self.config[2] = line[1].strip()
142                                 elif line[0] == "interval" :
143                                         try:
144                                                 self.config[3] = int(line[1].strip())
145                                         except:
146                                                 error = True
147                                                 print _("Error reading config on line %(line)d. Integer expected.")%{"line":i}
148                                                 continue
149                                 elif line[0] == "regexp" :
150                                         self.config[4] = line[1].strip()
151                                 elif line[0] == "width" :
152                                         try:
153                                                 self.config[5] = int(line[1].strip())
154                                         except:
155                                                 error = True
156                                                 print _("Error reading config on line %(line)d. Integer expected.")%{"line":i}
157                                                 continue
158                                 elif line[0] == "query_at_start" :
159                                         if line[1].strip() == "True" :
160                                                 self.config[6] = True
161                                         else :
162                                                 self.config[6] = False
163                                 elif line[0] == "retry" :
164                                         line[1] = line[1].strip()
165                                         if line[1] != "":
166                                                 line[1] = line[1].split("-")
167                                                 i = 0
168                                                 while i < len(line[1]) :
169                                                         try:
170                                                                 line[1][i] = int(line[1][i])
171                                                         except:
172                                                                 error = True
173                                                                 print _("Error reading config on line %(line)d. Integer expected.")%{"line":i}
174                                                         i += 1
175                                                 self.config[7] = line[1]
176                                         else:
177                                                 self.config[7] = []
178                                                 continue
179                                 elif line[0] == "font" :
180                                         try:
181                                                 self.config[8] = pango.FontDescription(line[1].strip())
182                                         except:
183                                                 error = True
184                                                 print _("Error reading config on line %(line)d. Pango font description expected.")%{"line":i}
185                                                 continue
186                                 elif line[0] == "bg_color" :
187                                         try:
188                                                 self.widget.set_bg_color(gtk.gdk.color_parse(line[1].strip()))
189                                         except:
190                                                 error = True
191                                                 print _("Error reading config on line %(line)d. Expected color definition.")%{"line":i}
192                                 elif line[0] == "text_color" :
193                                         try:
194                                                 self.widget.set_text_color(gtk.gdk.color_parse(line[1].strip()))
195                                         except:
196                                                 error = True
197                                                 print _("Error reading config on line %(line)d. Expected color definition.")%{"line":i}
198                                 elif line[0] == "name" :
199                                         self.config[9] = line[1].strip()
200                                 elif line[0] == "show_box" :
201                                         if line[1].strip() == "True" :
202                                                 self.config[11] = True
203                                         else :
204                                                 self.config[11] = False
205                                 elif line[0] == "language" :
206                                         try:
207                                                 if int(line[1].strip()) >=0 and int(line[1].strip()) < len(ussd_languages):
208                                                         self.config[10] = int(line[1].strip())
209                                                 else:
210                                                         error = True
211                                                         print _("Error reading config on line %(line)d. Unknown language code.")%{"line":i}
212                                         except:
213                                                 error = True
214                                                 print _("Error reading config on line %(line)d. Integer expected.")%{"line":i}
215                                 elif line[0] == "args" :
216                                         self.config[13] = line[1].strip()
217                                 elif line[0] == "reggroup" :
218                                         try:
219                                                 self.config[14] = int(line[1].strip())
220                                         except:
221                                                 error = True
222                                                 print _("Error reading config on line %(line)d. Integer expected.")%{"line":i}
223                                                 continue
224                                 elif line[0] == "listen_sms" :
225                                         if line[1].strip() == "True" :
226                                                 self.config[15] = True
227                                         else :
228                                                 self.config[15] = False
229                                 elif line[0] == "sms_number" :
230                                         self.config[16] = line[1].strip()
231                                 elif line[0] == "sms_regexp" :
232                                         self.config[17] = line[1].strip()
233                                 elif line[0] == "sms_timeout" :
234                                         try:
235                                                 self.config[18] = int(line[1].strip())
236                                         except:
237                                                 error = True
238                                                 print _("Error reading config on line %(line)d. Integer expected.")%{"line":i}
239                                                 continue
240                                 else :
241                                         error = True
242                                         print _("Error reading config on line %(line)d. Unexpected variable: ")%{"line":i}+line[0]
243                                         continue 
244
245                         config.close()
246
247                         if error :
248                                 self.widget.error = 1
249                                 self.widget.set_text (_("Config error"), 5000)  
250
251                         return self.config
252                 except  IOError:
253                         self.widget.error = 1
254                         self.widget.set_text (_("Config error"), 0)
255                         print _("IO error while reading config")
256
257                         return self.default_config
258
259         def on_show_settings( self, widget ) :
260                 dialog = UssdConfigDialog(self.config, self.widget.get_bg_color(), self.widget.get_text_color())
261
262                 while True:
263                         if dialog.run() != gtk.RESPONSE_OK :
264                                 dialog.destroy()
265                                 return
266
267                         test = check_regexp(dialog.regexp.get_text()) 
268                         if test :
269                                 dialog.on_error_regexp(test)
270                                 continue
271
272                         # Check, that we have ussd number
273                         if not check_number(dialog.ussdNumber.get_text()):
274                                 dialog.on_error_ussd_number()
275                                 continue
276                 
277                         if not check_number(dialog.sms_number.get_text()):
278                                 dialog.on_error_sms_number()
279                                 continue
280
281                         # Parse retry pattern
282                         retry = dialog.retryEdit.get_text().strip()
283                         if retry != "" :
284                                 retry = retry.split("-")
285                                 i = 0
286                                 while i < len(retry) :
287                                         try:
288                                                 retry[i] = int(retry[i])
289                                         except:
290                                                 dialog.on_error_retry_pattern()
291                                                 break 
292                                         i += 1
293                         
294                                 if i < len(retry):
295                                         continue
296                         else :
297                                 retry = []
298
299                         break
300
301                 self.config = [
302                         dialog.ussdNumber.get_text(), 
303                         dialog.parser.get_text(), 
304                         dialog.chain.get_text(), 
305                         dialog.update_interval.get_value(), 
306                         dialog.regexp.get_text(),
307                         dialog.widthEdit.get_value(),
308                         dialog.query_at_start.get_active(),
309                         retry,
310                         dialog.font,
311                         dialog.wname.get_text(),
312                         dialog.language.get_active(),
313                         dialog.show_box.get_active(),
314                         dialog.b_parser.get_text(),
315                         dialog.args.get_text(),
316                         dialog.reggroup.get_value(),
317                         dialog.sms_listener.get_active(),
318                         dialog.sms_number.get_text(),
319                         dialog.sms_regexp.get_text(),
320                         dialog.sms_timeout.get_value() 
321                 ]
322
323                 widget.set_bg_color(dialog.bg_color)
324                 widget.set_text_color(dialog.text_color)
325
326                 self.save_config()
327
328                 widget.set_width(self.config[5])        
329                 self.reset_timed_renew()
330                 self.widget.label.modify_font(self.config[8])
331
332                 dialog.destroy()
333
334                 # Before running this function widget wasn't configured
335                 if self.config == self.default_config:
336                         self.widget.set_text(_("Click to update"))
337                 return
338
339         def handle_sms(self, pdumsg, msgcenter, message, sendernumber):
340                 # Timeout was recieved first
341                 if self.sms_ready:
342                         return
343
344                 if self.config[16] == "" or self.config[16] == sendernumber:
345                         pdu = gsmdecode.decode_pdu (pdumsg)
346                         if pdu != None :
347                                 self.sms_reply += pdu['user_data']
348                                 if not pdu['part']:
349                                         if self.config[17] == "" or re.search( self.config[17], message, re.MULTILINE | re.UNICODE ):
350                                                 self.sms_ready = True
351                                                 self.sms_signal.remove()
352                                                 self.process_reply()
353
354         def callback_ussd_data( self, source, condition ):
355                 if condition == gobject.IO_IN or condition == gobject.IO_PRI :
356                         data = source.read( )
357                         if len( data ) > 0 :
358                                 self.cb_reply += data
359                                 return True
360                         else :
361                                 self.cb_ready = 1
362                                 return False
363
364                 elif condition == gobject.IO_HUP or condition == gobject.IO_ERR :
365                         self.cb_ready = 1
366                         self.ussd_ready = True
367                         self.process_reply()
368                         return False
369
370                 print (_("serious problems in program logic"))
371                 # This will force widget to show error message
372                 self.cb_reply = ""
373                 self.cb_ready = 1
374                 self.process_reply()
375                 return False
376
377
378         def call_external_script( self, ussd_code, language ):
379                 self.cb_ready = 0
380                 self.cb_reply = "";
381                 p = subprocess.Popen(['/usr/bin/ussdquery.py', ussd_code, "-l", ussd_languages[language]] + smart_split_string(self.config[13],"%","&"), stdout=subprocess.PIPE)
382                 gobject.io_add_watch( p.stdout, gobject.IO_IN | gobject.IO_PRI | gobject.IO_HUP | gobject.IO_ERR , self.callback_ussd_data )
383
384         def ussd_renew(self, widget, event):
385                 if self.widget.processing == 0:
386                         if self.config :
387                                 widget.processing = 1
388                                 widget.set_text(_("Processing"), 0)
389
390                                 self.ussd_ready = False
391                                 self.sms_ready = False
392                                 self.sms_reply = ""
393
394                                 if self.config[15]:
395                                         self.sms_counter += 1
396                                         self.retry_timer = gobject.timeout_add (1000*self.config[18], self.sms_timeout, self.sms_counter)
397                                         
398                                         DBusGMainLoop(set_as_default=True)
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):
517                 gtk.Dialog.__init__(self, _("USSD widget"), 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                                 # Delete backslash if it delimites usual numbers from % or &
880                                 if str[i] == '\\' and i < len(str)-1 and str[i+1] in ["1", "2", "3", "4", "5", "6", "7", "8", "9", "0"] :
881                                         continue
882                 if bs == 0 and (str[i] == '"' and qs == 1 or str[i] == "'" and qs == 2) :
883                         qs = 0
884                 elif bs == 0 and qs == 0 and (str[i] == '"' or str[i] == "'") :
885                         if str[i] == '"':
886                                 qs = 1
887                         else :
888                                 qs = 2
889                 elif bs == 0 and str[i] == '\\' :
890                         bs = 1
891                 elif bs == 0 and (str[i] == '%' or str[i] == '&') :
892                         subst = str[i]
893                         num = 0
894                 else :
895                         if bs == 1 and str[i] != '\\' and str[i] != '"' and str[i] != "'" :
896                                 word += "\\"
897                         if qs == 0 and (str[i] == " " or str[i] == "\t") :
898                                 if word != "" :
899                                         result.append(word)
900                                         word = ""
901                         else :
902                                 word += str[i]
903                         bs = 0 
904         
905         if subst == '&':
906                 if reres2 != None and num != 0 and num != -1:
907                         word += reres2.group(num)
908                 else:
909                         word += reply2
910         elif subst == '%':
911                 if reres1 != None and num != 0 and num != -1:
912                         word += reres1.group(num)
913                 else:
914                         word += reply1
915         if word != "" :
916                 result.append(word)
917         return result 
918
919 def check_regexp(regexp):
920         try :
921                 re.compile( regexp )
922         except Exception, e:
923                         return str(e)
924         return False
925
926 def check_number(number):
927         for s in number :
928                 if not (s in ["1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "+", "*", "#", " "]) :
929                         return False
930         return True
931
932 #=============== The widget itself ================
933
934 def get_color(logicalcolorname):
935         settings = gtk.settings_get_default()
936         color_style = gtk.rc_get_style_by_paths(settings, 'GtkButton', 'osso-logical-colors', gtk.Button)
937         return color_style.lookup_color(logicalcolorname)
938
939 class UssdWidgetPlugin(hildondesktop.HomePluginItem):
940         def __init__(self):
941                 hildondesktop.HomePluginItem.__init__(self)
942         
943                 self.processing = 0
944                 self.bg_color=gtk.gdk.color_parse('#000000')
945                 self.text_color=gtk.gdk.color_parse('#ffffff')
946                 self.error = 0
947                 self.timeout_version = 0
948
949                 colormap = self.get_screen().get_rgba_colormap()
950                 self.set_colormap (colormap)
951
952                 self.controller = USSD_Controller(self)
953                  
954 # TODO Click event would be better
955                 self.connect("button-press-event", self.controller.ussd_renew)
956
957                 self.vbox = gtk.HBox()
958                 self.add(self.vbox)
959
960                 self.set_settings(True)
961                 self.connect("show-settings", self.controller.on_show_settings)
962                 self.label = gtk.Label("")
963                 
964                 self.vbox.add(self.label)
965                 self.vbox.set_child_packing(self.label, False, False, 0, gtk.PACK_START)
966                 self.label.set_padding(15, 10)
967                 self.label.set_size_request(-1,-1)
968                 self.set_size_request(-1,-1)
969                 self.label.set_line_wrap (True)
970
971                 self.vbox.show_all()
972
973         def do_show(self):
974                 config = self.controller.read_config(self.get_applet_id())
975                 self.set_width(config[5])
976                 self.set_text(config[9])                
977                 if config[6]:
978                         self.controller.ussd_renew(self, None)
979
980                 self.label.modify_font(config[8])
981                 self.controller.reset_timed_renew()
982                 hildondesktop.HomePluginItem.do_show(self)
983         
984         def error_return (self):
985                 if self.error == 1 and self.processing == 0:
986                         self.set_text(self.text)
987                 return False
988
989         # showfor =
990         #       -1 - This is a permanent text message
991         #       0  - This is service message, but it shouldn't be hidden automatically
992         #       >0 - This is service message, show permament message after showfor milliseconds
993         def set_text(self, text, showfor=-1):
994                 if showfor > 0 :
995                         # Show previous text after 5 seconds
996                         gobject.timeout_add (showfor, self.error_return)
997                 else :
998                         if showfor == -1 :
999                                 self.text = text
1000                 
1001                 config = self.controller.get_config()
1002                 self.label.set_text(text)
1003
1004         def get_text(self):
1005                 return self.text
1006
1007         def set_width(self, width):
1008                 if width != 0:
1009                         self.label.set_width_chars (width)
1010                 else :
1011                         self.label.set_width_chars(-1)
1012
1013         def set_bg_color(self, color):
1014                 self.bg_color = color
1015
1016         def get_bg_color(self):
1017                 return self.bg_color
1018
1019         def set_text_color(self, color):
1020                 self.label.modify_fg(gtk.STATE_NORMAL, color)           
1021                 self.text_color = color
1022
1023         def get_text_color(self):
1024                 return self.text_color
1025
1026         def _expose(self, event):
1027                 cr = self.window.cairo_create()
1028
1029                 # draw rounded rect
1030                 width, height = self.label.allocation[2], self.label.allocation[3]
1031
1032                 #/* a custom shape, that could be wrapped in a function */
1033                 x0 = 0   #/*< parameters like cairo_rectangle */
1034                 y0 = 0
1035
1036                 radius = min(15, width/2, height/2)  #/*< and an approximate curvature radius */
1037
1038                 x1 = x0 + width
1039                 y1 = y0 + height
1040
1041                 cr.move_to  (x0, y0 + radius)
1042                 cr.arc (x0 + radius, y0 + radius, radius, 3.14, 1.5 * 3.14)
1043                 cr.line_to (x1 - radius, y0)
1044                 cr.arc (x1 - radius, y0 + radius, radius, 1.5 * 3.14, 0.0)
1045                 cr.line_to (x1 , y1 - radius)
1046                 cr.arc (x1 - radius, y1 - radius, radius, 0.0, 0.5 * 3.14)
1047                 cr.line_to (x0 + radius, y1)
1048                 cr.arc (x0 + radius, y1 - radius, radius, 0.5 * 3.14, 3.14)
1049
1050                 cr.close_path ()
1051
1052                 fg_color = get_color("ActiveTextColor")
1053
1054                 if self.processing :
1055                         bg_color=fg_color
1056                 else :
1057                         bg_color=self.bg_color
1058
1059                 cr.set_source_rgba (bg_color.red / 65535.0, bg_color.green/65535.0, bg_color.blue/65535.0, 0.7)
1060                 cr.fill_preserve ()
1061
1062                 if self.error :
1063                         cr.set_source_rgba (1.0, 0.0, 0.0, 0.5)
1064                 else :
1065                         cr.set_source_rgba (fg_color.red / 65535.0, fg_color.green / 65535.0, fg_color.blue / 65535.0, 0.7)
1066                 cr.stroke ()
1067
1068         def do_expose_event(self, event):
1069                 self.chain(event)
1070                 self._expose (event)
1071                 self.vbox.do_expose_event (self, event)
1072
1073 hd_plugin_type = UssdWidgetPlugin
1074
1075 # The code below is just for testing purposes.
1076 # It allows to run the widget as a standalone process.
1077 if __name__ == "__main__":
1078         import gobject
1079         gobject.type_register(hd_plugin_type)
1080         obj = gobject.new(hd_plugin_type, plugin_id="plugin_id")
1081         obj.show_all()
1082         gtk.main()