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