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