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