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