bb7e9063280d0a2d17bea947d31c59fe98f5ca5c
[iramuteq] / functions.py
1 #!/bin/env python
2 # -*- coding: utf-8 -*-
3 #Author: Pierre Ratinaud
4 #Copyright (c) 2008-2012 Pierre Ratinaud
5 #Lisense: GNU/GPL
6
7 import wx
8 import re
9 from ConfigParser import ConfigParser
10 from subprocess import Popen, call, PIPE
11 import thread
12 import os
13 import ast
14 import sys
15 import csv
16 import platform
17 import traceback
18 import codecs
19 import locale
20 import datetime
21 from copy import copy
22 from shutil import copyfile
23 #from dialog import BugDialog
24 import logging
25
26 log = logging.getLogger('iramuteq')
27
28
29 indices_simi = [u'cooccurrence' ,'pourcentage de cooccurrence',u'Russel',u'Jaccard', 'Kulczynski1', 'Kulczynski2', 'Mountford', 'Fager', 'simple matching', 'Hamman', 'Faith', 'Tanimoto', 'Dice', 'Phi', 'Stiles', 'Michael', 'Mozley', 'Yule', 'Yule2', 'Ochiai', 'Simpson', 'Braun-Blanquet','Chi-squared', 'Phi-squared', 'Tschuprow', 'Cramer', 'Pearson', 'binomial']
30
31
32 class History :
33     def __init__(self, filein, syscoding = 'utf8') :
34         self.filein = filein
35         self.syscoding = syscoding
36         self.corpora = {}
37         self.openedcorpus = {}
38         self.analyses = {}
39         self.history = {}
40         self.opened = {}
41         self.read()
42
43     def read(self) :
44         self.conf = DoConf(self.filein)
45         self.order = {}
46         self.ordera = {}
47         for i, section in enumerate(self.conf.conf.sections()) :
48             if self.conf.conf.has_option(section, 'corpus_name') :
49                 self.corpora[section] = self.conf.getoptions(section)
50                 self.order[len(self.order)] = section
51             else :
52                 self.analyses[section] = self.conf.getoptions(section)
53                 self.ordera[len(self.ordera)] = section
54         todel = []
55         for corpus in self.corpora :
56             self.history[corpus] = copy(self.corpora[corpus])
57         for analyse in self.analyses :
58             if self.analyses[analyse]['corpus'] in self.corpora :
59                 if 'analyses' in self.history[self.analyses[analyse]['corpus']] :
60                     self.history[self.analyses[analyse]['corpus']]['analyses'].append(self.analyses[analyse])
61                     todel.append(analyse)
62                 else :
63                     self.history[self.analyses[analyse]['corpus']]['analyses'] = [self.analyses[analyse]]
64                     todel.append(analyse)
65             else :
66                  self.history[analyse] = self.analyses[analyse]
67         #for analyse in todel :
68         #    del self.analyses[analyse]
69     
70     def write(self) :
71         sections = self.corpora.keys() + self.analyses.keys()
72         parametres = [self.corpora[key] for key in self.corpora.keys() if key != 'analyses'] + [self.analyses[key] for key in self.analyses.keys()]
73         self.conf.makeoptions(sections, parametres)
74         log.info('write history')
75
76     def add(self, analyse) :
77         tosave = {'uuid' : analyse['uuid'], 'ira': analyse['ira'], 'type' : analyse['type']}
78         if analyse.get('corpus', False) :
79             tosave['corpus'] = analyse['corpus']
80             tosave['name'] = analyse['name']
81             acorpus_uuid =  analyse['corpus']
82             if acorpus_uuid in self.corpora :
83                 if 'analyses' in self.history[acorpus_uuid] :
84                     self.history[acorpus_uuid]['analyses'].append(tosave)
85                 else :
86                     self.history[acorpus_uuid]['analyses'] = [tosave]
87                 self.analyses[analyse['uuid']] = tosave
88             else :
89                 self.analyses[analyse['uuid']] = tosave
90         elif 'corpus_name' in analyse :
91             tosave['corpus_name'] = analyse['corpus_name']
92             self.history[analyse['uuid']] = tosave
93             self.corpora[analyse['uuid']] = tosave
94         self.write()
95
96     def delete(self, uuid, corpus = False) :
97         if corpus :
98             del self.corpora[uuid]
99             self.conf.conf.remove_section(uuid)
100             for analyse in self.history[uuid].get('analyses', [False]) :
101                 if analyse :
102                     del self.analyses[analyse['uuid']]
103                     self.conf.conf.remove_section(analyse['uuid'])
104         else :
105             del self.analyses[uuid]
106             self.conf.conf.remove_section(uuid)
107         self.write()
108
109     def addtab(self, analyse) :
110         self.opened[analyse['uuid']] = analyse
111
112     def rmtab(self, analyse) :
113         del self.opened[analyse['uuid']]
114     
115     def __str__(self) :
116         return str(self.history)
117
118 class DoConf :
119     def __init__(self, configfile=None, diff = None, parametres = None) :
120         self.configfile = configfile
121         self.conf = ConfigParser()
122         if configfile is not None :
123             self.conf.readfp(codecs.open(configfile, 'r', 'utf8'))
124         self.parametres = {}
125         if parametres is not None :
126             self.doparametres(parametres)
127
128     def doparametres(self, parametres) :
129         return parametres
130
131     def getsections(self) :
132         return self.conf.sections()
133
134     def getoptions(self, section = None, diff = None):
135         parametres = {}
136         if section is None :
137             section = self.conf.sections()[0]
138         for option in self.conf.options(section) :
139             if self.conf.get(section, option).isdigit() :
140                 parametres[option] = int(self.conf.get(section, option))
141             elif self.conf.get(section, option) == 'False' :
142                 parametres[option] = False
143             elif self.conf.get(section, option) == 'True' :
144                 parametres[option] = True
145             elif self.conf.get(section, option).startswith('(') and self.conf.get(section, option).endswith(')') :
146                 parametres[option] = ast.literal_eval(self.conf.get(section, option))
147             else :
148                 parametres[option] = self.conf.get(section, option)
149         if 'type' not in parametres :
150             parametres['type'] = section
151         return parametres
152             
153     def makeoptions(self, sections, parametres, outfile = None) :
154         for i, section in enumerate(sections) :
155             if not self.conf.has_section(section) :
156                 self.conf.add_section(section)
157             for option in parametres[i] :
158                 if isinstance(parametres[i][option], int) :
159                     self.conf.set(section, option, `parametres[i][option]`)
160                 elif isinstance(parametres[i][option], basestring) :
161                     self.conf.set(section, option, parametres[i][option].encode('utf8'))
162                 elif isinstance(parametres[i][option], wx.Colour) :
163                     self.conf.set(section, option, str(parametres[i][option]))
164                 else :
165                     self.conf.set(section, option, `parametres[i][option]`)
166         if outfile is None :
167             outfile = self.configfile
168         with codecs.open(outfile, 'w', 'utf8') as f :
169             self.conf.write(f)
170
171     def totext(self, parametres) :
172         txt = ['Corpus']
173         for val in parametres :
174             if isinstance(parametres[val], int) :
175                 txt.append(' \t\t: '.join([val, `parametres[val]`]))
176             else :
177                 txt.append(' \t\t: '.join([val, parametres[val]]))
178         return '\n'.join(txt)
179
180
181 def write_tab(tab, fileout) :
182         writer = csv.writer(open(fileout, 'wb'), delimiter=';', quoting = csv.QUOTE_NONNUMERIC)
183         writer.writerows(tab)
184
185 class BugDialog(wx.Dialog):
186     def __init__(self, *args, **kwds):
187         # begin wxGlade: MyDialog.__init__
188         kwds["style"] = wx.DEFAULT_DIALOG_STYLE
189         kwds["size"] = wx.Size(500, 200)
190         wx.Dialog.__init__(self, *args, **kwds)
191         self.text_ctrl_1 = wx.TextCtrl(self, -1, "", style=wx.TE_MULTILINE)
192         self.button_1 = wx.Button(self, wx.ID_OK, "")
193
194         self.__set_properties()
195         self.__do_layout()
196         # end wxGlade
197
198     def __set_properties(self):
199         # begin wxGlade: MyDialog.__set_properties
200         self.SetTitle("Bug")
201         self.SetMinSize(wx.Size(500, 200))
202         self.text_ctrl_1.SetMinSize(wx.Size(500, 200))
203         
204         # end wxGlade
205
206     def __do_layout(self):
207         # begin wxGlade: MyDialog.__do_layout
208         sizer_1 = wx.BoxSizer(wx.VERTICAL)
209         sizer_1.Add(self.text_ctrl_1, 1, wx.EXPAND, 0)
210         sizer_1.Add(self.button_1, 0, wx.ALIGN_CENTER_HORIZONTAL, 0)
211         self.SetSizer(sizer_1)
212         sizer_1.Fit(self)
213         self.Layout()
214
215
216 def CreateIraFile(DictPathOut, clusternb, corpname='corpus_name', section = 'analyse'):
217     AnalyseConf = ConfigParser()
218     AnalyseConf.read(DictPathOut['ira'])
219     AnalyseConf.add_section(section)
220     date = datetime.datetime.now().ctime()
221     AnalyseConf.set(section, 'date', str(date))
222     AnalyseConf.set(section, 'clusternb', clusternb)
223     AnalyseConf.set(section, 'corpus_name', corpname)
224
225     fileout = open(DictPathOut['ira'], 'w')
226     AnalyseConf.write(fileout)
227     fileout.close()
228
229 def sortedby(list, direct, *indices):
230
231     """
232         sortedby: sort a list of lists (e.g. a table) by one or more indices
233                   (columns of the table) and return the sorted list
234
235         e.g.
236          for list = [[2,3],[1,2],[3,1]]:
237          sortedby(list,1) will return [[3, 1], [1, 2], [2, 3]],
238          sortedby(list,0) will return [[1, 2], [2, 3], [3, 1]]
239     """
240
241     nlist = map(lambda x, indices=indices: 
242                  map(lambda i, x=x: x[i], indices) + [x],
243                  list)
244     if direct == 1:
245         nlist.sort()
246     elif direct == 2:
247         nlist.sort(reverse=True)
248     return map(lambda l: l[-1], nlist)
249
250 def add_type(line, dictlem):
251     if line[4] in dictlem:
252         line.append(dictlem[line[4]])
253     else :
254         line.append('')
255     return line
256
257 def treat_line_alceste(i, line) :
258     if line[0] == '*' or line[0] == '*****' :
259         return line + ['']
260     if line[5] == 'NA':
261         print 'NA', line[5]
262         pass
263     elif float(line[5].replace(',', '.')) < 0.0001:
264         line[5] = '< 0,0001'
265     elif float(line[5].replace(',', '.')) > 0.05:
266         line[5] = 'NS (%s)' % str(float(line[5].replace(',', '.')))[0:7]
267     else:
268         line[5] = str(float(line[5].replace(',', '.')))[0:7]
269     return [i, int(line[0]), int(line[1]), float(line[2]), float(line[3]), line[6], line[4], line[5]]
270
271 def ReadProfileAsDico(parent, File, Alceste=False, encoding = sys.getdefaultencoding()):
272     #print 'lecture des profils : ReadProfileAsDico'
273     #if Alceste :
274     #    print 'lecture du dictionnaire de type'
275     #    dictlem = {}
276     #    for line in parent.corpus.lem_type_list :
277     #        dictlem[line[0]] = line[1]
278     dictlem = {}
279     print 'lecture des profiles'
280     #encoding = sys.getdefaultencoding()
281     print encoding
282     FileReader = codecs.open(File, 'r', encoding)
283     Filecontent = FileReader.readlines()
284     FileReader.close()
285     DictProfile = {}
286     count = 0
287     rows = [row.replace('\n', '').replace("'", '').replace('\"', '').replace(',', '.').replace('\r','').split(';') for row in Filecontent]
288     rows.pop(0)
289     ClusterNb = rows[0][2]
290     rows.pop(0)
291     clusters = [row[2] for row in rows if row[0] == u'**']
292     valclusters = [row[1:4] for row in rows if row[0] == u'****']
293     lp = [i for i, line in enumerate(rows) if line[0] == u'****']
294     prof = [rows[lp[i] + 1:lp[i+1] - 1] for i in range(0, len(lp)-1)] + [rows[lp[-1] + 1:len(rows)]] 
295     if Alceste :
296         prof = [[add_type(row, dictlem) for row in pr] for pr in prof]
297         prof = [[treat_line_alceste(i,line) for i, line in enumerate(pr)] for pr in prof] 
298     else :
299         prof = [[line + [''] for line in pr] for pr in prof]
300         prof = [[treat_line_alceste(i,line) for i, line in enumerate(pr)] for pr in prof]
301     for i, cluster in enumerate(clusters):
302         DictProfile[cluster] = [valclusters[i]] + prof[i]
303     return DictProfile
304
305 def GetTxtProfile(dictprofile) :
306     proflist = []
307     for classe in range(0, len(dictprofile)) :
308         prof = dictprofile[str(classe + 1)]
309         clinfo = prof[0]
310         proflist.append('\n'.join([' '.join(['classe %i' % (classe + 1), '-', '%s uce sur %s - %s%%' % (clinfo[0], clinfo[1], clinfo[2])]), '\n'.join(['%5s|%5s|%6s|%6s|%8s|%8s|%20s\t%10s' % tuple([str(val) for val in line]) for line in prof if len(line)==8])]))
311     return '\n\n'.join(proflist)
312
313 def formatExceptionInfo(maxTBlevel=5):
314          cla, exc, trbk = sys.exc_info()
315          excName = cla.__name__
316          try:
317              excArgs = exc.__dict__["args"]
318          except KeyError:
319              excArgs = "<no args>"
320          excTb = traceback.format_tb(trbk, maxTBlevel)
321          return (excName, excArgs, excTb)
322
323
324 #fonction des etudiants de l'iut
325 def decoupercharact(chaine, longueur, longueurOptimale, separateurs = None) :
326     """
327         on part du dernier caractère, et on recule jusqu'au début de la chaîne.
328         Si on trouve un '$', c'est fini.
329         Sinon, on cherche le meilleur candidat. C'est-à-dire le rapport poids/distance le plus important.
330     """
331     separateurs = [[u'.', 60.0], [u'?', 60.0], [u'!', 60.0], [u'£', 60], [u':', 50.0], [u';', 40.0], [u',', 10.0], [u' ', 0.1]]
332     trouve = False                 # si on a trouvé un bon séparateur
333     iDecoupe = 0                # indice du caractere ou il faut decouper
334     
335     # on découpe la chaine pour avoir au maximum 240 caractères
336     longueur = min(longueur, len(chaine) - 1)
337     chaineTravail = chaine[:longueur + 1]
338     nbCar = longueur
339     meilleur = ['', 0, 0]        # type, poids et position du meilleur separateur
340     
341     # on vérifie si on ne trouve pas un '$'
342     indice = chaineTravail.find(u'$')
343     if indice > -1:
344         trouve = True
345         iDecoupe = indice
346
347     # si on ne trouve rien, on cherche le meilleur séparateur
348     if not trouve:
349         while nbCar >= 0:
350             caractere = chaineTravail[nbCar]
351             distance = abs(longueurOptimale - nbCar) + 1
352             meilleureDistance = abs(longueurOptimale - meilleur[2]) + 1
353
354             # on vérifie si le caractére courant est une marque de ponctuation
355             for s in separateurs:
356                 if caractere == s[0]:
357                     # si c'est une ponctuation 
358                     
359                     if s[1] / distance > float(meilleur[1]) / meilleureDistance:
360                         # print nbCar, s[0]
361                         meilleur[0] = s[0]
362                         meilleur[1] = s[1]
363                         meilleur[2] = nbCar
364                         trouve = True
365                         iDecoupe = nbCar
366                         
367                     # et on termine la recherche
368                     break
369
370             # on passe au caractère précédant
371             nbCar = nbCar - 1
372     
373     # si on a trouvé
374     if trouve:
375         fin = chaine[iDecoupe + 1:]
376         retour = chaineTravail[:iDecoupe]
377         return len(retour) > 0, retour.split(), fin
378     # si on a rien trouvé
379     return False, chaine.split(), ''
380
381 def BugReport(parent):
382     for ch in parent.GetChildren():
383         if "<class 'wx._windows.ProgressDialog'>" == str(type(ch)):
384             ch.Destroy()   
385     dial = BugDialog(parent)
386     txt = u'            !== BUG ==!       \n'
387     txt += u'*************************************\n'
388     for line in formatExceptionInfo():
389         if type(line) == type([]):
390             for don in line:
391                 txt += don.replace('    ', ' ')
392         else:
393             txt += line + '\n'
394     if 'Rerror' in dir(parent) :
395         txt += parent.Rerror
396         parent.Rerror = ''
397     print formatExceptionInfo()
398     log.error(txt)
399     dial.text_ctrl_1.write(txt)
400     dial.CenterOnParent()
401     dial.ShowModal()
402     raise NameError('Bug')
403     
404 def PlaySound(parent):
405     if parent.pref.getboolean('iramuteq', 'sound') :
406         try:
407             if "gtk2" in wx.PlatformInfo:
408                 error = Popen(['aplay','-q',os.path.join(parent.AppliPath,'son_fin.wav')])
409             else :    
410                 sound = wx.Sound(os.path.join(parent.AppliPath, 'son_fin.wav'))
411                 sound.Play(wx.SOUND_SYNC)
412         except :
413             print 'pas de son'
414
415 def ReadDicoAsDico(dicopath):
416     with codecs.open(dicopath, 'r', 'UTF8') as f:
417         content = f.readlines()
418     dico = {}
419     for line in content :
420         if line[0] != u'':
421             line = line.replace(u'\n', '').replace('"', '').split('\t')
422             dico[line[0]] = line[1:]
423     return dico
424
425 def ReadLexique(parent, lang = 'french'):
426     parent.lexique = ReadDicoAsDico(parent.DictPath.get(lang, 'french'))
427
428 def ReadList(filein, encoding = sys.getdefaultencoding()):
429     #file = open(filein)
430     file = codecs.open(filein, 'r', encoding)
431     content = file.readlines()
432     file.close()
433     first = content.pop(0)
434     first = first.replace('\n', '').replace('\r','').replace('\"', '').split(';')
435     dict = {}
436     i = 0
437     for line in content:
438         line = line.replace('\n', '').replace('\r','').replace('\"', '').replace(',', '.')
439         line = line.split(';')
440         nline = [line[0]]
441         for val in line[1:]:
442             if val == u'NA' :
443                 don = ''
444             else: 
445                 try:
446                     don = int(val)
447                 except:
448                     don = float('%.5f' % float(val))
449             nline.append(don)
450         dict[i] = nline
451         i += 1
452     return dict, first
453
454 def exec_rcode(rpath, rcode, wait = True, graph = False):
455     print rpath, rcode
456     needX11 = False
457     if sys.platform == 'darwin' :
458         try :
459             macversion = platform.mac_ver()[0].split('.')
460             print macversion
461             if int(macversion[1]) < 5 :
462                 needX11 = True
463             else :
464                 needX11 = False
465         except :
466             needX11 = False
467
468     rpath = rpath.replace('\\','\\\\')
469     if not graph :
470         if wait :
471             if sys.platform == 'win32':
472                 error = call(["%s" % rpath, "--vanilla","--slave","-f", "%s" % rcode])
473             else :
474                 error = call([rpath, '--vanilla','--slave',"-f %s" % rcode])
475             return error
476         else :
477             if sys.platform == 'win32':
478                 pid = Popen(["%s" % rpath, '--vanilla','--slave','-f', "%s" % rcode])
479             else :
480                 pid = Popen([rpath, '--vanilla','--slave',"-f %s" % rcode], stderr = PIPE)
481             return pid
482     else :
483         if wait :
484             if sys.platform == 'win32':
485                 error = call(["%s" % rpath, '--vanilla','--slave','-f', "%s" % rcode])
486             elif sys.platform == 'darwin' and needX11:
487                 os.environ['DISPLAY'] = ':0.0'
488                 error = call([rpath, '--vanilla','--slave',"-f %s" % rcode])
489             else :
490                 error = call([rpath, '--vanilla','--slave',"-f %s" % rcode])
491             return error
492         else :
493             if sys.platform == 'win32':
494                 pid = Popen(["%s" % rpath, '--vanilla','--slave','-f', "%s" % rcode])
495             elif sys.platform == 'darwin' and needX11:
496                 os.environ['DISPLAY'] = ':0.0'
497                 pid = Popen([rpath, '--vanilla','--slave',"-f %s" % rcode], stderr = PIPE)
498             else :
499                 pid = Popen([rpath, '--vanilla','--slave',"-f %s" % rcode], stderr = PIPE)
500             return pid
501
502 def check_Rresult(parent, pid) :
503     if isinstance(pid, Popen) :
504         if pid.returncode != 0 :
505             error = pid.communicate()
506             error = [str(error[0]), error[1]]
507             if error[1] is None :
508                 error[1] = 'None'
509             parent.Rerror = '\n'.join([str(pid.returncode), '\n'.join(error)])
510             try :
511                 raise Exception('\n'.join(u'Erreur R', '\n'.join(error[1:])))
512             except :
513                 BugReport(parent)
514     else :
515         if pid !=0 :
516             try :
517                 raise Exception(u'Erreur R')
518             except :
519                 BugReport(parent)
520
521 def print_liste(filename,liste):
522     with open(filename,'w') as f :
523         for graph in liste :
524             f.write(';'.join(graph)+'\n')
525
526 def read_list_file(filename, encoding = sys.getdefaultencoding()):
527     with codecs.open(filename,'rU', encoding) as f :
528         content=f.readlines()
529         ncontent=[line.replace('\n','').split(';') for line in content if line.strip() != '']
530     return ncontent
531         
532 class MessageImage(wx.Frame):
533     def __init__(self, *args, **kwds):
534         # begin wxGlade: MyFrame.__init__
535         kwds["style"] = wx.DEFAULT_FRAME_STYLE
536         wx.Frame.__init__(self, *args, **kwds)
537         #self.text_ctrl_1 = wx.TextCtrl(self, -1, "", style=wx.TE_MULTILINE)
538         self.imageFile = False
539         self.imagename = u"chi_classe.png"
540         self.HtmlPage = wx.html.HtmlWindow(self, -1)
541         if "gtk2" in wx.PlatformInfo:
542             self.HtmlPage.SetStandardFonts()
543         self.HtmlPage.SetFonts('Courier', 'Courier')
544         
545         self.button_1 = wx.Button(self, -1, u"Fermer")
546         self.Bind(wx.EVT_BUTTON, self.OnCloseMe, self.button_1)
547         self.Bind(wx.EVT_CLOSE, self.OnCloseWindow)
548         self.__do_layout()
549         # end wxGlade
550
551     def __do_layout(self):
552         # begin wxGlade: MyFrame.__do_layout
553         sizer_1 = wx.BoxSizer(wx.VERTICAL)
554         self.sizer_2 = wx.BoxSizer(wx.VERTICAL)
555         self.sizer_2.Add(self.HtmlPage, 1, wx.EXPAND | wx.ADJUST_MINSIZE, 0)
556         self.sizer_2.Add(self.button_1, 0, wx.ALIGN_CENTER_HORIZONTAL | wx.ADJUST_MINSIZE, 0)
557         sizer_1.Add(self.sizer_2, 1, wx.EXPAND, 0)
558         self.SetAutoLayout(True)
559         self.SetSizer(sizer_1)
560         # end wxGlade
561
562     def addsaveimage(self, imageFile) :
563         self.imageFile = imageFile
564         self.button_2 = wx.Button(self, -1, u"Enregistrer l'image...")
565         self.Bind(wx.EVT_BUTTON, self.OnSaveImage, self.button_2)
566         self.sizer_2.Add(self.button_2, 0, wx.ALIGN_CENTER_HORIZONTAL | wx.ADJUST_MINSIZE, 0)
567         self.Layout()
568
569     def OnCloseMe(self, event):
570         self.Close(True)
571
572     def OnCloseWindow(self, event):
573         self.Destroy()
574
575     def OnSaveImage(self, event) :
576         dlg = wx.FileDialog(
577             self, message="Enregistrer sous...", defaultDir=os.getcwd(),
578             defaultFile= self.imagename, wildcard="png|*.png", style=wx.SAVE | wx.OVERWRITE_PROMPT
579             )
580         dlg.SetFilterIndex(2)
581         dlg.CenterOnParent()
582         if dlg.ShowModal() == wx.ID_OK:
583             path = dlg.GetPath()
584             copyfile(self.imageFile, path)
585             
586
587 def progressbar(self, maxi) :
588     if 'parent' in dir(self) :
589         parent = self.parent
590     else :
591         parent = self
592     return wx.ProgressDialog("Traitements",
593                              "Veuillez patienter...",
594                              maximum=maxi,
595                              parent=parent,
596                              style=wx.PD_APP_MODAL | wx.PD_AUTO_HIDE | wx.PD_ELAPSED_TIME | wx.PD_CAN_ABORT
597                              )
598
599
600 def treat_var_mod(variables) :
601     var_mod = {}
602     for variable in variables :
603         if u'_' in variable :
604             forme = variable.split(u'_')
605             var = forme[0]
606             mod = forme[1]
607             if not var in var_mod :
608                 var_mod[var] = [variable]
609             else :
610                 if not mod in var_mod[var] :
611                     var_mod[var].append(variable)
612     return var_mod