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

497 statements  

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

1#!/usr/bin/env python 

2# 

3 

4import sys 

5import os 

6import time 

7from functools import partial 

8from pathlib import Path 

9import wx 

10import wx.lib.mixins.inspection 

11 

12import numpy 

13import scipy 

14import larch 

15 

16from wxutils import (MenuItem, Font, Button, Choice, panel_pack) 

17 

18from .gui_utils import LarchWxApp 

19from .readlinetextctrl import ReadlineTextCtrl 

20from .larchfilling import Filling 

21from .columnframe import ColumnDataFileFrame 

22from .athena_importer import AthenaImporter 

23from . import inputhook 

24 

25from larch.io import (read_ascii, read_xdi, read_gsexdi, 

26 gsescan_group, 

27 is_athena_project, AthenaProject) 

28from larch.version import make_banner, version_data 

29from larch.utils import get_cwd, fix_varname 

30 

31FILE_WILDCARDS = "Data Files(*.0*,*.dat,*.xdi)|*.0*;*.dat;*.xdi|All files (*.*)|*.*" 

32 

33ICON_FILE = 'larch.ico' 

34BACKGROUND_COLOUR = '#FCFCFA' 

35FOREGROUND_COLOUR = '#050520' 

36 

37FONTSIZE_FW = 14 

38 

39def makeColorPanel(parent, color): 

40 p = wx.Panel(parent, -1) 

41 p.SetBackgroundColour(color) 

42 return p 

43 

44def wx_inspect(): 

45 wx.GetApp().ShowInspectionTool() 

46 

47def get_font(size): 

48 return wx.Font(size, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD) 

49 

50class LarchWxShell(object): 

51 ps1 = 'Larch>' 

52 ps2 = ' ... >' 

53 def __init__(self, wxparent=None, writer=None, _larch=None, 

54 prompt=None, historyfile=None, output=None, input=None): 

55 self._larch = _larch 

56 self.textstyle = None 

57 self.parent = wxparent 

58 self.prompt = prompt 

59 self.input = input 

60 self.output = output 

61 

62 if _larch is None: 

63 self._larch = larch.Interpreter(historyfile=historyfile, 

64 writer=self) 

65 self._larch.run_init_scripts() 

66 self.writer = self._larch.writer 

67 self.symtable = self._larch.symtable 

68 

69 self.objtree = wxparent.objtree 

70 

71 self.set_textstyle(mode='text') 

72 self._larch("_sys.display.colors['text2'] = {'color': 'blue'}", 

73 add_history=False) 

74 

75 self.symtable.set_symbol('_builtin.force_wxupdate', False) 

76 self.symtable.set_symbol('_sys.wx.inputhook', inputhook) 

77 self.symtable.set_symbol('_sys.wx.ping', inputhook.ping) 

78 self.symtable.set_symbol('_sys.wx.force_wxupdate', False) 

79 self.symtable.set_symbol('_sys.wx.wx_inspect', wx_inspect) 

80 self.symtable.set_symbol('_sys.wx.wxapp', wx.GetApp()) 

81 self.symtable.set_symbol('_sys.wx.parent', wx.GetApp().GetTopWindow()) 

82 self.symtable.set_symbol('_sys.last_eval_time', 0.0) 

83 self.fontsize = FONTSIZE_FW 

84 

85 if self.output is not None: 

86 style = self.output.GetDefaultStyle() 

87 bgcol = style.GetBackgroundColour() 

88 sfont = style.GetFont() 

89 sfont.Family = wx.TELETYPE 

90 sfont.Weight = wx.BOLD 

91 sfont.PointSize = self.fontsize 

92 style.SetFont(sfont) 

93 self.output.SetDefaultStyle(style) 

94 self.textstyle = wx.TextAttr('black', bgcol, sfont) 

95 self.SetPrompt(True) 

96 

97 def onUpdate(self, event=None): 

98 symtable = self.symtable 

99 if symtable.get_symbol('_builtin.force_wxupdate', create=True): 

100 app = wx.GetApp() 

101 evtloop = wx.EventLoop() 

102 while evtloop.Pending(): 

103 evtloop.Dispatch() 

104 app.ProcessIdle() 

105 symtable.set_symbol('_builtin.force_wxupdate', False) 

106 

107 

108 def SetPrompt(self, complete): 

109 if self.prompt is None: 

110 return 

111 sprompt, scolor = self.ps1, '#000075' 

112 if not complete: 

113 sprompt, scolor = self.ps2, '#E00075' 

114 self.prompt.SetLabel(sprompt) 

115 self.prompt.SetForegroundColour(scolor) 

116 self.prompt.Refresh() 

117 

118 def set_textstyle(self, mode='text'): 

119 if self.output is None: 

120 return 

121 

122 display_colors = self.symtable._sys.display.colors 

123 

124 textattrs = display_colors.get(mode, {'color':'black'}) 

125 color = textattrs['color'] 

126 

127 style = self.output.GetDefaultStyle() 

128 bgcol = BACKGROUND_COLOUR 

129 sfont = self.output.GetFont() 

130 style.SetFont(sfont) 

131 self.output.SetDefaultStyle(style) 

132 self.textstyle = wx.TextAttr(color, bgcol, sfont) 

133 

134 def write_sys(self, text): 

135 sys.stdout.write(text) 

136 sys.stdout.flush() 

137 

138 def write(self, text, **kws): 

139 if text is None: 

140 return 

141 if self.textstyle is None: 

142 self.set_textstyle() 

143 

144 if self.output is None or self.textstyle is None: 

145 self.write_sys(text) 

146 else: 

147 self.output.SetInsertionPointEnd() 

148 pos0 = self.output.GetLastPosition() 

149 self.output.WriteText(text) 

150 pos1 = self.output.GetLastPosition() 

151 self.output.SetStyle(pos0, pos1, self.textstyle) 

152 self.output.SetInsertionPoint(pos1) 

153 self.output.Refresh() 

154 wx.CallAfter(self.input.SetFocus) 

155 

156 def flush(self, *args): 

157 self.output.Refresh() 

158 self.needs_flush = False 

159 

160 def clear_input(self): 

161 self._larch.input.clear() 

162 self.SetPrompt(True) 

163 

164 def onFlushTimer(self, event=None): 

165 if self.needs_flush: 

166 self.flush() 

167 

168 def eval(self, text, add_history=True, **kws): 

169 if text is None: 

170 return 

171 if text.startswith('!'): 

172 return os.system(text[1:]) 

173 

174 elif text.startswith('help(') and text.endswith(')'): 

175 topic = text[5:-1] 

176 parent = self.symtable.get_parentpath(topic) 

177 self.objtree.ShowNode("%s.%s" % (parent, topic)) 

178 return 

179 else: 

180 if add_history: 

181 self.parent.AddToHistory(text) 

182 self.write("%s\n" % text) 

183 ret = self._larch.eval(text, add_history=add_history) 

184 if self._larch.error: 

185 self._larch.input.clear() 

186 self._larch.writer.set_textstyle('error') 

187 self._larch.show_errors() 

188 self._larch.writer.set_textstyle('text') 

189 elif ret is not None: 

190 self._larch.writer.write("%s\n" % repr(ret)) 

191 try: 

192 self.objtree.onRefresh() 

193 except ValueError: 

194 pass 

195 self.symtable._sys.last_eval_time = time.time() 

196 self.SetPrompt(self._larch.input.complete) 

197 return ret 

198 

199class LarchPanel(wx.Panel): 

200 """Larch Input/Output Panel + Data Viewer as a wx.Panel, 

201 suitable for embedding into apps 

202 """ 

203 def __init__(self, parent=None, _larch=None, font=None, 

204 historyfile='history_larchgui.lar', **kwds): 

205 self.parent = parent 

206 if not historyfile.startswith(larch.site_config.user_larchdir): 

207 historyfile = Path(larch.site_config.user_larchdir, 

208 historyfile).as_posix() 

209 

210 wx.Panel.__init__(self, parent, -1, size=(750, 725)) 

211 

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

213 splitter.SetMinimumPaneSize(150) 

214 

215 self.objtree = Filling(splitter, rootLabel='_main', 

216 fgcol=FOREGROUND_COLOUR, bgcol=BACKGROUND_COLOUR) 

217 

218 self.output = wx.TextCtrl(splitter, -1, '', 

219 style=wx.TE_MULTILINE|wx.TE_RICH|wx.TE_READONLY) 

220 

221 self.output.SetBackgroundColour(BACKGROUND_COLOUR) 

222 self.output.SetForegroundColour(FOREGROUND_COLOUR) 

223 if font is None: 

224 font = get_font(self.fontsize) 

225 

226 self.output.SetFont(font) 

227 self.objtree.tree.SetFont(font) 

228 self.objtree.text.SetFont(font) 

229 

230 self.output.CanCopy() 

231 self.output.SetInsertionPointEnd() 

232 splitter.SplitHorizontally(self.objtree, self.output, 0) 

233 

234 ipanel = wx.Panel(self) 

235 

236 self.prompt = wx.StaticText(ipanel, label='Larch>', size=(75,-1), 

237 style=wx.ALIGN_CENTER|wx.ALIGN_RIGHT) 

238 

239 self.input = wx.TextCtrl(ipanel, value='', size=(525,-1), 

240 style=wx.TE_LEFT|wx.TE_PROCESS_ENTER) 

241 

242 self.input.Bind(wx.EVT_TEXT_ENTER, self.onText) 

243 self.input.Bind(wx.EVT_CHAR, self.onChar) 

244 

245 self.hist_buff = [] 

246 self.hist_mark = 0 

247 

248 isizer = wx.BoxSizer(wx.HORIZONTAL) 

249 isizer.Add(self.prompt, 0, wx.BOTTOM|wx.CENTER) 

250 isizer.Add(self.input, 1, wx.ALIGN_LEFT|wx.EXPAND) 

251 

252 ipanel.SetSizer(isizer) 

253 isizer.Fit(ipanel) 

254 

255 opts = dict(flag=wx.ALL|wx.EXPAND, border=2) 

256 sizer = wx.BoxSizer(wx.VERTICAL) 

257 sizer.Add(splitter, 1, **opts) 

258 sizer.Add(ipanel, 0, **opts) 

259 

260 self.SetSizer(sizer) 

261 self.larchshell = LarchWxShell(wxparent=self, 

262 _larch = _larch, 

263 historyfile=historyfile, 

264 prompt = self.prompt, 

265 output = self.output, 

266 input = self.input) 

267 

268 self.objtree.SetRootObject(self.larchshell.symtable) 

269 # root = self.objtree.tree.GetRootItem() 

270 

271 

272 def write_banner(self): 

273 self.larchshell.set_textstyle('text2') 

274 self.larchshell.write(make_banner(show_libraries=['numpy', 'scipy', 'matplotlib', 'h5py', 

275 'lmfit', 'xraydb', 'wx','wxmplot'])) 

276 self.larchshell.write("\n \n") 

277 self.larchshell.set_textstyle('text') 

278 

279 def update(self): 

280 self.objtree.onRefresh() 

281 

282 def onText(self, event=None): 

283 text = event.GetString() 

284 self.input.Clear() 

285 if text.lower() in ('quit', 'exit', 'quit()', 'exit()'): 

286 if self.parent.exit_on_close: 

287 self.parent.onExit() 

288 else: 

289 wx.CallAfter(self.larchshell.eval, text) 

290 

291 def onChar(self, event=None): 

292 key = event.GetKeyCode() 

293 

294 entry = self.input.GetValue().strip() 

295 pos = self.input.GetSelection() 

296 ctrl = event.ControlDown() 

297 

298 if key == wx.WXK_RETURN and len(entry) > 0: 

299 pass 

300 if key in (wx.WXK_UP, wx.WXK_DOWN): 

301 if key == wx.WXK_UP: 

302 self.hist_mark = max(0, self.hist_mark-1) 

303 else: 

304 self.hist_mark += 1 

305 try: 

306 wx.CallAfter(self.set_input_text, self.hist_buff[self.hist_mark]) 

307 except IndexError: 

308 wx.CallAfter(self.set_input_text, '') 

309 event.Skip() 

310 

311 def set_input_text(self, text): 

312 self.input.SetValue(text) 

313 self.input.SetFocus() 

314 self.input.SetInsertionPointEnd() 

315 

316 

317 def AddToHistory(self, text=''): 

318 for tline in text.split('\n'): 

319 if len(tline.strip()) > 0: 

320 self.hist_buff.append(tline) 

321 self.hist_mark = len(self.hist_buff) 

322 

323 

324class LarchFrame(wx.Frame): 

325 def __init__(self, parent=None, _larch=None, is_standalone=True, 

326 historyfile='history_larchgui.lar', with_inspection=False, 

327 exit_on_close=False, with_raise=True, **kwds): 

328 

329 self.is_standalone = is_standalone 

330 self.with_inspection = with_inspection 

331 self.exit_on_close = exit_on_close 

332 self.parent = parent 

333 self.historyfile = historyfile 

334 self.subframes = {} 

335 self.last_array_sel = {} 

336 self.fontsize = FONTSIZE_FW 

337 

338 wx.Frame.__init__(self, parent, -1, size=(800, 725), 

339 style= wx.DEFAULT_FRAME_STYLE) 

340 self.SetTitle('LarchGUI') 

341 

342 self.font = get_font(self.fontsize) 

343 self.SetFont(self.font) 

344 sbar = self.CreateStatusBar(2, wx.CAPTION) 

345 

346 self.SetStatusWidths([-2,-1]) 

347 self.SetStatusText("Larch initializing...", 0) 

348 

349 self.mainpanel = LarchPanel(parent=self, _larch=_larch, 

350 historyfile=historyfile, 

351 font=self.font) 

352 

353 self.larchshell = self.mainpanel.larchshell 

354 self._larch = self.larchshell._larch 

355 

356 sizer = wx.BoxSizer(wx.VERTICAL) 

357 

358 sizer.Add(self.mainpanel, 1, wx.ALL|wx.EXPAND) 

359 

360 self.SetSizer(sizer) 

361 

362 self.Bind(wx.EVT_SHOW, self.onShow) 

363 self.BuildMenus() 

364 self.onSelectFont(fsize=self.fontsize) 

365 # larchdir = larch.site_config.larchdir 

366 

367 fico = Path(larch.site_config.icondir, ICON_FILE).absolute() 

368 if fico.exists(): 

369 self.SetIcon(wx.Icon(fico.as_posix(), wx.BITMAP_TYPE_ICO)) 

370 self.mainpanel.write_banner() 

371 if with_raise: 

372 self.Raise() 

373 

374 def Raise(self): 

375 self.SetStatusText("Ready", 0) 

376 self.Refresh() 

377 wx.Frame.Raise(self) 

378 

379 def BuildMenus(self): 

380 menuBar = wx.MenuBar() 

381 

382 fmenu = wx.Menu() 

383 if self.is_standalone: 

384 MenuItem(self, fmenu, "&Read Data File\tCtrl+O", 

385 "Read Data File", self.onReadData) 

386 MenuItem(self, fmenu, "&Read and Run Larch Script\tCtrl+R", 

387 "Read and Execute a Larch Script", self.onRunScript) 

388 MenuItem(self, fmenu, "&Save Session History\tCtrl+S", 

389 "Save Session History to File", self.onSaveHistory) 

390 MenuItem(self, fmenu, 'Change Working Directory\tCtrl+W', 

391 'Change Directory', self.onChangeDir) 

392 MenuItem(self, fmenu, 'Clear Input\tCtrl+D', 

393 'Clear Input', self.onClearInput) 

394 

395 if self.with_inspection: 

396 MenuItem(self, fmenu, 'Show wxPython Inspector\tCtrl+I', 

397 'Debug wxPython App', self.onWxInspect) 

398 fmenu.AppendSeparator() 

399 

400 if self.parent is None and self.exit_on_close: 

401 self.Bind(wx.EVT_CLOSE, self.onExit) 

402 MenuItem(self, fmenu, 'E&xit', 'End program', 

403 self.onExit) 

404 else: 

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

406 MenuItem(self, fmenu, 'Close Display', 

407 'Close display', self.onClose) 

408 

409 menuBar.Append(fmenu, '&File') 

410 

411 _sys = self.larchshell.symtable._sys 

412 if self.is_standalone and hasattr(_sys, 'gui_apps'): 

413 appmenu = wx.Menu() 

414 x_apps = _sys.gui_apps.keys() 

415 for appname in sorted(x_apps): 

416 label, creator = _sys.gui_apps[appname] 

417 

418 MenuItem(self, appmenu, label, label, 

419 partial(self.show_subframe, 

420 name=appname, creator=creator)) 

421 menuBar.Append(appmenu, 'Applications') 

422 

423 fsmenu = wx.Menu() 

424 self.fontsizes = {} 

425 for fsize in (10, 11, 12, 13, 14, 15, 16, 18, 20, 22, 24): 

426 m = MenuItem(self, fsmenu, "%d" % fsize, "%d" % fsize, 

427 self.onSelectFont, kind=wx.ITEM_RADIO) 

428 self.fontsizes[m.GetId()] = fsize 

429 if fsize == self.fontsize: 

430 m.Check() 

431 

432 menuBar.Append(fsmenu, 'Font Size') 

433 

434 hmenu = wx.Menu() 

435 MenuItem(self, hmenu, '&About', 

436 'Information about this program', self.onAbout) 

437 MenuItem(self, hmenu, '&Versions', 

438 'Show versions of Larch and libraries', self.onVersions) 

439 menuBar.Append(hmenu, '&Help') 

440 self.SetMenuBar(menuBar) 

441 

442 def onSelectFont(self, event=None, fsize=None): 

443 if fsize is None: 

444 fsize = self.fontsizes.get(event.GetId(), self.fontsize) 

445 self.fontsize = fsize 

446 

447 def set_fontsize(obj, fsize): 

448 fn = obj.GetFont() 

449 f1, f2 = fn.PixelSize 

450 fn.SetPixelSize(wx.Size(int((f1*fsize/f2)), fsize)) 

451 obj.SetFont(fn) 

452 

453 self.PointSize = fsize 

454 set_fontsize(self, fsize) 

455 set_fontsize(self.mainpanel.output, fsize) 

456 set_fontsize(self.mainpanel.objtree.tree, fsize) 

457 set_fontsize(self.mainpanel.objtree.text, fsize) 

458 self.mainpanel.objtree.text.fontsize = fsize 

459 

460 

461 

462 def onWxInspect(self, event=None): 

463 wx.GetApp().ShowInspectionTool() 

464 

465 def onXRFviewer(self, event=None): 

466 self.larchshell.eval("xrf_plot()") 

467 

468 def onClearInput(self, event=None): 

469 self.larchshell.clear_input() 

470 

471 def onClearInput(self, event=None): 

472 self.larchshell.clear_input() 

473 

474 def show_subframe(self, event=None, name=None, creator=None, **opts): 

475 if name is None or creator is None: 

476 return 

477 shown = False 

478 if name in self.subframes: 

479 try: 

480 self.subframes[name].Raise() 

481 shown = True 

482 except: 

483 del self.subframes[name] 

484 if not shown: 

485 self.subframes[name] = creator(parent=self, 

486 _larch=self.larchshell._larch, 

487 **opts) 

488 self.subframes[name].Show() 

489 

490 def onReadData(self, event=None): 

491 wildcard = 'Data file (*.dat)|*.dat|All files (*.*)|*.*' 

492 dlg = wx.FileDialog(self, message='Open Data File', 

493 defaultDir=get_cwd(), 

494 wildcard=FILE_WILDCARDS, 

495 style=wx.FD_OPEN|wx.FD_CHANGE_DIR) 

496 path = None 

497 if dlg.ShowModal() == wx.ID_OK: 

498 path = Path(dlg.GetPath()).absolute().as_posix() 

499 dlg.Destroy() 

500 

501 if path is None: 

502 return 

503 

504 if is_athena_project(path): 

505 self.show_subframe(name='athena_import', filename=path, 

506 creator=AthenaImporter, 

507 read_ok_cb=self.onReadAthenaProject_OK) 

508 else: 

509 filename = Path(path).fname 

510 pref = fix_varname((filename + '_'*8)[:8]).replace('.', '_').lower() 

511 

512 count, maxcount = 1, 9999 

513 groupname = "%s%3.3i" % (pref, count) 

514 while hasattr(self.larchshell.symtable, groupname) and count < maxcount: 

515 count += 1 

516 groupname = '%s%3.3i' % (pref, count) 

517 

518 fh = open(path, 'r') 

519 line1 = fh.readline().lower() 

520 fh.close() 

521 reader = read_ascii 

522 if 'epics stepscan file' in line1: 

523 reader = read_gsexdi 

524 elif 'epics scan' in line1: 

525 reader = gsescan_group 

526 elif 'xdi' in line1: 

527 reader = read_xdi 

528 

529 dgroup = reader(str(path), _larch=self.larchshell._larch) 

530 dgroup._path = path 

531 dgroup._filename = filename 

532 dgroup._groupname = groupname 

533 self.show_subframe(name='coledit', event=None, 

534 creator=ColumnDataFileFrame, 

535 filename=path, 

536 last_array_sel=self.last_array_sel, 

537 read_ok_cb=self.onReadScan_Success) 

538 

539 

540 def onReadScan_Success(self, script, path, groupname=None, array_sel=None, 

541 overwrite=False): 

542 """ called when column data has been selected and is ready to be used""" 

543 self.larchshell.eval(script.format(group=groupname, path=path)) 

544 if array_sel is not None: 

545 self.last_array_sel = array_sel 

546 self.larchshell.flush() 

547 

548 def onReadAthenaProject_OK(self, path, namelist): 

549 """read groups from a list of groups from an athena project file""" 

550 read_cmd = "_prj = read_athena('{path:s}', do_fft=False, do_bkg=False)" 

551 self.larchshell.eval(read_cmd.format(path=path)) 

552 dgroup = None 

553 script = "{group:s} = _prj.{prjgroup:s}" 

554 for gname in namelist: 

555 this = getattr(self.larchshell.symtable._prj, gname) 

556 gid = str(getattr(this, 'athena_id', gname)) 

557 self.larchshell.eval(script.format(group=gid, prjgroup=gname)) 

558 self.larchshell.eval("del _prj") 

559 

560 def onRunScript(self, event=None): 

561 wildcard = 'Larch file (*.lar)|*.lar|All files (*.*)|*.*' 

562 dlg = wx.FileDialog(self, message='Open and Run Larch Script', 

563 wildcard=wildcard, 

564 style=wx.FD_OPEN|wx.FD_CHANGE_DIR) 

565 if dlg.ShowModal() == wx.ID_OK: 

566 fout = Path(dlg.GetPath()).absolute() 

567 fname = fout.name 

568 os.chdir(fout.parent) 

569 text = "run('%s')" % fname 

570 self.larchshell.write("%s\n" % text) 

571 wx.CallAfter(self.larchshell.eval, text) 

572 dlg.Destroy() 

573 

574 def onSaveHistory(self, event=None): 

575 wildcard = 'Larch file (*.lar)|*.lar|All files (*.*)|*.*' 

576 deffile = 'history.lar' 

577 dlg = wx.FileDialog(self, message='Save Session History File', 

578 wildcard=wildcard, 

579 defaultFile=deffile, 

580 style=wx.FD_SAVE|wx.FD_CHANGE_DIR) 

581 if dlg.ShowModal() == wx.ID_OK: 

582 fout = Path(dlg.GetPath()).absolute().as_posix() 

583 self._larch.input.history.save(fout, session_only=True) 

584 self.SetStatusText("Wrote %s" % fout, 0) 

585 dlg.Destroy() 

586 

587 def onText(self, event=None): 

588 text = event.GetString() 

589 self.larchshell.write("%s\n" % text) 

590 self.input.Clear() 

591 if text.lower() in ('quit', 'exit', 'quit()', 'exit()'): 

592 if self.exit_on_close: 

593 self.onExit() 

594 else: 

595 self.panel.AddToHistory(text) 

596 wx.CallAfter(self.larchshell.eval, text) 

597 

598 def onChangeDir(self, event=None): 

599 dlg = wx.DirDialog(None, 'Choose a Working Directory', 

600 defaultPath = get_cwd(), 

601 style = wx.DD_DEFAULT_STYLE) 

602 

603 if dlg.ShowModal() == wx.ID_OK: 

604 os.chdir(dlg.GetPath()) 

605 dlg.Destroy() 

606 return get_cwd() 

607 

608 def onAbout(self, event=None): 

609 about_msg = """LarchGui: 

610 %s""" % (make_banner(withlibraries=True)) 

611 

612 dlg = wx.MessageDialog(self, about_msg, 

613 "About LarchGui", wx.OK | wx.ICON_INFORMATION) 

614 dlg.ShowModal() 

615 dlg.Destroy() 

616 

617 def onVersions(self, event=None): 

618 vdat = version_data(with_libraries=True) 

619 out = [] 

620 for key, val in vdat.items(): 

621 out.append(f"{key:20s}: {val}") 

622 version_message = '\n'.join(out) 

623 dlg = wx.Dialog(self, wx.ID_ANY, size=(700, 400), 

624 title='Larch Versions') 

625 

626 font = get_font(self.fontsize) 

627 dlg.SetFont(font) 

628 panel = wx.Panel(dlg) 

629 txt = wx.StaticText(panel, label=version_message) 

630 s = wx.Sizer 

631 sizer = wx.BoxSizer(wx.VERTICAL) 

632 sizer.Add(txt, 1, wx.LEFT|wx.ALL, 5) 

633 panel.SetSizer(sizer) 

634 panel_pack(dlg, panel) 

635 dlg.Show() 

636 

637 def onShow(self, event=None): 

638 if event.Show: 

639 self.mainpanel.update() 

640 

641 def onClose(self, event=None): 

642 try: 

643 self.Hide() 

644 except: 

645 pass 

646 

647 def onExit(self, event=None, force=False, with_sysexit=True): 

648 if not self.exit_on_close: 

649 self.Hide() 

650 return 

651 if force: 

652 ret = wx.ID_YES 

653 else: 

654 dlg = wx.MessageDialog(None, 'Really Quit?', 'Question', 

655 wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION) 

656 ret = dlg.ShowModal() 

657 

658 if ret == wx.ID_YES: 

659 try: 

660 self._larch.input.history.save() 

661 except: 

662 pass 

663 try: 

664 try: 

665 for a in self.GetChildren(): 

666 a.Destroy() 

667 except: 

668 pass 

669 self.Destroy() 

670 

671 except: 

672 pass 

673 if with_sysexit: 

674 sys.exit() 

675 else: 

676 try: 

677 event.Veto() 

678 except: 

679 pass 

680 

681class LarchApp(LarchWxApp): 

682 "simple app to wrap LarchFrame" 

683 def __init__(self, with_inspection=False, **kws): 

684 self.with_inspection = with_inspection 

685 LarchWxApp.__init__(self, **kws) 

686 

687 def createApp(self): 

688 frame = LarchFrame(exit_on_close=True, with_inspection=self.with_inspection) 

689 frame.Show() 

690 self.SetTopWindow(frame) 

691 return True 

692 

693if __name__ == '__main__': 

694 LarchApp().MainLoop()