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
« prev ^ index » next coverage.py v7.6.0, created at 2024-10-16 21:04 +0000
1#!/usr/bin/env python
2#
4import sys
5import os
6import time
7from functools import partial
8from pathlib import Path
9import wx
10import wx.lib.mixins.inspection
12import numpy
13import scipy
14import larch
16from wxutils import (MenuItem, Font, Button, Choice, panel_pack)
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
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
31FILE_WILDCARDS = "Data Files(*.0*,*.dat,*.xdi)|*.0*;*.dat;*.xdi|All files (*.*)|*.*"
33ICON_FILE = 'larch.ico'
34BACKGROUND_COLOUR = '#FCFCFA'
35FOREGROUND_COLOUR = '#050520'
37FONTSIZE_FW = 14
39def makeColorPanel(parent, color):
40 p = wx.Panel(parent, -1)
41 p.SetBackgroundColour(color)
42 return p
44def wx_inspect():
45 wx.GetApp().ShowInspectionTool()
47def get_font(size):
48 return wx.Font(size, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD)
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
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
69 self.objtree = wxparent.objtree
71 self.set_textstyle(mode='text')
72 self._larch("_sys.display.colors['text2'] = {'color': 'blue'}",
73 add_history=False)
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
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)
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)
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()
118 def set_textstyle(self, mode='text'):
119 if self.output is None:
120 return
122 display_colors = self.symtable._sys.display.colors
124 textattrs = display_colors.get(mode, {'color':'black'})
125 color = textattrs['color']
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)
134 def write_sys(self, text):
135 sys.stdout.write(text)
136 sys.stdout.flush()
138 def write(self, text, **kws):
139 if text is None:
140 return
141 if self.textstyle is None:
142 self.set_textstyle()
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)
156 def flush(self, *args):
157 self.output.Refresh()
158 self.needs_flush = False
160 def clear_input(self):
161 self._larch.input.clear()
162 self.SetPrompt(True)
164 def onFlushTimer(self, event=None):
165 if self.needs_flush:
166 self.flush()
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:])
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
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()
210 wx.Panel.__init__(self, parent, -1, size=(750, 725))
212 self.splitter = splitter = wx.SplitterWindow(self, style=wx.SP_LIVE_UPDATE)
213 splitter.SetMinimumPaneSize(150)
215 self.objtree = Filling(splitter, rootLabel='_main',
216 fgcol=FOREGROUND_COLOUR, bgcol=BACKGROUND_COLOUR)
218 self.output = wx.TextCtrl(splitter, -1, '',
219 style=wx.TE_MULTILINE|wx.TE_RICH|wx.TE_READONLY)
221 self.output.SetBackgroundColour(BACKGROUND_COLOUR)
222 self.output.SetForegroundColour(FOREGROUND_COLOUR)
223 if font is None:
224 font = get_font(self.fontsize)
226 self.output.SetFont(font)
227 self.objtree.tree.SetFont(font)
228 self.objtree.text.SetFont(font)
230 self.output.CanCopy()
231 self.output.SetInsertionPointEnd()
232 splitter.SplitHorizontally(self.objtree, self.output, 0)
234 ipanel = wx.Panel(self)
236 self.prompt = wx.StaticText(ipanel, label='Larch>', size=(75,-1),
237 style=wx.ALIGN_CENTER|wx.ALIGN_RIGHT)
239 self.input = wx.TextCtrl(ipanel, value='', size=(525,-1),
240 style=wx.TE_LEFT|wx.TE_PROCESS_ENTER)
242 self.input.Bind(wx.EVT_TEXT_ENTER, self.onText)
243 self.input.Bind(wx.EVT_CHAR, self.onChar)
245 self.hist_buff = []
246 self.hist_mark = 0
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)
252 ipanel.SetSizer(isizer)
253 isizer.Fit(ipanel)
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)
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)
268 self.objtree.SetRootObject(self.larchshell.symtable)
269 # root = self.objtree.tree.GetRootItem()
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')
279 def update(self):
280 self.objtree.onRefresh()
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)
291 def onChar(self, event=None):
292 key = event.GetKeyCode()
294 entry = self.input.GetValue().strip()
295 pos = self.input.GetSelection()
296 ctrl = event.ControlDown()
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()
311 def set_input_text(self, text):
312 self.input.SetValue(text)
313 self.input.SetFocus()
314 self.input.SetInsertionPointEnd()
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)
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):
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
338 wx.Frame.__init__(self, parent, -1, size=(800, 725),
339 style= wx.DEFAULT_FRAME_STYLE)
340 self.SetTitle('LarchGUI')
342 self.font = get_font(self.fontsize)
343 self.SetFont(self.font)
344 sbar = self.CreateStatusBar(2, wx.CAPTION)
346 self.SetStatusWidths([-2,-1])
347 self.SetStatusText("Larch initializing...", 0)
349 self.mainpanel = LarchPanel(parent=self, _larch=_larch,
350 historyfile=historyfile,
351 font=self.font)
353 self.larchshell = self.mainpanel.larchshell
354 self._larch = self.larchshell._larch
356 sizer = wx.BoxSizer(wx.VERTICAL)
358 sizer.Add(self.mainpanel, 1, wx.ALL|wx.EXPAND)
360 self.SetSizer(sizer)
362 self.Bind(wx.EVT_SHOW, self.onShow)
363 self.BuildMenus()
364 self.onSelectFont(fsize=self.fontsize)
365 # larchdir = larch.site_config.larchdir
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()
374 def Raise(self):
375 self.SetStatusText("Ready", 0)
376 self.Refresh()
377 wx.Frame.Raise(self)
379 def BuildMenus(self):
380 menuBar = wx.MenuBar()
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)
395 if self.with_inspection:
396 MenuItem(self, fmenu, 'Show wxPython Inspector\tCtrl+I',
397 'Debug wxPython App', self.onWxInspect)
398 fmenu.AppendSeparator()
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)
409 menuBar.Append(fmenu, '&File')
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]
418 MenuItem(self, appmenu, label, label,
419 partial(self.show_subframe,
420 name=appname, creator=creator))
421 menuBar.Append(appmenu, 'Applications')
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()
432 menuBar.Append(fsmenu, 'Font Size')
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)
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
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)
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
462 def onWxInspect(self, event=None):
463 wx.GetApp().ShowInspectionTool()
465 def onXRFviewer(self, event=None):
466 self.larchshell.eval("xrf_plot()")
468 def onClearInput(self, event=None):
469 self.larchshell.clear_input()
471 def onClearInput(self, event=None):
472 self.larchshell.clear_input()
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()
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()
501 if path is None:
502 return
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()
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)
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
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)
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()
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")
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()
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()
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)
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)
603 if dlg.ShowModal() == wx.ID_OK:
604 os.chdir(dlg.GetPath())
605 dlg.Destroy()
606 return get_cwd()
608 def onAbout(self, event=None):
609 about_msg = """LarchGui:
610 %s""" % (make_banner(withlibraries=True))
612 dlg = wx.MessageDialog(self, about_msg,
613 "About LarchGui", wx.OK | wx.ICON_INFORMATION)
614 dlg.ShowModal()
615 dlg.Destroy()
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')
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()
637 def onShow(self, event=None):
638 if event.Show:
639 self.mainpanel.update()
641 def onClose(self, event=None):
642 try:
643 self.Hide()
644 except:
645 pass
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()
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()
671 except:
672 pass
673 if with_sysexit:
674 sys.exit()
675 else:
676 try:
677 event.Veto()
678 except:
679 pass
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)
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
693if __name__ == '__main__':
694 LarchApp().MainLoop()