Refactoring. Suppression de la customisation du path, inutile à présent.
[minwii.git] / src / minwii / logapp.py
1 # -*- coding: utf-8 -*-
2 """
3 Interface graphique pour l'analyse des fichiers de log minwii.
4
5 $Id$
6 $URL$
7 """
8
9 import os
10 os.environ['WINWII_NO_LOG'] = '1'
11 from Tkinter import *
12 import tkFileDialog
13 from glob import glob
14 from os.path import join as pjoin
15 from os.path import basename
16 from os.path import getsize
17 import os.path
18 from minwii.loganalyse import LogFileAnalyser
19 from minwii.config import LOGS_DIR
20 from pprint import pprint
21
22 class Application(Frame) :
23 def __init__(self, master=None) :
24 Frame.__init__(self, master)
25 self.configureStretching()
26 self.createWidgets()
27 self.logDir = ''
28 self.logFiles = []
29 self.currentFilePath = ''
30 self.resultsFrame = None
31
32 if os.path.exists(LOGS_DIR) :
33 self.chooseDirDialog(dir=LOGS_DIR)
34
35 def configureStretching(self) :
36 top=self.winfo_toplevel()
37 top.rowconfigure(0, weight=1)
38 top.columnconfigure(0, weight=1)
39
40 self.grid(sticky=N+S+E+W, padx=10, pady=10)
41 self.rowconfigure(0, weight=1)
42 self.columnconfigure(0, weight=1)
43
44 def createWidgets(self) :
45 # zone d'affichage des données'
46 self.dataFrame = df = Frame(self)
47
48 self.identFrame = Identification(df)
49 self.identFrame.grid(sticky=NW)
50
51 # barre de boutons
52 self.btnFrame = bf = Frame(self)
53 bf.grid(row=1, column=0, sticky=W+S+E)
54 bf.rowconfigure(0, weight=1)
55 for i in range(3) :
56 bf.columnconfigure(i, weight=1)
57
58 self.chooseLogDir = Button(bf, text="Parcourir…", command=self.chooseDirDialog)
59 self.chooseLogDir.grid(row=0, column=0, sticky=W)
60
61 self.nav = Navbar(bf, incCallback=self.loadLogFile, decCallback=self.loadLogFile)
62
63 self.quitButton = Button(bf, text='Terminer', command=self.quit)
64 self.quitButton.grid(row=0, column=2, sticky=E)
65
66 def chooseDirDialog(self, dir=None) :
67 if dir is None :
68 self.logDir = tkFileDialog.askdirectory(title='Sélectionnez un dossier de fichiers de logs')
69 else :
70 self.logDir = dir
71 if self.logDir :
72 self.logFiles = glob(pjoin(self.logDir, '*.log'))
73 self._cleanupJunkFiles()
74 self.logFiles.sort()
75 self.logFiles.reverse()
76 self.dataFrame.grid(row=0, column=0, sticky=NW)
77 self.nav.setSize(len(self.logFiles))
78 self.nav.grid(row=0, column=1)
79 self.loadLogFile(self.nav)
80
81 def _cleanupJunkFiles(self) :
82 files = []
83 while self.logFiles :
84 f = self.logFiles.pop()
85 if not getsize(f) :
86 os.remove(f)
87 continue
88 else :
89 of = open(f)
90 lfa = LogFileAnalyser(of)
91 if lfa.getLastEventTicks() is None :
92 of.close()
93 os.remove(f)
94 continue
95 else :
96 of.close()
97
98 files.append(f)
99
100 self.logFiles = files
101
102
103 def loadLogFile(self, nav) :
104 index = nav.index - 1
105 filepath = self.logFiles[index]
106 self.currentFilePath = filepath
107 lfa = LogFileAnalyser(self.currentFilePath)
108 self.identFrame.refresh(lfa)
109 if self.resultsFrame :
110 self.resultsFrame.destroy()
111 self.resultsFrame = ResultsFrame(self.dataFrame)
112 self.resultsFrame.layResults(lfa)
113 lfa.close()
114 self.resultsFrame.grid()
115
116
117 class Navbar(Frame) :
118 def __init__(self, master=None, size=1, incCallback=None, decCallback=None) :
119 Frame.__init__(self, master)
120 self.caption = StringVar()
121 self.createWidgets()
122 self.setSize(size)
123 self.incCallback = incCallback if incCallback else lambda x : None
124 self.decCallback = decCallback if decCallback else lambda x : None
125 self.caption.set('%d / %d' % (self.index, self.to))
126
127 def createWidgets(self) :
128 self.backBtn = Button(self,
129 text='◀',
130 command = self.dec
131 )
132 self.backBtn.grid(row=0, column=0)
133
134 self.lbl = Label(self, textvariable=self.caption)
135 self.lbl.grid(row=0, column=1)
136
137 self.nextBtn = Button(self,
138 text='▶',
139 command = self.inc)
140 self.nextBtn.grid(row=0, column=2)
141
142 def refreshStates(self) :
143 if self.index == self.from_ :
144 self.backBtn.configure(state=DISABLED)
145 else :
146 self.backBtn.configure(state=NORMAL)
147
148 if self.index < self.to :
149 self.nextBtn.configure(state=NORMAL)
150 else :
151 self.nextBtn.configure(state=DISABLED)
152
153 self.caption.set('%d / %d' % (self.index, self.to))
154
155
156 def dec(self) :
157 self.index = self.index - 1
158 self.refreshStates()
159 self.decCallback(self)
160
161 def inc(self) :
162 self.index = self.index + 1
163 self.refreshStates()
164 self.incCallback(self)
165
166 def setSize(self, size) :
167 self.from_ = 1
168 self.to = size
169 self.index = 1
170 self.refreshStates()
171
172
173 class Identification(Frame) :
174 def __init__(self, master=None) :
175 Frame.__init__(self, master)
176 self.fileName = StringVar()
177 self.hid = StringVar()
178 self.patientName = StringVar()
179 self.createWidgets()
180
181 def refresh(self, lfa) :
182 filename = basename(lfa.logfile.name)
183 self.fileName.set(filename)
184 self.hid.set(lfa.getHID())
185 metadata = lfa.getMetadata()
186 self.patientName.set(metadata.get('PatientName', ''))
187 self.commentsText.delete(1.0, END)
188 self.commentsText.insert(1.0, metadata.get('Comments', ''))
189
190 def createWidgets(self) :
191 fileLbl = Label(self, text='Fichier :')
192 fileLbl.grid(row=0, column=0, sticky=E)
193
194 fileNameLbl = Label(self, textvariable=self.fileName)
195 fileNameLbl.grid(row=0, column=1, sticky=W)
196
197 hidLbl = Label(self, text='HID :')
198 hidLbl.grid(row=1, column=0, sticky=E)
199
200 hidNameLbl = Label(self, textvariable=self.hid)
201 hidNameLbl.grid(row=1, column=1, sticky=W)
202
203 nameLbl = Label(self, text='Patient :')
204 nameLbl.grid(row=2, column=0, sticky=E)
205
206 self.nameEntry = Entry(self, width=40, textvariable=self.patientName)
207 self.nameEntry.grid(row=2, column=1, sticky=W)
208
209 commentsLbl = Label(self, text='Commentaires :')
210 commentsLbl.grid(row=3, column=0, sticky=E)
211
212 self.commentsText = Text(self, width=40, height=4, undo=True, wrap=WORD)
213 self.commentsText.grid(row=3, column=1, sticky=W)
214
215 self.saveBtn = Button(self, text='Enregistrer', command=self.saveMetadata)
216 self.saveBtn.grid(row=4, column=1, sticky=E)
217
218 def saveMetadata(self):
219 app = self.master.master
220 filepath = app.currentFilePath
221 lfa = LogFileAnalyser(filepath, mode='r+')
222 patientName = '%s\n' % self.nameEntry.get().replace('\n', ' ').strip()
223 comments = '%s\n' % self.commentsText.get(1.0, END).replace('\n', ' ').strip()
224 metadata = (('PatientName', self.nameEntry.get()),
225 ('Comments', comments))
226 lfa.setMetadata(metadata)
227
228
229 class ResultsFrame(Frame) :
230
231 def layResults(self, lfa) :
232 results = lfa.analyse()
233 if results :
234 for i, kvt in enumerate(results) :
235 k, v, timeBased = kvt
236 kl = Label(self, text='%s :' % k)
237 kl.grid(row=i, column=0, sticky=E)
238
239 if not timeBased :
240 vl = Label(self, text=v)
241 vl.grid(row=i, column=1, sticky=W)
242 else :
243 maxv = max(v)
244 if maxv :
245 cw, ch = 200, 100
246 c = Canvas(self, background='#fff', width=cw, height=ch)
247 rectW = int(float(cw) / len(v))
248 unitRectH = float(ch) / maxv
249 for j, fv in enumerate(v) :
250 if not fv : continue
251 x0 = j * rectW
252 y0 = ch - int(unitRectH * fv)
253 x1 = (j + 1) * rectW
254 y1 = ch
255 c.create_rectangle(x0, y0, x1, y1, fill="#9085ba")
256 c.grid(row=i, column=1, sticky=W)
257
258 else :
259 vl = Label(self, text='—')
260 vl.grid(row=i, column=1, sticky=W)
261 else :
262 msg = Label(self, text="Pas de données exploitables.")
263 msg.grid()
264
265
266 def main() :
267 app = Application()
268 app.master.title("Analyseur des sessions MINDs")
269 app.mainloop()
270
271 if __name__ == '__main__' :
272 main()