Coverage for larch/wxlib/feff_browser.py: 15%

424 statements  

« prev     ^ index     » next       coverage.py v7.6.0, created at 2024-10-16 21:04 +0000

1import os 

2import sys 

3import time 

4import logging 

5import shutil 

6from datetime import datetime, timedelta 

7from pathlib import Path 

8 

9import wx 

10import wx.lib.scrolledpanel as scrolled 

11import wx.dataview as dv 

12 

13import larch 

14from larch.site_config import user_larchdir 

15from larch.utils import unixpath, mkdir, read_textfile 

16from larch.wxlib import (GridPanel, GUIColors, Button, pack, SimpleText, 

17 Font, LEFT, FRAMESTYLE, 

18 FONTSIZE, MenuItem, EditableListBox, OkCancel, 

19 FileCheckList, Choice, HLine, ReportFrame, Popup, 

20 LarchWxApp) 

21 

22from larch.xafs import get_feff_pathinfo 

23from larch.utils.physical_constants import ATOM_SYMS 

24 

25ATSYMS = ['< All Atoms>'] + ATOM_SYMS[:96] 

26EDGES = ['< All Edges>', 'K', 'L3', 'L2', 'L1', 'M5'] 

27 

28 

29LEFT = LEFT|wx.ALL 

30DVSTYLE = dv.DV_VERT_RULES|dv.DV_ROW_LINES|dv.DV_MULTIPLE 

31 

32class FeffPathsModel(dv.DataViewIndexListModel): 

33 def __init__(self, feffpaths, with_use=True): 

34 dv.DataViewIndexListModel.__init__(self, 0) 

35 self.data = [] 

36 self.paths = {} 

37 self.with_use = with_use 

38 self.feffpaths = feffpaths 

39 self.read_data() 

40 

41 def set_data(self, feffpaths): 

42 self.paths = {} 

43 self.feffpaths = feffpaths 

44 self.read_data() 

45 

46 def read_data(self): 

47 self.data = [] 

48 if self.feffpaths is None: 

49 row = ['feffNNNN.dat', '0.0000', '2', '6', '100.0'] 

50 if self.with_use: row.append(False) 

51 row.append('* -> * -> *') 

52 self.data.append(row) 

53 else: 

54 for fp in self.feffpaths: 

55 row = [fp.filename, '%.4f' % fp.reff, 

56 '%.0f' % fp.nleg, '%.0f' % fp.degen, 

57 '%.3f' % fp.cwratio] 

58 use = False 

59 if self.with_use: 

60 if fp.filename in self.paths: 

61 use = self.paths[fp.filename] 

62 row.append(use) 

63 row.append(fp.geom) 

64 self.data.append(row) 

65 self.paths[fp.filename] = use 

66 self.Reset(len(self.data)) 

67 

68 

69 def select_all(self, use=True): 

70 for pname in self.paths: 

71 self.paths[pname] = use 

72 self.read_data() 

73 

74 def select_above(self, item): 

75 itemname = self.GetValue(item, 0) 

76 use = True 

77 for row in self.data: 

78 self.paths[row[0]] = use 

79 if row[0] == itemname: 

80 use = not use 

81 self.read_data() 

82 

83 def GetColumnType(self, col): 

84 if self.with_use and col == 5: 

85 return "bool" 

86 return "string" 

87 

88 def GetValueByRow(self, row, col): 

89 return self.data[row][col] 

90 

91 def SetValueByRow(self, value, row, col): 

92 

93 self.data[row][col] = value 

94 return True 

95 

96 def GetColumnCount(self): 

97 return len(self.data[0]) 

98 

99 def GetCount(self): 

100 return len(self.data) 

101 

102 def GetAttrByRow(self, row, col, attr): 

103 """set row/col attributes (color, etc)""" 

104 nleg = self.data[row][2] 

105 cname = self.data[row][0] 

106 if nleg == '2': 

107 attr.SetColour('#000') 

108 attr.SetBold(False) 

109 return True 

110 elif nleg == '3': 

111 attr.SetColour('#A11') 

112 attr.SetBold(False) 

113 return True 

114 elif nleg == '4': 

115 attr.SetColour('#11A') 

116 attr.SetBold(False) 

117 return True 

118 else: 

119 attr.SetColour('#393') 

120 attr.SetBold(False) 

121 return True 

122 return False 

123 

124 

125class RemoveFeffCalcDialog(wx.Dialog): 

126 """dialog for removing Feff Calculations""" 

127 

128 def __init__(self, parent, ncalcs=1, **kws): 

129 title = "Remove Feff calculations?" 

130 wx.Dialog.__init__(self, parent, wx.ID_ANY, title=title, size=(325, 275)) 

131 panel = GridPanel(self, ncols=3, nrows=4, pad=2, itemstyle=LEFT) 

132 

133 panel.Add(SimpleText(panel, f'Remove {ncalcs:d} Feff calculations?'), 

134 dcol=3, newrow=True) 

135 panel.Add(SimpleText(panel, 'Warning: this cannot be undone!'), 

136 dcol=3, newrow=True) 

137 panel.Add((5, 5), newrow=True) 

138 panel.Add(HLine(panel, size=(500, 3)), dcol=2, newrow=True) 

139 panel.Add(OkCancel(panel), dcol=2, newrow=True) 

140 panel.pack() 

141 

142 def GetResponse(self): 

143 self.Raise() 

144 return (self.ShowModal() == wx.ID_OK) 

145 

146class FeffResultsPanel(wx.Panel): 

147 """ present Feff results """ 

148 def __init__(self, parent=None, feffresult=None, path_importer=None, 

149 _larch=None): 

150 wx.Panel.__init__(self, parent, -1, size=(700, 500)) 

151 self.parent = parent 

152 self.path_importer = path_importer 

153 self._larch = _larch 

154 self.feffresult = feffresult 

155 self.report_frame = None 

156 

157 self.dvc = dv.DataViewCtrl(self, style=DVSTYLE) 

158 self.dvc.SetMinSize((695, 350)) 

159 

160 self.model = FeffPathsModel(None, with_use=callable(path_importer)) 

161 self.dvc.AssociateModel(self.model) 

162 

163 panel = wx.Panel(self) 

164 # panel.SetBackgroundColour(GUIColors.bg) 

165 

166 sizer = wx.GridBagSizer(1, 1) 

167 

168 bkws = dict(size=(175, -1)) 

169 btn_header = Button(panel, "Show Full Header", action=self.onShowHeader, **bkws) 

170 btn_feffinp = Button(panel, "Show Feff.inp", action=self.onShowFeffInp, **bkws) 

171 btn_geom = Button(panel, "Show Path Geometries", action=self.onShowGeom, **bkws) 

172 

173 if callable(self.path_importer): 

174 btn_import = Button(panel, "Import Paths", action=self.onImportPath, **bkws) 

175 btn_above = Button(panel, "Select All Above Current", action=self.onSelAbove, **bkws) 

176 btn_none = Button(panel, "Select None", action=self.onSelNone, **bkws) 

177 

178 opts = dict(size=(475, -1), style=LEFT) 

179 self.feff_folder = SimpleText(panel, '', **opts) 

180 self.feff_datetime = SimpleText(panel, '',**opts) 

181 self.feff_header = [SimpleText(panel, '', **opts), 

182 SimpleText(panel, '', **opts), 

183 SimpleText(panel, '', **opts), 

184 SimpleText(panel, '', **opts), 

185 SimpleText(panel, '', **opts), 

186 SimpleText(panel, '', **opts)] 

187 

188 

189 ir = 0 

190 sizer.Add(SimpleText(panel, 'Feff Folder:'), (ir, 0), (1, 1), LEFT, 2) 

191 sizer.Add(self.feff_folder, (ir, 1), (1, 4), LEFT, 2) 

192 ir += 1 

193 sizer.Add(SimpleText(panel, 'Date Run:'), (ir, 0), (1, 1), LEFT, 2) 

194 sizer.Add(self.feff_datetime, (ir, 1), (1, 5), LEFT, 2) 

195 

196 ir += 1 

197 sizer.Add(SimpleText(panel, 'Header:'), (ir, 0), (1, 1), LEFT, 1) 

198 sizer.Add(self.feff_header[0], (ir, 1), (1, 5), LEFT, 1) 

199 ir += 1 

200 sizer.Add(SimpleText(panel, ''), (ir, 0), (1, 1), LEFT, 1) 

201 sizer.Add(self.feff_header[1], (ir, 1), (1, 5), LEFT, 1) 

202 ir += 1 

203 sizer.Add(SimpleText(panel, ''), (ir, 0), (1, 1), LEFT, 1) 

204 sizer.Add(self.feff_header[2], (ir, 1), (1, 5), LEFT, 1) 

205 ir += 1 

206 sizer.Add(SimpleText(panel, ''), (ir, 0), (1, 1), LEFT, 1) 

207 sizer.Add(self.feff_header[3], (ir, 1), (1, 5), LEFT, 1) 

208 ir += 1 

209 sizer.Add(SimpleText(panel, ''), (ir, 0), (1, 1), LEFT, 1) 

210 sizer.Add(self.feff_header[4], (ir, 1), (1, 5), LEFT, 1) 

211 ir += 1 

212 sizer.Add(SimpleText(panel, ''), (ir, 0), (1, 1), LEFT, 1) 

213 sizer.Add(self.feff_header[5], (ir, 1), (1, 5), LEFT, 1) 

214 

215 ir += 1 

216 sizer.Add(btn_header, (ir, 0), (1, 2), LEFT, 2) 

217 sizer.Add(btn_feffinp, (ir, 2), (1, 2), LEFT, 2) 

218 sizer.Add(btn_geom, (ir, 4), (1, 2), LEFT, 2) 

219 

220 if callable(self.path_importer): 

221 ir += 1 

222 sizer.Add(btn_above, (ir, 0), (1, 2), LEFT, 2) 

223 sizer.Add(btn_none, (ir, 2), (1, 2), LEFT, 2) 

224 sizer.Add(btn_import, (ir, 4), (1, 2), LEFT, 2) 

225 

226 ir += 1 

227 sizer.Add(wx.StaticLine(panel, size=(600, 2)),(ir, 0), (1, 6), LEFT, 2) 

228 

229 pack(panel, sizer) 

230 

231 columns = [('Feff File', 100, 'text'), 

232 ('R (\u212B)', 60, 'text'), 

233 ('# legs', 60, 'text'), 

234 ('# paths', 65, 'text'), 

235 ('Importance', 100, 'text')] 

236 if callable(self.path_importer): 

237 columns.append(('Use', 50, 'bool')) 

238 columns.append(('Geometry', 200, 'text')) 

239 

240 for icol, dat in enumerate(columns): 

241 label, width, dtype = dat 

242 method = self.dvc.AppendTextColumn 

243 mode = dv.DATAVIEW_CELL_EDITABLE 

244 if dtype == 'bool': 

245 method = self.dvc.AppendToggleColumn 

246 mode = dv.DATAVIEW_CELL_ACTIVATABLE 

247 method(label, icol, width=width, mode=mode) 

248 c = self.dvc.Columns[icol] 

249 align = wx.ALIGN_RIGHT 

250 if (label.startswith('Feff') or label.startswith('Geom')): 

251 align = wx.ALIGN_LEFT 

252 c.Alignment = c.Renderer.Alignment = align 

253 c.SetSortable(False) 

254 

255 

256 mainsizer = wx.BoxSizer(wx.VERTICAL) 

257 mainsizer.Add(panel, 0, LEFT, 1) 

258 mainsizer.Add(self.dvc, 0, LEFT, 1) 

259 

260 pack(self, mainsizer) 

261 self.dvc.EnsureVisible(self.model.GetItem(0)) 

262 

263 if feffresult is not None: 

264 self.set_feffresult(feffresult) 

265 

266 def onSelAll(self, event=None): 

267 self.model.select_all(True) 

268 

269 def onSelNone(self, event=None): 

270 self.model.select_all(False) 

271 

272 def onSelAbove(self, event=None): 

273 if self.dvc.HasSelection(): 

274 self.model.select_above(self.dvc.GetSelection()) 

275 

276 def onShowHeader(self, event=None): 

277 if self.feffresult is not None: 

278 self.show_report(self.feffresult.header, 

279 title=f'Header for {self.feffresult.folder:s}', 

280 default_filename=f'{self.feffresult.folder:s}_header.txt') 

281 

282 def onShowGeom(self, event=None): 

283 if self.feffresult is None: 

284 return 

285 show = False 

286 out = [] 

287 for data in self.model.data: 

288 if data[5]: 

289 show = True 

290 out.append(f'### {self.feffresult.folder:s}/{data[0]:s} ###') 

291 out.append('#Atom IPOT X Y Z Beta Eta Length') 

292 fname = data[0] 

293 

294 for fp in self.feffresult.paths: 

295 if fname == fp.filename: 

296 for i, px in enumerate(fp.geometry): 

297 at, ipot, r, x, y, z, beta, eta = px 

298 if i == 0: r = 0 

299 t = f'{at:4s} {ipot:3d} {x:9.4f} {y:9.4f} {z:9.4f} {beta:9.4f} {eta:9.4f} {r:9.4f}' 

300 out.append(t) 

301 if show: 

302 out = '\n'.join(out) 

303 self.show_report(out, title=f'Path Geometries for {self.feffresult.folder:s}', 

304 default_filename=f'{self.feffresult.folder:s}_paths.dat') 

305 

306 

307 

308 def onShowFeffInp(self, event=None): 

309 if self.feffresult is not None: 

310 text = None 

311 fname = Path(self.feffresult.folder, 'feff.inp') 

312 if fname.exists(): 

313 text = read_textfile(fname) 

314 else: 

315 fname = Path(user_larchdir, 'feff', 

316 self.feffresult.folder, 'feff.inp') 

317 if fname.exists(): 

318 text = read_textfile(fname) 

319 if text is not None: 

320 self.show_report(text, title=f'Feff.inp for {self.feffresult.folder:s}', 

321 default_filename=f'{self.feffresult.folder:s}_feff.inp', 

322 wildcard='Input Files (*.inp)|*.inp') 

323 

324 def show_report(self, text, title='Text', default_filename='out.txt', wildcard=None): 

325 if wildcard is None: 

326 wildcard='Text Files (*.txt)|*.txt' 

327 default_filename = Path(default_filename).name 

328 try: 

329 self.report_frame.set_text(text) 

330 self.report_frame.SetTitle(title) 

331 self.report_frame.default_filename = default_filename 

332 self.report_frame.wildcard = wildcard 

333 except: 

334 self.report_frame = ReportFrame(parent=self, 

335 text=text, title=title, 

336 default_filename=default_filename, 

337 wildcard=wildcard) 

338 

339 

340 def onImportPath(self, event=None): 

341 folder = self.feffresult.folder 

342 fname = Path(folder).name 

343 for data in self.model.data: 

344 if data[5]: 

345 fname = data[0] 

346 fullpath = Path(folder, fname).as_posix() 

347 for pathinfo in self.feffresult.paths: 

348 if pathinfo.filename == fname: 

349 self.path_importer(fullpath, pathinfo) 

350 break 

351 

352 self.onSelNone() 

353 

354 

355 def set_feffresult(self, feffresult): 

356 self.feffresult = feffresult 

357 self.feff_folder.SetLabel(feffresult.folder) 

358 self.feff_datetime.SetLabel(feffresult.datetime) 

359 nhead = len(self.feff_header) 

360 

361 for i, text in enumerate(feffresult.header.split('\n')[:nhead]): 

362 self.feff_header[i].SetLabel(text) 

363 self.model.set_data(feffresult.paths) 

364 try: 

365 self.dvc.EnsureVisible(self.model.GetItem(0)) 

366 self.dvc.SetCurrentItem(self.dvc.GetTopItem()) 

367 except: 

368 pass 

369 

370 

371class FeffResultsFrame(wx.Frame): 

372 """ present Feff results """ 

373 def __init__(self, parent=None, feffresult=None, path_importer=None, _larch=None): 

374 wx.Frame.__init__(self, parent, -1, size=(900, 650), style=FRAMESTYLE) 

375 

376 title = "Manage Feff calculation results" 

377 self.larch = _larch 

378 if _larch is None: 

379 self.larch = larch.Interpreter() 

380 # self.larch.eval("# started Feff results browser\n") 

381 # self.larch.eval("if not hasattr('_sys', '_feffruns'): _sys._feffruns = {}") 

382 if not hasattr(self.larch.symtable._sys, '_feffruns'): 

383 self.larch.symtable._sys._feffruns = {} 

384 self.parent = parent 

385 

386 self.feff_folder = unixpath(Path(user_larchdir, 'feff')) 

387 mkdir(self.feff_folder) 

388 

389 self.SetTitle(title) 

390 self.SetSize((925, 650)) 

391 self.SetFont(Font(FONTSIZE)) 

392 self.createMenus() 

393 

394 display0 = wx.Display(0) 

395 client_area = display0.ClientArea 

396 xmin, ymin, xmax, ymax = client_area 

397 xpos = int((xmax-xmin)*0.15) + xmin 

398 ypos = int((ymax-ymin)*0.20) + ymin 

399 self.SetPosition((xpos, ypos)) 

400 

401 splitter = wx.SplitterWindow(self, style=wx.SP_LIVE_UPDATE) 

402 splitter.SetMinimumPaneSize(250) 

403 

404 # left hand panel 

405 lpanel = wx.Panel(splitter) 

406 ltop = wx.Panel(lpanel) 

407 

408 def Btn(msg, x, act): 

409 b = Button(ltop, msg, size=(x, 30), action=act) 

410 b.SetFont(Font(FONTSIZE)) 

411 return b 

412 

413 sel_none = Btn('Select None', 120, self.onSelNone) 

414 sel_all = Btn('Select All', 120, self.onSelAll) 

415 tsizer = wx.BoxSizer(wx.HORIZONTAL) 

416 tsizer.Add(sel_all, 1, LEFT|wx.GROW, 1) 

417 tsizer.Add(sel_none, 1, LEFT|wx.GROW, 1) 

418 pack(ltop, tsizer) 

419 

420 self.fefflist = FileCheckList(lpanel, select_action=self.onShowFeff, 

421 size=(300, -1)) 

422 

423 lsizer = wx.BoxSizer(wx.VERTICAL) 

424 lsizer.Add(ltop, 0, LEFT|wx.GROW, 1) 

425 lsizer.Add(self.fefflist, 1, LEFT|wx.GROW|wx.ALL, 1) 

426 pack(lpanel, lsizer) 

427 

428 # right hand side 

429 panel = wx.Panel(splitter) ## scrolled.ScrolledPanel(splitter) 

430 wids = self.wids = {} 

431 toprow = wx.Panel(panel) 

432 

433 wids['central_atom'] = Choice(toprow, choices=ATSYMS, size=(125, -1), 

434 action=self.onCentralAtom) 

435 wids['edge'] = Choice(toprow, choices=EDGES, size=(125, -1), 

436 action=self.onAbsorbingEdge) 

437 

438 flabel = SimpleText(toprow, 'Filter Calculations by Element and Edge:', size=(175, -1)) 

439 tsizer = wx.BoxSizer(wx.HORIZONTAL) 

440 tsizer.Add(flabel, 0, LEFT, 2) 

441 tsizer.Add(wids['central_atom'], 0, LEFT|wx.GROW, 2) 

442 tsizer.Add(wids['edge'], 0, LEFT|wx.GROW, 2) 

443 pack(toprow, tsizer) 

444 

445 sizer = wx.BoxSizer(wx.VERTICAL) 

446 self.feff_panel = FeffResultsPanel(panel, path_importer=path_importer, 

447 _larch=_larch) 

448 sizer.Add(toprow, 0, LEFT|wx.GROW|wx.ALL, 2) 

449 sizer.Add(HLine(panel, size=(650, 2)), 0, LEFT|wx.GROW|wx.ALL, 2) 

450 sizer.Add(self.feff_panel, 1, LEFT|wx.GROW|wx.ALL, 2) 

451 pack(panel, sizer) 

452 # panel.SetupScrolling() 

453 splitter.SplitVertically(lpanel, panel, 1) 

454 self.Show() 

455 wx.CallAfter(self.onSearch) 

456 

457 def onShowFeff(self, event=None): 

458 fr = self.feffruns.get(self.fefflist.GetStringSelection(), None) 

459 if fr is not None: 

460 self.feff_panel.set_feffresult(fr) 

461 

462 

463 def onSearch(self, event=None): 

464 catom = self.wids['central_atom'].GetStringSelection() 

465 edge = self.wids['edge'].GetStringSelection() 

466 all_catoms = 'All' in catom 

467 all_edges = 'All' in edge 

468 

469 self.fefflist.Clear() 

470 self.feffruns = {} 

471 flist = os.listdir(self.feff_folder) 

472 flist = sorted(flist, key=lambda t: -os.stat(Path(self.feff_folder, t)).st_mtime) 

473 _feffruns = self.larch.symtable._sys._feffruns 

474 for path in flist: 

475 fullpath = Path(self.feff_folder, path) 

476 if fullpath.is_dir(): 

477 try: 

478 _feffruns[path] = thisrun = get_feff_pathinfo(fullpath) 

479 if ((len(thisrun.paths) < 1) or 

480 (len(thisrun.ipots) < 1) or thisrun.shell is None): 

481 

482 self.larch.symtable._sys._feffruns.pop(path) 

483 else: 

484 self.feffruns[path] = thisrun 

485 if ((all_catoms or (thisrun.absorber == catom)) and 

486 (all_edges or (thisrun.shell == edge))): 

487 self.fefflist.Append(path) 

488 except: 

489 print(f"could not read Feff calculation from '{path}'") 

490 

491 def onCentralAtom(self, event=None): 

492 self.onSearch() 

493 

494 def onAbsorbingEdge(self, event=None): 

495 self.onSearch() 

496 

497 def onSelAll(self, event=None): 

498 self.fefflist.select_all() 

499 

500 def onSelNone(self, event=None): 

501 self.fefflist.select_none() 

502 

503 def onRemoveFeffFolders(self, event=None): 

504 dlg = RemoveFeffCalcDialog(self, ncalcs=len(self.fefflist.GetCheckedStrings())) 

505 dlg.Raise() 

506 dlg.SetWindowStyle(wx.STAY_ON_TOP) 

507 remove = dlg.GetResponse() 

508 dlg.Destroy() 

509 if remove: 

510 for checked in self.fefflist.GetCheckedStrings(): 

511 shutil.rmtree(unixpath(Pathn(self.feff_folder, checked))) 

512 self.onSearch() 

513 

514 def onFeffFolder(self, event=None): 

515 "prompt for Feff Folder" 

516 dlg = wx.DirDialog(self, 'Select Main Folder for Feff Calculations', 

517 style=wx.DD_DEFAULT_STYLE|wx.DD_CHANGE_DIR) 

518 

519 dlg.SetPath(self.feff_folder) 

520 if dlg.ShowModal() == wx.ID_CANCEL: 

521 return None 

522 self.feff_folder = Path(dlg.GetPath()).absolute() 

523 Path.mkdir(self.feff_folder, mode=755, parents=True, exist_ok=True) 

524 

525 def onImportFeffCalc(self, event=None): 

526 "prompt to import Feff calculation folder" 

527 dlg = wx.DirDialog(self, 'Select Folder wth Feff Calculations', 

528 style=wx.DD_DEFAULT_STYLE|wx.DD_CHANGE_DIR) 

529 

530 dlg.SetPath(self.feff_folder) 

531 if dlg.ShowModal() == wx.ID_CANCEL: 

532 return None 

533 path = Path(dlg.GetPath()).absolute() 

534 if path.exists(): 

535 flist = os.listdir(path) 

536 if ('paths.dat' in flist and 'files.dat' in flist and 

537 'feff0001.dat' in flist and 'feff.inp' in flist): 

538 dname = Path(path).name 

539 dest = unixpath(Path(self.feff_folder, dname)) 

540 shutil.copytree(path, dest) 

541 self.onSearch() 

542 else: 

543 Popup(self, f"{path:s} is not a complete Feff calculation", 

544 "cannot import Feff calculation") 

545 

546 def createMenus(self): 

547 # ppnl = self.plotpanel 

548 self.menubar = wx.MenuBar() 

549 fmenu = wx.Menu() 

550 

551 MenuItem(self, fmenu, "Rescan Main Feff Folder", 

552 "Rescan Feff Folder for Feff calculations", 

553 self.onSearch) 

554 

555 MenuItem(self, fmenu, "Import Feff calculation", 

556 "Import other Feff calculation", 

557 self.onImportFeffCalc) 

558 

559 fmenu.AppendSeparator() 

560 

561 MenuItem(self, fmenu, "Set Main Feff Folder", 

562 "Select Main Feff Folder for Feff calculations", 

563 self.onFeffFolder) 

564 

565 

566 MenuItem(self, fmenu, "Remove Selected Feff calculations", 

567 "Completely remove Feff calculations", self.onRemoveFeffFolders) 

568 

569 fmenu.AppendSeparator() 

570 MenuItem(self, fmenu, "Quit", "Exit", self.onClose) 

571 

572 self.menubar.Append(fmenu, "&File") 

573 self.SetMenuBar(self.menubar) 

574 self.Bind(wx.EVT_CLOSE, self.onClose) 

575 

576 def onClose(self, event=None): 

577 self.Destroy() 

578 

579 

580class FeffResultsBrowserApp(LarchWxApp): 

581 def __init__(self, dat=None, **kws): 

582 self.dat = dat 

583 LarchWxApp.__init__(self, **kws) 

584 

585 def createApp(self): 

586 frame = FeffResultsFrame(feffresult=self.dat) 

587 self.SetTopWindow(frame) 

588 return True 

589 

590if __name__ == '__main__': 

591 dat = None 

592 FeffResultsBrowserApp(dat).MainLoop()