pax_global_header00006660000000000000000000000064144104570440014515gustar00rootroot0000000000000052 comment=e64882eae9be271a4b6e8037d6ec03b66bafdf54 wxutils-0.3.0/000077500000000000000000000000001441045704400132345ustar00rootroot00000000000000wxutils-0.3.0/.gitignore000066400000000000000000000002311441045704400152200ustar00rootroot00000000000000*.py[cod] *~ # C extensions *.so # Packages *.egg *.egg-info dist build eggs parts bin var sdist __pycache__ .coverage doc/_build wxutils/version.py wxutils-0.3.0/.travis.yml000066400000000000000000000015041441045704400153450ustar00rootroot00000000000000# Config file for automatic testing at travis-ci.org language: python sudo: false python: - 2.7 - 3.6 before_install: - if [[ "$TRAVIS_PYTHON_VERSION" == "2.7" ]]; then wget https://repo.continuum.io/miniconda/Miniconda-latest-Linux-x86_64.sh -O miniconda.sh; else wget https://repo.continuum.io/miniconda/Miniconda3-latest-Linux-x86_64.sh -O miniconda.sh; fi - bash miniconda.sh -b -p $HOME/miniconda - export PATH="$HOME/miniconda/bin:$PATH" - hash -r - conda config --set always_yes yes --set changeps1 no - conda update -q conda - conda info -a install: - conda create -q -n test_env python=$TRAVIS_PYTHON_VERSION six numpy - source activate test_env - conda install pytest wxpython - python setup.py install script: - cd tests - pytest wxutils-0.3.0/INSTALL000066400000000000000000000000441441045704400142630ustar00rootroot00000000000000To install, use:: pip install . wxutils-0.3.0/LICENSE000066400000000000000000000023011441045704400142350ustar00rootroot00000000000000Copyright, Licensing, and Re-distribution ----------------------------------------- This code and all files here are distributed under the following license: Copyright (c) 2013-2018 Matthew Newville, The University of Chicago Permission to use and redistribute the source code or binary forms of this software and its documentation, with or without modification is hereby granted provided that the above notice of copyright, these terms of use, and the disclaimer of warranty below appear in the source code and documentation, and that none of the names of above institutions or authors appear in advertising or endorsement of works derived from this software without specific prior written permission from all parties. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THIS SOFTWARE. wxutils-0.3.0/README.rst000066400000000000000000000034401441045704400147240ustar00rootroot00000000000000wxutils ======= wxutils provides wxPython utilities and convenience functions. The wxutils library is a small collection of functions and classes, and is by no means comprehensive. The aim is to simplify code, reduce boiler-plate, make wxPython coding a bit more python-like, and prevent repeating code across several projects. The largest share of classes in wxutils are simplified versions of wxPython widgets with limited but common attributes and patterns. For example:: btn = wxutils.Button(parent, label, action=action, **kws) binds a callback function (the "action") to a wx.Button, corresponding to:: b = wx.Button(parent, label=label, **kws) if callable(action): parent.Bind(wx.EVT_BUTTON, action, b) Yes, this can be viewed as merely a convenience, and not completely general. But it is a remarkably common pattern (at least in my code), replaces 3 lines with 1, and hides the ugliest parts of wxPython. There are several similar convenience widgets, including Check, Choice, and SimpleText (a simplified variant of StaticText), MenuItem, Font, HLine, OkCancel, HyperText. In addition, there are more complex widgets, such as * ``FloatCtrl`` a wx.TextCrtl that allows numerical input only. Precision, upper bound, and lower bound can be set, and a callback can be bound to the control. * ``NumericCombo`` wx.ComboBox with a FloatCtrl * ``EditableListBox`` a list box with a built-in popup menu to arrange order of the items with "move up/down, to top, to bottom" * ``YesNo`` a wx.Choice of only 'No' and 'Yes' * ``GridPanel`` a combined GridBagSizer and Panel that simplifies adding widgets to a GridBagSizer. * ``FileOpen``, ``FileSave`` wrappers (supporting wildcards) to FileDialog. And some other miscellaneous stuff as well. Yeah, it's sort of a motley collection. wxutils-0.3.0/pyproject.toml000066400000000000000000000003141441045704400161460ustar00rootroot00000000000000[build-system] requires = ["setuptools>=45", "wheel", "setuptools_scm>=6.2"] build-backend = "setuptools.build_meta" [tool.setuptools_scm] write_to = "wxutils/version.py" version_scheme = "post-release" wxutils-0.3.0/setup.cfg000066400000000000000000000030651441045704400150610ustar00rootroot00000000000000[metadata] name = wxutils description = utilities and conveniences for wxPython long_description = wxutils provides utilities and convenience functions for wxPython. The wxutils library is a small collection of functions and classes, and is by no means comprehensive. The aim is to simplify code, reduce boiler-plate, make wxPython coding a bit more python-like, and prevent repeating code across projects. Many of the classes in wxutils are simplified versions of wxPython widgets that add callbacks for normal action events. author = Matthew Newville author_email = matt.newville@gmail.com url = https://github.com/newville/wxutils license = BSD License platforms = any classifiers = Development Status :: 5 - Production/Stable Intended Audience :: End Users/Desktop Intended Audience :: Developers License :: OSI Approved :: MIT License Operating System :: MacOS :: MacOS X Operating System :: Microsoft :: Windows Operating System :: POSIX Programming Language :: Python Programming Language :: Python :: 3 :: Only Programming Language :: Python :: 3.7 Programming Language :: Python :: 3.8 Programming Language :: Python :: 3.9 Programming Language :: Python :: 3.10 keywords = wxPython project_urls = Source = https://github.com/newville/wxutils Documentation = https://github.com/newville/wxutils/ Tracker = https://github.com/newville/wxutils/issues [options] packages = find: include_package_data = True python_requires = >=3.6 setup_requires = setuptools_scm install_requires = wxPython wxutils-0.3.0/setup.py000066400000000000000000000001321441045704400147420ustar00rootroot00000000000000#!/usr/bin/env python import setuptools if __name__ == "__main__": setuptools.setup() wxutils-0.3.0/tests/000077500000000000000000000000001441045704400143765ustar00rootroot00000000000000wxutils-0.3.0/tests/run_testcov.sh000066400000000000000000000000621441045704400173030ustar00rootroot00000000000000pythonw -m pytest --cov=wxutils --cov-report=html wxutils-0.3.0/tests/test_imports.py000066400000000000000000000011141441045704400175010ustar00rootroot00000000000000import wx import time import unittest import pytest from wxutils import (Button, CEN, Check, Choice, EditableListBox, FRAMESTYLE, FileOpen, FileSave, FloatCtrl, Font, GUIColors, GridPanel, HLine, HyperText, LCEN, LEFT, MenuItem, Popup, RCEN, RIGHT, RowPanel, SimpleText, TextCtrl, fix_filename, get_icon, pack) class TestCase(unittest.TestCase): def test_imports(self): import wxutils assert('Check' in dir(wxutils)) if __name__ == '__main__': pytest.main(['-v', '-x', '-s']) wxutils-0.3.0/tests/test_wxdemo.py000066400000000000000000000207551441045704400173230ustar00rootroot00000000000000import time import os import sys import wx from functools import partial from wxutils import (Button, CEN, Check, Choice, EditableListBox, OkCancel, FRAMESTYLE, FileOpen, FileSave, FloatCtrl, FloatSpin, Font, GUIColors, GridPanel, LabeledTextCtrl, HLine, HyperText, LEFT, MenuItem, Popup, RIGHT, RowPanel, SimpleText, TextCtrl, fix_filename, get_icon, pack, BitmapButton, ToggleButton, YesNo, NumericCombo, make_steps) from wxutils.periodictable import PeriodicTablePanel, PTableFrame from wxutils.filechecklist import FileCheckList PY_FILES = "Python scripts (*.py)|*.py" ALL_FILES = "All files (*.*)|*.*" class DemoFrame(wx.Frame): def __init__(self, idletime=30.0): wx.Frame.__init__(self, None, -1, 'wxutil demo', style=wx.DEFAULT_FRAME_STYLE|wx.RESIZE_BORDER|wx.TAB_TRAVERSAL) self.SetTitle('wxutil demo') self.SetFont(Font(11)) self.idletime = idletime self.deadtime = time.time() + idletime self.set_menu() self.statusbar = self.CreateStatusBar(2, 1) self.statusbar.SetStatusWidths([-2, -1]) statusbar_fields = ['Initializing....', ' '] for i in range(len(statusbar_fields)): self.statusbar.SetStatusText(statusbar_fields[i], i) self.Bind(wx.EVT_CLOSE, self.onExit) panel = GridPanel(self, nrows=8, ncols=4) tctrl_name = TextCtrl(panel, value='', action=self.onName, size=(250, -1)) lctrl_addr = LabeledTextCtrl(panel, value='<>', action=self.onAddr, labeltext=' Address: ', size=(250, -1)) lab3 = HyperText(panel,' FloatCtrl: ', size=(100, -1), action=self.onHyperText) val3 = FloatCtrl(panel, '3', action=self.onFloat1, precision=2, minval=0, maxval=1000, size=(250, -1)) lab4 = HyperText(panel,' FloatSpin: ', size=(100, -1), action=self.onHyperText) val4 = FloatSpin(panel, '12.2', action=self.onFloatSpin, digits=2, increment=0.1, size=(250, -1)) labx = HyperText(panel,' NumericCombo: ', size=(100, -1), action=self.onHyperText) steps = make_steps(prec=1, tmin=0, tmax=100) valx = NumericCombo(panel, steps, precision=1) self.choice1 = Choice(panel, size=(200, -1),action=self.onChoice) self.choice1.SetChoices(['Apple', 'Banana', 'Cherry']) yesno = YesNo(panel) check1 = Check(panel, label='enable? ', action=self.onCheck) btn1 = Button(panel, label='Start', size=(100, -1), action=self.onStart) pinbtn = BitmapButton(panel, get_icon('pin'), size=(50, -1), action=partial(self.onBMButton, opt='pin1'), tooltip='use last point selected from plot') togbtn = ToggleButton(panel, 'Press Me', action=self.onToggleButton, size=(100, -1), tooltip='do it, do it now, you will like it') browse_btn = Button(panel, 'Open File', action=self.onFileOpen, size=(150, -1)) okcancel = OkCancel(panel, onOK=self.onOK, onCancel=self.onCancel) ptable_btn = Button(panel, 'Show Periodic Table', action=self.onPTable, size=(175, -1)) edlist_btn = Button(panel, 'Show Editable Listbox', action=self.onEdList, size=(175, -1)) filelist_btn = Button(panel, 'Show File CheckList', action=self.onFileList, size=(175, -1)) panel.AddText(' Name: ', style=LEFT) panel.Add(tctrl_name, dcol=2) panel.Add(lctrl_addr.label, newrow=True) panel.Add(lctrl_addr, dcol=2) panel.Add(lab3, newrow=True) panel.Add(val3, dcol=3) panel.Add(lab4, newrow=True) panel.Add(val4, dcol=3) panel.Add(labx, newrow=True) panel.Add(valx, dcol=3) panel.AddText(' Choice : ', newrow=True) panel.Add(check1) panel.Add(self.choice1) panel.AddText(' Yes or No: ', newrow=True) panel.Add(yesno) panel.Add(HLine(panel, size=(500, -1)), dcol=3, newrow=True) panel.Add(btn1, newrow=True) panel.Add(pinbtn) panel.Add(togbtn) panel.Add(browse_btn, newrow=True) panel.Add(ptable_btn) panel.Add(edlist_btn, newrow=True) panel.Add(filelist_btn) panel.Add(okcancel, newrow=True) panel.pack() self.timer = wx.Timer(self) self.Bind(wx.EVT_TIMER, self.onTimer, self.timer) fsizer = wx.BoxSizer(wx.VERTICAL) fsizer.Add(panel, 0, LEFT|wx.EXPAND) wx.CallAfter(self.init_timer) psize = panel.GetBestSize() self.SetSize((psize[0]+5, psize[1]+25)) pack(self, fsizer) self.Refresh() def init_timer(self): self.timer.Start(100) def onTimer(self, event=None): time_remaining = self.deadtime - time.time() msg = "Time remaining = %.1f sec" % (time_remaining) self.statusbar.SetStatusText(msg, 1) if time_remaining < 0: print("quitting...") self.onExit() def report(self, reason, value): self.statusbar.SetStatusText("%s: %s" % (reason, value), 0) self.deadtime = time.time() + self.idletime def set_menu(self): menu = wx.Menu() mexit = MenuItem(self, menu, "Q&uit", "Quit Program", self.onExit) menubar = wx.MenuBar() menubar.Append(menu, "&File"); self.SetMenuBar(menubar) def onOK(self, event=None): self.report("on OK: ", '') def onCancel(self, event=None): self.report("on Cancel: ", '') def onName(self, event=None): self.report("on Name: ", event) def onAddr(self, event=None): self.report("on Addr: ", event) def onHyperText(self, event=None, label=None): self.report("on HyperText ", label) def onFloat1(self, value=None): self.report("on Float1 ", value) def onFloatSpin(self, event=None): self.report("on FloatSpin ", event.GetString()) def onChoice(self, event=None): self.report("on Choice ", event.GetString()) def onCheck(self, event=None): self.report("on Check ", event.IsChecked()) self.choice1.Enable(event.IsChecked()) def onStart(self, event=None): self.report("on Start Button ", '') def onBMButton(self, event=None, opt='xxx'): self.report("on Bitmap Button ", opt) def onToggleButton(self, event=None): self.report(" on Toggle Button %s " % event.GetString(), event.IsChecked()) def onPTable(self, event=None): PTableFrame(fontsize=10).Show() self.report("on Periodic Table: ", '') def onEdList(self, event=None): frame = wx.Frame(self) edlist = EditableListBox(frame, self.onEdListSelect) edlist.Append(" Item 1 ") edlist.Append(" Item 2 ") edlist.Append(" Next ") self.report("on Edit List: ", '') sizer = wx.BoxSizer(wx.VERTICAL) sizer.Add(edlist, 1, wx.EXPAND|wx.ALL, 5) pack(frame, sizer) frame.Show() frame.Raise() def onEdListSelect(self, event=None, **kws): self.report(" Editable List selected ", event.GetString()) def onFileList(self, event=None): frame = wx.Frame(self) edlist = FileCheckList(frame, select_action=self.onFileListSelect) for i in range(8): edlist.Append("File.%3.3d" % (i+1)) self.report("on File List: ", '') sizer = wx.BoxSizer(wx.VERTICAL) sizer.Add(edlist, 1, wx.EXPAND|wx.ALL, 5) pack(frame, sizer) frame.Show() frame.Raise() def onFileListSelect(self, event=None, **kws): self.report(" File List selected ", event.GetString()) def onFileOpen(self, event=None): wildcards = "%s|%s" % (PY_FILES, ALL_FILES) dlg = wx.FileDialog(self, message='Select File', wildcard=wildcards, style=wx.FD_OPEN) if dlg.ShowModal() == wx.ID_OK: path = os.path.abspath(dlg.GetPath()) self.report("file ", path) dlg.Destroy() def onExit(self, event=None): self.Destroy() def test_wxdemo(idletime=5.0): app = wx.App() DemoFrame(idletime=idletime).Show(True) app.MainLoop() if __name__ == '__main__': test_wxdemo(idletime=30.0) wxutils-0.3.0/wxutils/000077500000000000000000000000001441045704400147535ustar00rootroot00000000000000wxutils-0.3.0/wxutils/__init__.py000066400000000000000000000025151441045704400170670ustar00rootroot00000000000000#!/usr/bin/env python """ simplified wx widgets and utilities """ from .version import version as __version__ __author__ = 'Matthew Newville' import sys import wx if sys.platform.lower() == 'darwin': wx.PyApp.IsDisplayAvailable = lambda _: True from . import utils from .utils import (gcd, ExceptionPopup, set_sizer, pack, panel_pack, show_wxsizes, SetTip, Font, HLine, Check, MenuItem, Popup, RIGHT, LEFT, CEN , LCEN, RCEN, CCEN, LTEXT, FRAMESTYLE) from .buttons import Button, ToggleButton, BitmapButton from .choice import Choice, YesNo from .dates import hms, DateTimeCtrl from .dialogs import (OkCancel, FileOpen, FileSave, SelectWorkdir, SavedParameterDialog, fix_filename) from .text import SimpleText, TextCtrl, LabeledTextCtrl, HyperText from .filechecklist import FileCheckList, FileDropTarget from .listbox import EditableListBox from .gridpanel import GridPanel, RowPanel from .icons import get_icon from .floats import (make_steps, set_float, FloatCtrl, NumericCombo, FloatSpin, FloatSpinWithPin) from .notebooks import flatnotebook from .periodictable import PeriodicTablePanel from .paths import (platform, nativepath, get_homedir, get_configfile, save_configfile, get_cwd) from .colors import GUIColors, COLORS, set_color wxutils-0.3.0/wxutils/buttons.py000066400000000000000000000015771441045704400170350ustar00rootroot00000000000000import wx def Button(parent, label, action=None, **kws): """Simple button with bound action b = Button(parent, label, action=None, **kws) """ thisb = wx.Button(parent, label=label, **kws) if callable(action): parent.Bind(wx.EVT_BUTTON, action, thisb) return thisb def BitmapButton(parent, bmp, action=None, tooltip=None, size=(20, 20), **kws): b = wx.BitmapButton(parent, id=-1, bitmap=bmp, size=size, **kws) if action is not None: parent.Bind(wx.EVT_BUTTON, action, b) if tooltip is not None: b.SetToolTip(tooltip) return b def ToggleButton(parent, label, action=None, tooltip=None, size=(25, 25), **kws): b = wx.ToggleButton(parent, -1, label, size=size, **kws) if action is not None: b.Bind(wx.EVT_TOGGLEBUTTON, action) if tooltip is not None: b.SetToolTip(tooltip) return b wxutils-0.3.0/wxutils/choice.py000066400000000000000000000032301441045704400165550ustar00rootroot00000000000000import wx class Choice(wx.Choice): """Simple Choice with default and bound action c = Choice(panel, choices, default=0, action=None, **kws) """ def __init__(self, parent, choices=None, default=0, action=None, **kws): if choices is None: choices = [] wx.Choice.__init__(self, parent, -1, choices=choices, **kws) self.Select(default) self.Bind(wx.EVT_CHOICE, action) def SetChoices(self, choices): index = 0 try: current = self.GetStringSelection() if current in choices: index = choices.index(current) except: pass self.Clear() self.AppendItems(choices) self.SetStringSelection(choices[index]) class YesNo(wx.Choice): """ A simple wx.Choice with choices set to ('No', 'Yes') c = YesNo(parent, defaultyes=True, choices=('No', 'Yes')) has methods SetChoices(self, choices) and Select(choice) """ def __init__(self, parent, defaultyes=True, choices=('No', 'Yes'), size=(60, -1)): wx.Choice.__init__(self, parent, -1, size=size) self.choices = choices self.Clear() self.SetItems(self.choices) try: default = int(defaultyes) except: default = 0 self.SetSelection(default) def SetChoices(self, choices): self.Clear() self.SetItems(choices) self.choices = choices def Select(self, choice): if isinstance(choice, int): self.SetSelection(0) elif choice in self.choices: self.SetSelection(self.choices.index(choice)) wxutils-0.3.0/wxutils/colors.py000066400000000000000000000015051441045704400166270ustar00rootroot00000000000000import wx COLORS = {'text': wx.Colour(0, 0, 0), 'bg': wx.Colour(240,240,230), 'nb_active': wx.Colour(254,254,195), 'nb_area': wx.Colour(250,250,245), 'nb_text': wx.Colour(10,10,180), 'nb_activetext': wx.Colour(80,10,10), 'title': wx.Colour(80,10,10), 'pvname': wx.Colour(10,10,80), 'list_bg': wx.Colour(255, 255, 250), 'list_fg': wx.Colour(5, 5, 25)} class GUIColors(object): def __init__(self): for key, rgb in COLORS.items(): setattr(self, key,rgb) def set_color(widget, color, bg=None): if color not in COLORS: color = 'text' widget.SetForegroundColour(COLORS[color]) if bg is not None: if bg not in COLORS: color = 'bg' method = widget.SetBackgroundColour(COLORS[bg]) wxutils-0.3.0/wxutils/dates.py000066400000000000000000000027361441045704400164350ustar00rootroot00000000000000#!/usr/bin/env python # epics/wx/utils.py """ date utilities and widgets """ import os from datetime import timedelta import wx import wx.lib.masked as masked def hms(secs): "format time in seconds to H:M:S" return str(timedelta(seconds=int(secs))) class DateTimeCtrl(object): """ Simple Combined date/time control """ def __init__(self, parent, name='datetimectrl', use_now=False): self.name = name panel = self.panel = wx.Panel(parent) bgcol = wx.Colour(250, 250, 250) datestyle = wx.DP_DROPDOWN|wx.DP_SHOWCENTURY|wx.DP_ALLOWNONE self.datectrl = wx.DatePickerCtrl(panel, size=(120, -1), style=datestyle) self.timectrl = masked.TimeCtrl(panel, -1, name=name, limited=False, fmt24hr=True, oob_color=bgcol) timerheight = self.timectrl.GetSize().height spinner = wx.SpinButton(panel, -1, wx.DefaultPosition, (-1, timerheight), wx.SP_VERTICAL ) self.timectrl.BindSpinButton(spinner) sizer = wx.BoxSizer(wx.HORIZONTAL) sizer.Add(self.datectrl, 0, wx.ALIGN_CENTER) sizer.Add(self.timectrl, 0, wx.ALIGN_CENTER) sizer.Add(spinner, 0, wx.ALIGN_LEFT) panel.SetSizer(sizer) sizer.Fit(panel) if use_now: self.timectrl.SetValue(wx.DateTime_Now()) wxutils-0.3.0/wxutils/debugtime.py000077500000000000000000000033621441045704400173010ustar00rootroot00000000000000import time import sys class debugtime(object): """debugtimer returns a Timer object that can be used to time the running of portions of code, and then write a simple report of the results. the Timer object has methods: clear() reset Timer add(msg, verbose=False) record time, with message get_report() get text of timer report show() print timer report An example: timer = debugtimer() x = 1 timer.add('now run foo') foo() timer.add('now run bar') bar() timer.show_report() """ def __init__(self): self.clear() self.add('debugtime init') def clear(self): self.times = [] def add(self,msg='', verbose=False): # print msg self.times.append((msg,time.time())) if verbose: sys.stdout.write("%s\n"% msg) def show(self, writer=None, clear=True): if writer is None: writer = sys.stdout.write writer('%s\n' % self.get_report()) if clear: self.clear() def get_report(self): m0, t0 = self.times[0] tlast= t0 out = ["# %s %s " % (m0,time.ctime(t0))] lmsg = 0 for m, t in self.times[1:]: lmsg = max(lmsg, len(m)) m = '# Message' m = m + ' '*(lmsg-len(m)) out.append("#--------------------" + '-'*lmsg) out.append('%s Delta(s) Total(s)' % (m)) for m,t in self.times[1:]: tt = t-t0 dt = t-tlast if len(m) 1: for i in range(t.count('.') - 1): idot = t.find('.') t = "%s_%s" % (t[:idot], t[idot+1:]) return t def FileOpen(parent, message, default_dir=None, default_file=None, multiple=False, wildcard=None): """File Open dialog wrapper. returns full path on OK or None on Cancel """ out = None if default_dir is None: try: default_dir = get_cwd() except: pass if wildcard is None: wildcard = 'All files (*.*)|*.*' style = wx.FD_OPEN|wx.FD_CHANGE_DIR if multiple: style = style|wx.FD_MULTIPLE dlg = wx.FileDialog(parent, message=message, wildcard=wildcard, defaultFile=default_file, defaultDir=default_dir, style=style) out = None if dlg.ShowModal() == wx.ID_OK: out = os.path.abspath(dlg.GetPath()) dlg.Destroy() return out def FileSave(parent, message, default_file=None, default_dir=None, wildcard=None): "File Save dialog" out = None if wildcard is None: wildcard = 'All files (*.*)|*.*' if default_dir is None: try: default_dir = get_cwd() except: pass dlg = wx.FileDialog(parent, message=message, wildcard=wildcard, defaultFile=default_file, style=wx.FD_SAVE|wx.FD_CHANGE_DIR) if dlg.ShowModal() == wx.ID_OK: out = os.path.abspath(dlg.GetPath()) dlg.Destroy() return out def SelectWorkdir(parent, message='Select Working Folder...'): "prompt for and change into a working directory " dlg = wx.DirDialog(parent, message, style=wx.DD_DEFAULT_STYLE|wx.DD_CHANGE_DIR) path = os.path.abspath(os.curdir) dlg.SetPath(path) if dlg.ShowModal() == wx.ID_CANCEL: return None path = os.path.abspath(dlg.GetPath()) dlg.Destroy() os.chdir(path) return path def OkCancel(panel, onOK=None, onCancel=None): """Add OK / Cancel buttons OkCancel(panel, onOK=None, onCancel=None) returns a wx.StdDialogButtonSizer """ btnsizer = wx.StdDialogButtonSizer() _ok = wx.Button(panel, wx.ID_OK) _no = wx.Button(panel, wx.ID_CANCEL) panel.Bind(wx.EVT_BUTTON, onOK, _ok) panel.Bind(wx.EVT_BUTTON, onCancel, _no) _ok.SetDefault() btnsizer.AddButton(_ok) btnsizer.AddButton(_no) btnsizer.Realize() return btnsizer class SavedParameterDialog(wx.Dialog): """dialog to prompt for string with read/write of previous value to config file dlg = SavedParameterDialog(label='Prefix', title='Parameter Prompt', configfile='.myparam.dat') res = dlg.GetResponse() dlg.Destroy() if res.ok: prefix = res.value """ def __init__(self, label='Value:', title='Parameter Prompt', configfile=None, **kws): wx.Dialog.__init__(self, None, wx.ID_ANY, title=title) value = '' self.configfile = configfile if configfile is not None: cfile = get_configfile(configfile) if cfile is not None: with open(cfile, 'r') as fh: for line in fh.readlines(): line = line.strip().replace('\n','').replace('\r','') if line.startswith('#') or len(line)<1: continue value = line break self.text = wx.TextCtrl(self, value=value, size=(250, -1)) panel = GridPanel(self, ncols=3, nrows=4, pad=2) panel.Add(wx.StaticText(self, label=label), newrow=True) panel.Add(self.text) panel.Add(OkCancel(self), dcol=2, newrow=True) panel.pack() def GetResponse(self, master=None, gname=None, ynorm=True): self.Raise() response = namedtuple('Reponse', ('ok', 'value')) ok, value = False, None if self.ShowModal() == wx.ID_OK: value = self.text.GetValue() ok = True if ok and self.configfile is not None: tout = '# saved %s\n %s \n\n' % (time.ctime(), value) save_configfile(self.configfile, tout) return response(ok, value) wxutils-0.3.0/wxutils/filechecklist.py000066400000000000000000000125761441045704400201510ustar00rootroot00000000000000import wx class FileDropTarget(wx.FileDropTarget): def __init__(self, main): wx.FileDropTarget.__init__(self) self.reader = getattr(main, 'onRead', None) def OnDropFiles(self, x, y, filenames): if self.reader is not None: for file in filenames: self.reader(file) return (self.reader is not None) class FileCheckList(wx.CheckListBox): """ A ListBox with pop-up menu to arrange order of items and remove items from list supply select_action for EVT_LISTBOX selection action """ def __init__(self, parent, main=None, select_action=None, right_click=True, remove_action=None, pre_actions=None, post_actions=None, **kws): wx.CheckListBox.__init__(self, parent, **kws) self.SetDropTarget(FileDropTarget(main)) if select_action is not None: self.Bind(wx.EVT_LISTBOX, select_action) self.Bind(wx.EVT_CHECKLISTBOX, self.check_event) self.remove_action = remove_action self.rclick_actions = {} core_actions = [("Select All", self.select_all), ("Select All above", self.select_allabove), ("Select All below", self.select_allbelow), ("Select None", self.select_none), ("Select None above", self.select_noneabove), ("Select None below", self.select_nonebelow), ("--sep--", None), ("Move up", None), ("Move down", None), ("Move to Top", None), ("Move to Bottom", None), ("Remove from List", None)] click_actions = [] if pre_actions is not None: click_actions.extend(pre_actions) click_actions.append(("--sep--", None)) click_actions.extend(core_actions) if post_actions is not None: click_actions.append(("--sep--", None)) click_actions.extend(post_actions) click_actions.append(("--sep--", None)) if right_click: self.Bind(wx.EVT_RIGHT_DOWN, self.onRightClick) for title, action in click_actions: self.rclick_actions[title] = action self.Bind(wx.EVT_MENU, self.onRightEvent) def check_event(self, evt=None): index = evt.GetSelection() label = self.GetString(index) self.SetSelection(index) def onRightClick(self, evt=None): menu = wx.Menu() self.rclick_wids = {} for label, action in self.rclick_actions.items(): if label == '--sep--': menu.AppendSeparator() else: wid = menu.Append(-1, label) self.rclick_wids[wid.Id] = (label, action) self.PopupMenu(menu) menu.Destroy() def rename_item(self, old, new): names = self.GetItems() if old not in names: return i = names.index(old) names[i] = new self.Clear() for name in names: self.Append(name) def onRightEvent(self, event=None): idx = self.GetSelection() if idx < 0: # no item selected return names = self.GetItems() this = names[idx] do_relist = False label, action = self.rclick_wids[event.GetId()] if label == "Move up" and idx > 0: names.pop(idx) names.insert(idx-1, this) do_relist = True elif label == "Move down" and idx < len(names): names.pop(idx) names.insert(idx+1, this) do_relist = True elif label == "Move to Top": names.pop(idx) names.insert(0, this) do_relist = True elif label == "Move to Bottom": names.pop(idx) names.append(this) do_relist = True elif label == "Remove from List": names.pop(idx) if self.remove_action is not None: self.remove_action(this) do_relist = True elif action is not None: action(event=event) if do_relist: self.refresh(names) def refresh(self, names): self.Clear() for name in names: self.Append(name) def select_all(self, event=None): self.SetCheckedStrings(self.GetStrings()) def select_none(self, event=None): self.SetCheckedStrings([]) def select_allabove(self, event=None, name=None): self._alter_list(select=True, reverse=False) def select_allbelow(self, event=None, name=None): self._alter_list(select=True, reverse=True) def select_noneabove(self, event=None): self._alter_list(select=False, reverse=False) def select_nonebelow(self, event=None): self._alter_list(select=False, reverse=True) def _alter_list(self, select=True, reverse=False): all = list(self.GetStrings()) if reverse: all.reverse() this = self.GetStringSelection() slist = list(self.GetCheckedStrings()) for name in all: if select and name not in slist: slist.append(name) elif not select and name in slist: slist.remove(name) if name == this: break self.SetCheckedStrings(slist) wxutils-0.3.0/wxutils/floats.py000066400000000000000000000300061441045704400166140ustar00rootroot00000000000000#!/usr/bin/env python """ Code for floating point controls """ import sys from functools import partial import wx from wx.lib.agw import floatspin as fspin from .icons import get_icon HAS_NUMPY = False try: import numpy HAS_NUMPY = True except ImportError: pass def make_steps(prec=3, tmin=0, tmax=10, base=10, steps=(1, 2, 5)): """make a list of 'steps' to use for a numeric ComboBox returns a list of floats, such as [0.01, 0.02, 0.05, 0.10, 0.20, 0.50, 1.00, 2.00...] """ steplist = [] power = -prec step = tmin while True: decade = base**power for step in (j*decade for j in steps): if step > 0.99*tmin and step <= tmax and step not in steplist: steplist.append(step) if step >= tmax: break power += 1 return steplist def set_float(val, default=0): """ utility to set a floating value, useful for converting from strings """ out = None if not val in (None, ''): try: out = float(val) except ValueError: return None if HAS_NUMPY: if numpy.isnan(out): out = default else: if not(out > 0) and not(out<0) and not(out==0): out = default return out class FloatCtrl(wx.TextCtrl): """ Numerical Float Control:: wx.TextCtrl that allows only numerical input takes a precision argument and optional upper / lower bounds options: action callback on Enter (or lose focus) act_on_losefocus boolean (default False) to act on lose focus events action_kws keyword args for action """ def __init__(self, parent, value='', minval=None, maxval=None, precision=3, odd_only=False, even_only=False, bell_on_invalid = True, act_on_losefocus=False, gformat=False, action=None, action_kws=None, **kws): self.__digits = '0123456789.-e' self.__prec = precision self.odd_only = odd_only self.even_only = even_only if precision is None: self.__prec = 0 self.format = '%%.%if' % self.__prec self.gformat = gformat if gformat: self.__prec = max(8, self.__prec) self.format = '%%.%ig' % self.__prec self.is_valid = True self.__val = set_float(value) self.__max = set_float(maxval) self.__min = set_float(minval) self.__bound_val = None self.__mark = None self.__action = None self.fgcol_valid = "Black" self.bgcol_valid = "White" self.fgcol_invalid = "Red" self.bgcol_invalid = (254, 254, 80) self.bell_on_invalid = bell_on_invalid self.act_on_losefocus = act_on_losefocus # set up action if action_kws is None: action_kws = {} self.SetAction(action, **action_kws) this_sty = wx.TE_PROCESS_ENTER|wx.TE_RIGHT if 'style' in kws: this_sty = this_sty | kws['style'] kws['style'] = this_sty wx.TextCtrl.__init__(self, parent, wx.ID_ANY, **kws) self.__CheckValid(self.__val) self.SetValue(self.__val) self.Bind(wx.EVT_CHAR, self.OnChar) self.Bind(wx.EVT_TEXT, self.OnText) self.Bind(wx.EVT_SET_FOCUS, self.OnSetFocus) self.Bind(wx.EVT_KILL_FOCUS, self.OnKillFocus) self.__GetMark() def SetAction(self, action, **kws): "set callback action" if hasattr(action,'__call__'): self.__action = partial(action, **kws) def SetPrecision(self, prec=0, gformat=None): "set precision" self.__prec = prec if gformat is not None: self.gformat = gformat if self.gformat: self.__prec = max(8, self.__prec) self.format = '%%.%ig' % self.__prec else: self.format = '%%.%if' % prec def __GetMark(self): " keep track of cursor position within text" try: self.__mark = min(wx.TextCtrl.GetSelection(self)[0], len(wx.TextCtrl.GetValue(self).strip())) except: self.__mark = 0 def __SetMark(self, mark=None): "set mark for later" if mark is None: mark = self.__mark self.SetSelection(mark, mark) def SetValue(self, value=None, act=True): " main method to set value " if value is None: value = wx.TextCtrl.GetValue(self).strip() self.__CheckValid(value) self.__GetMark() value = set_float(value) if value is not None: wx.TextCtrl.SetValue(self, self.format % value) if self.is_valid and hasattr(self.__action, '__call__') and act: self.__action(value=self.__val) elif not self.is_valid and self.bell_on_invalid: wx.Bell() self.__SetMark() def OnKillFocus(self, event): "focus lost" self.__GetMark() if self.act_on_losefocus and hasattr(self.__action, '__call__'): self.__action(value=self.__val) event.Skip() def OnSetFocus(self, event): "focus gained - resume editing from last mark point" self.__SetMark() event.Skip() def OnChar(self, event): """ on Character event""" key = event.GetKeyCode() entry = wx.TextCtrl.GetValue(self).strip() pos = wx.TextCtrl.GetSelection(self)[0] # really, the order here is important: # 1. return sends to ValidateEntry if key == wx.WXK_RETURN: if not self.is_valid: wx.TextCtrl.SetValue(self, self.format % set_float(self.__bound_val)) else: self.SetValue(entry) return # 2. other non-text characters are passed without change if (key < wx.WXK_SPACE or key == wx.WXK_DELETE or key > 255): event.Skip() return # 3. check for multiple '.' and out of place '-' signs and ignore these # note that chr(key) will now work due to return at #2 ckey = chr(key) if ((ckey == '.' and (self.__prec == 0 or '.' in entry)) or (ckey != '-' and pos == 0 and entry.startswith('-'))): return if 'e' in entry: if ckey == 'e': return ind_e = entry.index('e') if (ckey == '-' and pos not in (0, ind_e+1)): return elif (ckey == '-' and '-' in entry): return # allow 'inf' and '-inf', but very restricted if ckey in ('i', 'n', 'f'): has_num = any([x in entry for x in self.__digits[:11]]) if ((ckey in entry) or has_num or entry not in ('', '-', '-i', 'i', '-n', 'n', '-f', 'f', '-in', 'in', '-if', 'if', '-nf', 'nf', '-inf', 'inf')): return elif (ckey != '-' and ckey in self.__digits and any([x in ('i', 'n', 'f') for x in entry])): return # 4. allow digits or +/-inf but not other characters if ckey in self.__digits or ckey in ('i', 'n', 'f'): event.Skip() def OnText(self, event=None): "text event" try: if event.GetString() != '': self.__CheckValid(event.GetString()) except: pass event.Skip() def GetValue(self): if self.__prec > 0: return set_float("%%.%if" % (self.__prec) % (self.__val)) else: return int(self.__val) def GetMin(self): "return min value" return self.__min def GetMax(self): "return max value" return self.__max def SetMin(self, val): "set min value" self.__min = set_float(val) def SetMax(self, val): "set max value" self.__max = set_float(val) def __CheckValid(self, value): "check for validity of value" val = self.__val self.is_valid = True try: val = set_float(value) if self.__min is not None and (val < self.__min): self.is_valid = False val = self.__min if self.__max is not None and (val > self.__max): self.is_valid = False val = self.__max if self.__prec == 0: if self.odd_only and (val % 2 == 0): self.is_valid = False val = val + 1 elif self.even_only and (val %2 == 1): self.is_valid = False val = val + 1 except: self.is_valid = False self.__bound_val = self.__val = val fgcol, bgcol = self.fgcol_valid, self.bgcol_valid if not self.is_valid: fgcol, bgcol = self.fgcol_invalid, self.bgcol_invalid self.SetForegroundColour(fgcol) self.SetBackgroundColour(bgcol) self.Refresh() def FloatSpin(parent, value=0, action=None, tooltip=None, size=(100, -1), digits=1, increment=1, use_gtk3=False, **kws): """FloatSpin with action and tooltip""" if value is None: value = 0 # need to work this out better for GTK3 - lots of small # differences with GTK2, but this one is the biggest headache. # SpinCtrlDouble is like FloatSpin, but with every option # having a slightly different name... if use_gtk3 and 'gtk3' in wx.PlatformInfo: maxval = kws.pop('max_val', None) minval = kws.pop('min_val', None) fmt = "%%%df" % digits fs = wx.SpinCtrlDouble(parent, -1, value=fmt % value, size=(size[0]+40, size[1]), inc=increment, **kws) fs.SetDigits(digits) if minval is not None: fs.SetMin(minval) if maxval is not None: fs.SetMax(maxval) if action is not None: fs.Bind(wx.EVT_SPINCTRLDOUBLE, action) else: fs = fspin.FloatSpin(parent, -1, size=size, value=value, digits=digits, increment=increment, **kws) if action is not None: fs.Bind(fspin.EVT_FLOATSPIN, action) if tooltip is not None: fs.SetToolTip(tooltip) return fs def FloatSpinWithPin(parent, value=0, pin_action=None, tooltip='select point from plot', **kws): """create a FloatSpin with Pin button with action""" fspin = FloatSpin(parent, value=value, **kws) bmbtn = wx.BitmapButton(parent, id=-1, bitmap=get_icon('pin'), size=(25, 25)) if pin_action is not None: parent.Bind(wx.EVT_BUTTON, pin_action, bmbtn) bmbtn.SetToolTip(tooltip) return fspin, bmbtn class NumericCombo(wx.ComboBox): """ Numeric Combo: ComboBox with numeric-only choices """ def __init__(self, parent, choices, precision=3, fmt=None, init=0, default_val=None, width=80): self.fmt = fmt if fmt is None: self.fmt = "%%.%if" % precision self.choices = choices schoices = [self.fmt % i for i in self.choices] wx.ComboBox.__init__(self, parent, -1, '', (-1, -1), (width, -1), schoices, wx.CB_DROPDOWN|wx.TE_PROCESS_ENTER) init = min(init, len(self.choices)) if default_val is not None: if default_val in schoices: self.SetStringSelection(default_val) else: self.add_choice(default_val, select=True) else: self.SetStringSelection(schoices[init]) self.Bind(wx.EVT_TEXT_ENTER, self.OnEnter) def OnEnter(self, event=None): self.add_choice(float(event.GetString())) def add_choice(self, val, select=True): if val not in self.choices: self.choices.append(val) self.choices.sort() self.choices.reverse() self.Clear() self.AppendItems([self.fmt % x for x in self.choices]) if select: self.SetSelection(self.choices.index(val)) wxutils-0.3.0/wxutils/gridpanel.py000066400000000000000000000051771441045704400173040ustar00rootroot00000000000000import wx from .text import SimpleText from .utils import pack, LEFT class RowPanel(wx.Panel): """ a simple row panel with horizontal sizer""" def __init__(self, parent, **kws): wx.Panel.__init__(self, parent, **kws) self.sizer = wx.BoxSizer(wx.HORIZONTAL) def Add(self, item, expand=0, style=LEFT, padding=2): self.sizer.Add(item, expand, style, padding) def AddText(self, label, expand=0, style=LEFT, padding=2, **kws): self.sizer.Add(SimpleText(self, label, **kws), expand, style, padding) def pack(self): pack(self, self.sizer) class GridPanel(wx.Panel): """A simple panel with a GridBagSizer""" def __init__(self, parent, nrows=10, ncols=10, pad=2, gap=5, itemstyle=wx.ALIGN_CENTER, **kws): wx.Panel.__init__(self, parent, **kws) self.sizer = wx.GridBagSizer(nrows, ncols) self.sizer.SetVGap(gap) self.sizer.SetHGap(gap) self.irow = 0 self.icol = 0 self.itemstyle = itemstyle self.pad=pad def Add(self, item, irow=None, icol=None, drow=1, dcol=1, style=None, newrow=False, pad=None, **kws): """add item with default values for col, row, and size""" if newrow: self.NewRow() if style is None: style = self.itemstyle if irow is None: irow = self.irow if pad is None: pad = self.pad if icol is None: icol = self.icol self.sizer.Add(item, (irow, icol), (drow, dcol), style, pad, **kws) self.icol = self.icol + dcol def AddMany(self, items, newrow=False, **kws): """add items""" if newrow: self.NewRow() for item in items: self.Add(item, **kws) def AddManyText(self, items, newrow=False, **kws): """add items""" if newrow: self.NewRow() for item in items: self.AddText(item, **kws) def NewRow(self): "advance row, set col # = 0" self.irow += 1 self.icol = 0 def AddText(self, label, newrow=False, dcol=1, style=None, **kws): """add a Simple StaticText item""" if style is None: style = LEFT self.Add(SimpleText(self, label, style=style, **kws), dcol=dcol, style=style, newrow=newrow) def pack(self): tsize = self.GetSize() msize = self.GetMinSize() self.SetSizer(self.sizer) self.sizer.Fit(self) nsize = (10*int(1.1*(max(msize[0], tsize[0])/10)), 10*int(1.1*(max(msize[1], tsize[1])/10.))) self.SetSize(nsize) wxutils-0.3.0/wxutils/icons.py000066400000000000000000000451231441045704400164450ustar00rootroot00000000000000import wx from wx.lib.embeddedimage import PyEmbeddedImage RAW_ICONS = { "nn": b"""iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAIAAABvFaqvAAAACXBIWXMAAAxMAAAMTAEAiU+qAAAACXZwQWcAAAAYAAAAGAB4TKWmAAAB3klEQVQ4y+2VMW7bQBBF/3CHkhrFCFwlKV2l9gF8Al8k6dy5IkEV7twld1GlE1gG3aRymVAuXBiGERq7Mz8FEUoiBQRIm/yKnP3zwJmd5UprVAGJPQlAmHGay6fP3wF8/fLhNTIE6Zb2vIJEIDrHcqcZSS6qBrgFbhdVQ9KMfsgfnYh2gJKSkyzLBliHUIdQA+uybEim5GNWtBHInTE6yaLoKHcitUgdwh2wLoqGZIxD1hDkzpQ4oAA1MGSltFfjHmhQkWrdU3qW6uEaozHrOk/CjCFIUWzKsglBzYZbScIMIWhZNkWxCUHM2HsyCEiIQFUWi4eq2kwmKoIsw1hZBhFMJlpVm8XiQVWkGx2BRGMQxMjLyx/X1w2gO3MSRijbmbR0cfHu6up9nosR6g5VLJfPq9XL2dlbd5AIAa+vvLn5GeO2vDyX09P5dCpm6D55tXpZLp/Pz9+4QaJRM7Stz2Z7xTw9+cnJt8fHJCIASB4f6/39x6OjPVuXmBzavc9mmfu2qSJoWx83u219Ps86Q5/YPehu8m5Tu74OzlQXd98u9YnamwaSceh3UOSA/9Am/5X+g/5pkEL+4BheMAclUCNk1y2gIw9wQFVCkDzPAMToqtIdx+SQbPuzEYERvwBwYMALE1tK0wAAACV0RVh0Y3JlYXRlLWRhdGUAMjAxMC0xMS0xMlQxMTozNDo0NS0wNjowMF4RciAAAAAldEVYdG1vZGlmeS1kYXRlADIwMTAtMTEtMTJUMTA6NDc6MzEtMDY6MDDgsF4bAAAAHXRFWHRTb2Z0d2FyZQBHUEwgR2hvc3RzY3JpcHQgOC43MQM/aDQAAAAASUVORK5CYII=""", "ss": b"""iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAIAAABvFaqvAAAACXBIWXMAAAxMAAAMTAEAiU+qAAAACXZwQWcAAAAYAAAAGAB4TKWmAAAB4UlEQVQ4y+2Vv3LTQBDGv72TYleuPDiBB6BheI40eRHo0qWQ5NhFunTwMnkHZ+yhoaEDAePKQ2HrbvejOFuOZTOhh280o9H++d3tanWStTITkNhKQEPu8XOpb998Xi5jnjsAIdhwmC0+vX4x9EEhDtiliCASmRd4AQSt6ADAATFSlaqWzDEmDzIH6aQArgX/SSIQeSYGhHsu5G/1H/RPg7J045OxJCEC8sSkJmPHk8Y1ax/M9iAzmHUTTtrdrqQtaL22fv+gzH7fdb4MEfT7zh02o03MzACHh4dfVfV9MPBpQe+x2XC1MkB268tqZZeXX3o9UYUInMNqpbe351dXAzNIUHpBCLy5+XZ/XwPZ/oCAP+qStvsD4vX1xd3dyzwXJSQY/a5h0+mPsqzPztK+qNrFeA8RcQ5No5PJRVGMUu80HSMiIBEji2JUludNE1Nfj5UKb5pYludFMYqR6RWDQFAmmTFGIzke18Asy+Yic2B/icyzbA7MxuOaZIxmts0Nyj1oxyLJqqqBmfeLliUy934BzKoqUdhSToASKwTrsDqUEOwp5TTouEbv596frugQdGRNLFWSnE5q4BF4nE5qkqq0U/HBKN3fUTslhCp7ubx7/xXAxw+vNoHeS3J1Jj4SvwHUp92N41HPcAAAACV0RVh0Y3JlYXRlLWRhdGUAMjAxMC0xMS0xMlQxMTozNDo0NS0wNjowMF4RciAAAAAldEVYdG1vZGlmeS1kYXRlADIwMTAtMTEtMTJUMTA6NDc6NDUtMDY6MDAeOnMRAAAAHXRFWHRTb2Z0d2FyZQBHUEwgR2hvc3RzY3JpcHQgOC43MQM/aDQAAAAASUVORK5CYII=""", "sw": b"""iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAIAAABvFaqvAAAACXBIWXMAAAxMAAAMTAEAiU+qAAAACXZwQWcAAAAYAAAAGAB4TKWmAAACWklEQVQ4y42VTU4bQRCFX1XbFmNbipULOAfhEKzsJXgBghslIMsjFthmiVhGiVAOwIyl7IOXUcCgBPwz0/WyGMd2sIEpjUat/vn6ve7qbpl4FgQkcoYIvAcA5+ajRJASBSdwAkhekBlKhXnB6bySgCK3FgBJQlWE4ajXu1dFknBJSjxzxmxmJHu9UbEYlcvxxcUDySQxkolnXlA2oN8fVauRSCwS12rx5eUDSTOmlg+00BIEERCJxKqxSFypxN3uiOQ0oeZZl2JRer37VutmPBZVJUFCBEli06nN+6VG8zkdRc4NgNi5gUhUrUbn56OltVlCkmn6tiPVARBn/yCIMlOzmdGYeOLr1Z9PH39lVWbPKd1uRolXKFEQRL3eaNFnvmufv/x27joMb0mmqeVx1O+PFn2WoKtvjypRqXTd6dxmk0yn+RytLmVmzblYNS4Wr9vt26zh7CyXo1VQAQCN6iRN9ehoWKno1pbs799MJnBOvKdzYmbVKtrteqNRS1MWixtOZiE70t5DRGYzabWGqnh8hKp6T1XxnkGAk5N6o1HLcmpjuhX+nTmhUUTG4+xmEDOKiJkFATqderP5GgXASmbLPF9FQEIVgJXLDMN6s/mio2eKlrG4q0hUq3J6+mFn593rWtYUrYUZJhNm9Dev0M2gbNjTE3d3f4ThXakkZm+QCtnSbmwT0TTl4eEQwN7e+zRh4WWD810TkXXxJNUhSfTgYM7yHqrYOK+KwAgaBRDw/w/0FAEprdbw+Phu8XKsiYf8vEu/DyYEXvAHAk5lOjXnZHu7ss7KnqO/bT27tA7euksAAAAldEVYdGNyZWF0ZS1kYXRlADIwMTAtMTEtMTJUMTE6MzQ6NDUtMDY6MDBeEXIgAAAAJXRFWHRtb2RpZnktZGF0ZQAyMDEwLTExLTEyVDEwOjQ3OjA5LTA2OjAwXdAXnwAAAB10RVh0U29mdHdhcmUAR1BMIEdob3N0c2NyaXB0IDguNzEDP2g0AAAAAElFTkSuQmCC""", "ne": b"""iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAIAAABvFaqvAAAACXBIWXMAAAxMAAAMTAEAiU+qAAAACXZwQWcAAAAYAAAAGAB4TKWmAAACX0lEQVQ4y42UMU/bQBzF3//ONjiJVJoPEDp0q9SvwoaQGGIGCJ8JBpMBJc6IGKtWqOzYjFWlCnYEqASSnO9eB4c0pknqJ8uy5Hc/v3d3PhlZegIS8xKBtbi8HFrLtTVlHQWLRUKAT5/XPS3QApSNziHw8PPHuNO59TwhhY6AACx/T0gS+HbxUZVflRIdHDTjuCVCEqKFACHlC6JECUiohYFFoDVywyhqHh21fN/RQWRBPxJFHQ/L5fliLaOoCaDTuTVGAOGCBgSWJJpJKZlMGEXNbvdDrSZF2MXO1SCR6cj1dVErvf8B5Tl9XwaDh93dX09PFAH5l14VZAw9T5LkIYpunp8FUM6hYBW4+RVXKyi+L/3+w97ezcuLKKVIKiUk63WEIUiKKoLJUtCs0f7+zWgErcU5ai2kq9dxeroZx60gIEmtp6vmLcuSJEUWKKWspVJiLcMQx8ebW1vvAAyHrcPDW2vVNJexnNdk4kj2evdhmAKZUtev9zQM037/nuR47ApbHN8FwZWS9OL7sAQyxpFMkvtGIxVJtb4GMq2vRdJGI02S+5mHZJ47kicnd1pfffn626veaHt7o/DM9qoxbLeb4xG1FhhLukqNCs8b5TlJTgyROzpHkoNBpUb/ylnmjhgbkux274LgSqlMJJslCsO011uaZV7GcrqPgkD5vpptf+dcGDKON3d2SvOy8m96rXZ+/rixkYlkIlmVRm8STZe/GHB29lirZb6/anaXgcRYeqq0/KMR2+33VRtNa0GMo/fqdw7FoTN7qAoiPEvI9OiFCCY5AGiN3FWliMASfwDgYH5MXi5Z8wAAACV0RVh0Y3JlYXRlLWRhdGUAMjAxMC0xMS0xMlQxMTozNDo0NS0wNjowMF4RciAAAAAldEVYdG1vZGlmeS1kYXRlADIwMTAtMTEtMTJUMTA6NDY6NTYtMDY6MDAMugIsAAAAHXRFWHRTb2Z0d2FyZQBHUEwgR2hvc3RzY3JpcHQgOC43MQM/aDQAAAAASUVORK5CYII=""", "ww": b"""iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAIAAABvFaqvAAAACXBIWXMAAAxMAAAMTAEAiU+qAAAACXZwQWcAAAAYAAAAGAB4TKWmAAAB3UlEQVQ4y6VVO27bUBCc/YiFgcBllDZXsK+U3gewxI9dJQjgs9iNG3eukhOkD0DGCAzIQGBZfLubgpIiyw7JIFM+koOZnTdLWlooIQJ7IMAczBgEEVJAhSAE0LNnHa2MYNm+z3ihJQIIWIqrq4fl0tcng0ytxS7co209Ik5Ovh8ff3MPs3CPfrQWvKfFHap0dvbj4qI+OBCiIS0b8HOWEEFZNvN5zaxjOQAQdMtiFqqU501V1Vmmq1Vy34xscEQBBf1xVBRNVdUi2n3c+RIZJpoQFIFAiFBVNWVZq4rZWkPbxmLhT09OBOqdlgXoaeXZhIqiKctaRN1j62UyocNDjkAEengikGVEEXF+3sxm+yw7N2QYqiR3Pz98+ninKu6vFWUcsmxMl8ZBvn75LIKbmwd5PZ5R1gDoqo3T02lK+MuwZeSwlYXcoyimzMjzLn4ww8yOjt5cX78fGb+CQCAzzOdT97UuZpit49+tUZ81BIjBjJSiKKYR6C43NuUwAxH625ti0zUiiJBZlOWUGUXRMFMXacfST0S7sonATGbI82lVvXNP/xD+VtEOF1KK2ezt/X26vf01pvpr7G3Ibkl2e/LycvH4aN3J4Iak1kPppdK185FIAbUA4X9/Rxb4DQ6meRDp8UWOAAAAJXRFWHRjcmVhdGUtZGF0ZQAyMDEwLTExLTEyVDExOjM0OjQ1LTA2OjAwXhFyIAAAACV0RVh0bW9kaWZ5LWRhdGUAMjAxMC0xMS0xMlQxMDo0Nzo0MS0wNjowMOp1VwIAAAAddEVYdFNvZnR3YXJlAEdQTCBHaG9zdHNjcmlwdCA4LjcxAz9oNAAAAABJRU5ErkJggg==""", "ee": b"""iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAIAAABvFaqvAAAACXBIWXMAAAxMAAAMTAEAiU+qAAAACXZwQWcAAAAYAAAAGAB4TKWmAAABxklEQVQ4y61VS04bQRSs1/0MiDUoM/fgEBwlyooDWPOxL8BZ4BJsoyiLbLKKZEhWThaYmX6vshgDNgZPR6RWLc1Tqaq6+o2sjCogMQp3xIDdQREkQqMgCiDjRHhk2Z3l88e3MYhdrfz6+rclgq/JJ9Ab98OdZnTn2dm3i4sfJPve3bdmemPIsLQO4vg4Xl4u5vM7VXF/qStkpfM0HbSqFm17GyPcucml5EhKfAzFHe48ONCmWbijbYuUGKOIAIBOxsyJbB1IxKiz2UIETVOYIQRAoHe/LO51R5LE4WHoewJwpztUY9suQkBVFe4ERIria9dRZA8RRCCC5dIHrkFdCGKW6rpsmqLrKaqfU8roNfCiiU9c83k5nRZydPTl4cFzaHZ7KIIYYeYfP53m9mgUmtJ2H/7RWkpra3pyou8Mezotup5y+zNlXv/5+febmz8xRnfEiJSsbcvh+h2iH05jZgqTiQAIQQCklOq6rKqhkAJAe4eOKBravL41EZilqiqbZuOJOHTwn4MQEIJ0nTVNWdeF2fNDQ85i24R7ms0GFoQgWwr+12LLIiJ5f29XV8uB4gXLQCS9U7N321vLPxFqhOC9vyMj/gJsaMELYgNmYQAAACV0RVh0Y3JlYXRlLWRhdGUAMjAxMC0xMS0xMlQxMTozNDo0NS0wNjowMF4RciAAAAAldEVYdG1vZGlmeS1kYXRlADIwMTAtMTEtMTJUMTA6NDc6MzYtMDY6MDAlF2CVAAAAHXRFWHRTb2Z0d2FyZQBHUEwgR2hvc3RzY3JpcHQgOC43MQM/aDQAAAAASUVORK5CYII=""", "camera": b"""iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAKO2lDQ1BpY2MAAHjanVN3WJP3Fj7f92UPVkLY8LGXbIEAIiOsCMgQWaIQkgBhhBASQMWFiApWFBURnEhVxILVCkidiOKgKLhnQYqIWotVXDjuH9yntX167+3t+9f7vOec5/zOec8PgBESJpHmomoAOVKFPDrYH49PSMTJvYACFUjgBCAQ5svCZwXFAADwA3l4fnSwP/wBr28AAgBw1S4kEsfh/4O6UCZXACCRAOAiEucLAZBSAMguVMgUAMgYALBTs2QKAJQAAGx5fEIiAKoNAOz0ST4FANipk9wXANiiHKkIAI0BAJkoRyQCQLsAYFWBUiwCwMIAoKxAIi4EwK4BgFm2MkcCgL0FAHaOWJAPQGAAgJlCLMwAIDgCAEMeE80DIEwDoDDSv+CpX3CFuEgBAMDLlc2XS9IzFLiV0Bp38vDg4iHiwmyxQmEXKRBmCeQinJebIxNI5wNMzgwAABr50cH+OD+Q5+bk4eZm52zv9MWi/mvwbyI+IfHf/ryMAgQAEE7P79pf5eXWA3DHAbB1v2upWwDaVgBo3/ldM9sJoFoK0Hr5i3k4/EAenqFQyDwdHAoLC+0lYqG9MOOLPv8z4W/gi372/EAe/tt68ABxmkCZrcCjg/1xYW52rlKO58sEQjFu9+cj/seFf/2OKdHiNLFcLBWK8ViJuFAiTcd5uVKRRCHJleIS6X8y8R+W/QmTdw0ArIZPwE62B7XLbMB+7gECiw5Y0nYAQH7zLYwaC5EAEGc0Mnn3AACTv/mPQCsBAM2XpOMAALzoGFyolBdMxggAAESggSqwQQcMwRSswA6cwR28wBcCYQZEQAwkwDwQQgbkgBwKoRiWQRlUwDrYBLWwAxqgEZrhELTBMTgN5+ASXIHrcBcGYBiewhi8hgkEQcgIE2EhOogRYo7YIs4IF5mOBCJhSDSSgKQg6YgUUSLFyHKkAqlCapFdSCPyLXIUOY1cQPqQ28ggMor8irxHMZSBslED1AJ1QLmoHxqKxqBz0XQ0D12AlqJr0Rq0Hj2AtqKn0UvodXQAfYqOY4DRMQ5mjNlhXIyHRWCJWBomxxZj5Vg1Vo81Yx1YN3YVG8CeYe8IJAKLgBPsCF6EEMJsgpCQR1hMWEOoJewjtBK6CFcJg4Qxwicik6hPtCV6EvnEeGI6sZBYRqwm7iEeIZ4lXicOE1+TSCQOyZLkTgohJZAySQtJa0jbSC2kU6Q+0hBpnEwm65Btyd7kCLKArCCXkbeQD5BPkvvJw+S3FDrFiOJMCaIkUqSUEko1ZT/lBKWfMkKZoKpRzame1AiqiDqfWkltoHZQL1OHqRM0dZolzZsWQ8ukLaPV0JppZ2n3aC/pdLoJ3YMeRZfQl9Jr6Afp5+mD9HcMDYYNg8dIYigZaxl7GacYtxkvmUymBdOXmchUMNcyG5lnmA+Yb1VYKvYqfBWRyhKVOpVWlX6V56pUVXNVP9V5qgtUq1UPq15WfaZGVbNQ46kJ1Bar1akdVbupNq7OUndSj1DPUV+jvl/9gvpjDbKGhUaghkijVGO3xhmNIRbGMmXxWELWclYD6yxrmE1iW7L57Ex2Bfsbdi97TFNDc6pmrGaRZp3mcc0BDsax4PA52ZxKziHODc57LQMtPy2x1mqtZq1+rTfaetq+2mLtcu0W7eva73VwnUCdLJ31Om0693UJuja6UbqFutt1z+o+02PreekJ9cr1Dund0Uf1bfSj9Rfq79bv0R83MDQINpAZbDE4Y/DMkGPoa5hpuNHwhOGoEctoupHEaKPRSaMnuCbuh2fjNXgXPmasbxxirDTeZdxrPGFiaTLbpMSkxeS+Kc2Ua5pmutG003TMzMgs3KzYrMnsjjnVnGueYb7ZvNv8jYWlRZzFSos2i8eW2pZ8ywWWTZb3rJhWPlZ5VvVW16xJ1lzrLOtt1ldsUBtXmwybOpvLtqitm63Edptt3xTiFI8p0in1U27aMez87ArsmuwG7Tn2YfYl9m32zx3MHBId1jt0O3xydHXMdmxwvOuk4TTDqcSpw+lXZxtnoXOd8zUXpkuQyxKXdpcXU22niqdun3rLleUa7rrStdP1o5u7m9yt2W3U3cw9xX2r+00umxvJXcM970H08PdY4nHM452nm6fC85DnL152Xlle+70eT7OcJp7WMG3I28Rb4L3Le2A6Pj1l+s7pAz7GPgKfep+Hvqa+It89viN+1n6Zfgf8nvs7+sv9j/i/4XnyFvFOBWABwQHlAb2BGoGzA2sDHwSZBKUHNQWNBbsGLww+FUIMCQ1ZH3KTb8AX8hv5YzPcZyya0RXKCJ0VWhv6MMwmTB7WEY6GzwjfEH5vpvlM6cy2CIjgR2yIuB9pGZkX+X0UKSoyqi7qUbRTdHF09yzWrORZ+2e9jvGPqYy5O9tqtnJ2Z6xqbFJsY+ybuIC4qriBeIf4RfGXEnQTJAntieTE2MQ9ieNzAudsmjOc5JpUlnRjruXcorkX5unOy553PFk1WZB8OIWYEpeyP+WDIEJQLxhP5aduTR0T8oSbhU9FvqKNolGxt7hKPJLmnVaV9jjdO31D+miGT0Z1xjMJT1IreZEZkrkj801WRNberM/ZcdktOZSclJyjUg1plrQr1zC3KLdPZisrkw3keeZtyhuTh8r35CP5c/PbFWyFTNGjtFKuUA4WTC+oK3hbGFt4uEi9SFrUM99m/ur5IwuCFny9kLBQuLCz2Lh4WfHgIr9FuxYji1MXdy4xXVK6ZHhp8NJ9y2jLspb9UOJYUlXyannc8o5Sg9KlpUMrglc0lamUycturvRauWMVYZVkVe9ql9VbVn8qF5VfrHCsqK74sEa45uJXTl/VfPV5bdra3kq3yu3rSOuk626s91m/r0q9akHV0IbwDa0b8Y3lG19tSt50oXpq9Y7NtM3KzQM1YTXtW8y2rNvyoTaj9nqdf13LVv2tq7e+2Sba1r/dd3vzDoMdFTve75TsvLUreFdrvUV99W7S7oLdjxpiG7q/5n7duEd3T8Wej3ulewf2Re/ranRvbNyvv7+yCW1SNo0eSDpw5ZuAb9qb7Zp3tXBaKg7CQeXBJ9+mfHvjUOihzsPcw83fmX+39QjrSHkr0jq/dawto22gPaG97+iMo50dXh1Hvrf/fu8x42N1xzWPV56gnSg98fnkgpPjp2Snnp1OPz3Umdx590z8mWtdUV29Z0PPnj8XdO5Mt1/3yfPe549d8Lxw9CL3Ytslt0utPa49R35w/eFIr1tv62X3y+1XPK509E3rO9Hv03/6asDVc9f41y5dn3m978bsG7duJt0cuCW69fh29u0XdwruTNxdeo94r/y+2v3qB/oP6n+0/rFlwG3g+GDAYM/DWQ/vDgmHnv6U/9OH4dJHzEfVI0YjjY+dHx8bDRq98mTOk+GnsqcTz8p+Vv9563Or59/94vtLz1j82PAL+YvPv655qfNy76uprzrHI8cfvM55PfGm/K3O233vuO+638e9H5ko/ED+UPPR+mPHp9BP9z7nfP78L/eE8/sfx6nbAAAABmJLR0QAAAAAAAD5Q7t/AAAACXBIWXMAAA3WAAAN1gGQb3mcAAAACXZwQWcAAAAYAAAAGAB4TKWmAAAF+0lEQVRIx6WW2W9V1xXGf3s459zJXJ9z7zUm8cBkI2gwlWhInESxUojLC0lppSRSX1LEC/9BpfLUZ176B/QtVFWlJmqQoqRpQK2UQSDFYEqN7diGa65HsH3ne8bdBxvkVqqqKkta0p60115a3/etLdhlBw8e3D1lfn6e72v66cCybQYHB7l+/fqzzVQ2RyqToV5voJREKwVSEkcxSRIjlUJJhaUVSmukUmilsG2bxfm57UuMMZw/f56Jb79FCJl+99333Pff/6Vb8AppQALq/3VjDG+9/TbGGMTJkyfxPK+n2Wy+eHjo8Fv5fP6H6XRaWtq6ffvO5L2NrU3d07NXGmMIggBtWTi2DQJMsr3m+x2CICQMQ3zfp9NuLfWUSjdWV1Yq+ty5c6Vms3k5lUr9YvSVUU8rRSqdIpvJ/mhwcD/l8kOKpRJbm1vkunLkcjnq9TpaKcIwRGnNvt5enFSKIAgIgoBKpRJWt7Z+HwTBr3S9Xu87evTouOsVvI2NTQ4e2E8YRty/P43rubiua1ZWV0WxVDJDQ0PEcSQ6nY6xbRuTJEJrbdKZLJalhRDCCCHo7x+w5r6bHZ2YmHhOZ9Jp0ep0+OqvX9BpNCgVXFOr1+l0fHFk+IjxPJd0JkOlUhFfXb1qhg8PcfyFY0Jry9QbDTZW18TDctncnrxL2/dFu9kwRc9l/PQZk9+zBy2FQGuNHwQ82dyksrQkjDHmyOFD7C16Ip1OGyeVoiuT5ienf8zZs2fptNtMTEzwaLGMMTB86BAFz+Prm7eYXlsj39WFZWmEEGhjDLZlIYSgWm/QbjWJwpD7s4a19XWCICSf72L8zBmGh4aY+ucUd+9OYtk2GMPCwgKNRp2RkRFKhSJ/6Pjsye/Btm2SON7mge04SCkRQiClRCnFVq3GVrUGwIkTJ3hldJR7U1PMzMxy+o03OHJkmCDwmbx7h08++ZSF+XmGhoc58cIxypVltN6m2L9lEIQhrVaLKIrQWqOUws3nOf6DY9TqdRqtNu+98w6plMPiYplarUY2k+Pll1/ixo2/EUQxBbebzWoNpTXmaQClNUXPY+zVUYqeh5KSWr3O9MwszWaTgueyvLaO53oIAV98/hcePJgnNtDT00v/QD/7nttHtVrF87xtZkuFAHQURWAMPUWPx6urVDeekMvl6Nu3j1p1i/mFJkopWo0mvaUif/rwQ37z249IMn2weYe81eDy5V/T19fHo8oSxYLHzNwcSZJgMGjbsWm2WkxM3mN2Zpo4DEniCD8IEEKScmwWHz2iy/WQUjA9M0cz1Y/ODxFvzpFsVlhaWubFU6dYf7LJwoMF6o0mURwhEEgMhEGItjS2k8IIQWzAdtLYqRR+EHL7zuSzOjmOjVr5EjP1AaJ2n1TKIpvL4bku3d3dfDc3TxhFRGG0o6ZC4Ac+SZJso2gHSYhtMVRa8+U337C3t5dTp04xOvoSWgmiMERKiesVGHt9DKUVy8vLzD6oMLB/kCiKQIAWQKfTMXEcb6vrf+i5UoqOH/DpZ5/RaLU4++abDPy8n2ajgTGGbDaL63bzx48+5tbXf+f04Bp2qUjLj5BCoOM4klEY6h0eGIzBGPMsljEGIYRZf7LBnz++xvLKqhl77VX2lkooJc296Rl+98FVbt6aMKVsxGsntmjsCSknyDiJpL77j3t6/4EDnb6eEhlLi8D3EUJgWRZaawyGMAx3mkxC2Glz89YtCoUCxhjW19Zp1euMHBvGsS0+bzg83z2I39ioPaqsdMTzz/f1jIwcfz2fz48kcSyklEZphW3ZQkpJFEdWo97IttvtTBwnlhAIQERRtJ2dlGYn49jS2s9kMg3Hcdod37/58OHD6wLgwoULDAwMYOIYpRTatnBsh0OHDvLT8z+TgAsUgMxO1xI7bnY8AUKgCjwxxrQuXbrEtWvXEMVikcePH//Xpu04jgiCwDbGOIC9E0DuOpLseAQEUkq/q6srrlarZmxs7CkY4eLFiywuLj4tMEIIpqamKJfL7Hrx0/O7M9gNvgQwIyMjjI+Pc+XKle/9K/mf9i9tr8IAhgsCkQAAACV0RVh0Y3JlYXRlLWRhdGUAMjAxMC0xMS0xMlQxMTozNDo0NS0wNjowMF4RciAAAAAldEVYdG1vZGlmeS1kYXRlADIwMTAtMTEtMTFUMjI6NDM6MDYtMDY6MDCXhuNuAAAAAElFTkSuQmCC""", "se": b"""iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAIAAABvFaqvAAAACXBIWXMAAAxMAAAMTAEAiU+qAAAACXZwQWcAAAAYAAAAGAB4TKWmAAACaUlEQVQ4y5WVwW4TSRCG/796xsGOD34DhDgi8TjADRkEgjcCJMRcgiccEcfdZaPlnpk8wTrnTRwExo57un4O4xgTQuwtjUatUc1X9Vd3VXOelBESAJBICQBCWH7Zxkg0QhaIQIAA4I5OtlwE2xYEQIDhInKMMkNZnhXFxAwxbp1SS4pJkmJ0Se/ff+716jyvynIiabFwbWcxCY3LXZI+fPg8GNRkTdb9frW/P1kF2Ap0HiVpNJrs7tZkbVaTNVB1u/8jr5i0LOli4TE6CQkSzGw243A4LsuzPOdW9VpJe/du0u9XZBXCEVCHcERW22j0pMaFmCRf5j8aTbrdCqjNji7eGzQ2jSQtopa7tvIry5ZVrbHqbrcajS6z/CL8yxf/fTz4+gO0yn9/fyuNTeOSiuIkhMM//vryE2gV83qN5+feur15c9LpHBqrg3+ml0EbNb59O2ndXr8+yfNDszqE+uPB1+zXfcxzNo3u3x+QePx4PJ16CJaSQuB8ridPxr0e53M9f37cNBYCPAkAY1J2VX/GqDxnWZ49ejSezWhGd5nR3Xd36Y7ZTGYmicCff9/Ofne+2nP44MGAxHA4ns1EmrtITqft9KBcYNuyuG5YrDQWxc1eT4CbQQKJtgfa4dPahqmTZYxR9+4N9vZu9ftsKW0bXbINoNU/87ncr/PcAHJXp8OiOH348N9v39Sir879GkoTleUsitNnz46bxkhK4lpd2jXB34IkuKOlPH16nJLRluflkpHLJG298uugEPDq1elweCyRhJIIEPr5gVze7uP6dbQeJyV8+jRNSTs7llzE1SaBwJ27N74DejPTVruTNQsAAAAldEVYdGNyZWF0ZS1kYXRlADIwMTAtMTEtMTJUMTE6MzQ6NDUtMDY6MDBeEXIgAAAAJXRFWHRtb2RpZnktZGF0ZQAyMDEwLTExLTEyVDEwOjQ2OjUyLTA2OjAw+PUmPwAAAB10RVh0U29mdHdhcmUAR1BMIEdob3N0c2NyaXB0IDguNzEDP2g0AAAAAElFTkSuQmCC""", "nw": b"""iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAIAAABvFaqvAAAACXBIWXMAAAxMAAAMTAEAiU+qAAAACXZwQWcAAAAYAAAAGAB4TKWmAAACVUlEQVQ4y5WUvU4bQRSFz53ZMV7biiwewLwIRUJPjSUau+CdAIFMA/4pEWUUhPIA7FpJqkiR2yjEoDj4Z2bnpFhjjP9zitVq5u53zxntXPn5231tDwiIYKEIaCXDoddadnfzWoN8UyACRwTfvgz2PnwXQEQ4UwIBKEpE6BxPTkp77/PeQ6sFzQISSgAREsSsK6WFHkqxVitVKttJAjVHSUnpssxaGXsWehjjT09Llcq2s9R66QkEAEimXqZxIiC9MTw+Tr0wMEsYE9AiLwCQy8nZ2U65XByNaFZSAKhVewrZrKRcWcOZA6XfkBBBr8fDwx+t1qMx4hw3BnGMSCneA1DPz1KpdJrNxyAQa7kWJABECckwRD4PkkoJSaVUvy/VaqfReDRmFSsFUWuQzGRYq5UuL3fyeZBea/GeWstggKOjzpqMt3c9rWOlYmPuz88fSJK8uuqGYQTESrVfnlEYRo1Gl+Ro5PlWNiHuPv9VEmUy97XaQ1o0HHqSjUbKiqZYcRhG9foClk2Ij5/+aH1/cfFA0rnxtrWeZLPZLRQikUjrNhBr3RaJCoWo2exOal5Bt3e9k+NfaRM/1SbtWa9vlNEmxMiSpHOcV1q3PqOnTQjn6RMu0+qMrVaXpPd0nrDLKSsyisRKxZnM+HCHlgHWKf0Py+WiCKrVTr9PEUnvgDFqa+vlbqx1tChjLBIXi/HNzdN/RJs/e2OiXC6+vn6aNLDJBtFmMh4cFAcDZrOyv//O2tc5JdYzWDdrpuX9eGxPXgA4IkgIAchNQSIYOQDQGs6PVxLiH5XrpvEOYi2tAAAAJXRFWHRjcmVhdGUtZGF0ZQAyMDEwLTExLTEyVDExOjM0OjQ1LTA2OjAwXhFyIAAAACV0RVh0bW9kaWZ5LWRhdGUAMjAxMC0xMS0xMlQxMDo0NzowMS0wNjowMG4/WfgAAAAddEVYdFNvZnR3YXJlAEdQTCBHaG9zdHNjcmlwdCA4LjcxAz9oNAAAAABJRU5ErkJggg==""", "cross": b"iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAABHNCSVQICAgIfAhkiAAAADtJREFUGJVjYGBg+M9AGPxnRFPIiK4AXQKbYhQxZBNwOYERr1XoTmHCYQp2Y0mxmijPEBU8THgUoYgBABqMDQrxNogwAAAAAElFTkSuQmCC", "plus": b"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAFlJREFUOI3FUkEOwCAMAuP/v4wXu4PWrkuXyKWHUgIocBsMdspwW6QuCdKq80Egg7JAnzP2eebQitFb1v2SAMByhMeBtzRX5PG1aR14jPo/yOC3DjykItzHAE3QExOIgOhpAAAAAElFTkSuQmCC", "redo": b"iVBORw0KGgoAAAANSUhEUgAAABAAAAAPCAYAAADtc08vAAAABHNCSVQICAgIfAhkiAAAAgFJREFUKJGdks9Lk3Ecx1/P8+zR55nP89VoMa1EMtYSpDCIaJfEQ108dZSO28VLCJPA/yBYjaBE2MBD4Ml+SNKhgxGpRaSRusPyUjMnC9fapm3+mN8O0nLNjHof358378/780NhD0Yn5+XdsSmW0tky13XWQ+T6VYU/oFwIRsblo1cx6o2aKlG2uMkFbzMjN3oqjIKRcakAhB+/lPefvcVlqABslFTyW9vYuoNabaeCmx3sU/Y2VAA6+4ekS5PomkY8s05Xh4f2FjcLn1JMvFukVRjomka+uEOyUOB0i5vlZJr81jYOgNx6AU+TYK1YoqvDQyjQXTlzb1h6D9XR1KBjGyqbmSynXE5mVnKoALbuwDKcWIbGsSP1VTuYHexT4pl1ABobbJoPN2AZToDdBABC2AjLZGwqtu+2RZ2JZTgRwi5ztr66a3C+/QRrxQJHbYGrpvpi53rD8lJrI8I0q2oqQCjQrcwk0gjL5EpHG539Q/KnoOfmiARYTK2S+JYmVyggLBNhmSg1+q8/CNx5KIVawuc9ydfcGsOvPzAd8lfEGZ2cl/GlFLHEF5aTaT7nv1MhuDwQldcutnHc7WIlleHei4Uqk7/CF4zKBxNzci7+UT55/l76glF5kF79nZgO+ZVbT9+wmt3gjLcZn7fxwIaO/cjpkF+5bQ1LYdb+U/r/wg/0p7W1+p/g5QAAAABJRU5ErkJggg==", "info": b"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACzElEQVR42m2TW0gUURjHv3Nmb+6ut028lOaldm0RL2gGxWamSWHWi4JhPRjokyhCYWA9lfRQD0FUBJbdCBEEUzFILFjxQaxQWi+prXfXXV11J2zXXXdmOjPO7no78zAcvu//+/7nO+dDcMD6Ob14OiwisnDItpk0aXOi5XWXecm68qW5Irtvby7aualq/q49k6FrlEulOQ4Pi4ALxLa8DFjsG/0zc9bKDzezhvcByt8PntRr Y7vlCmk4QmhXkGNZAIyBI0CXy7MxNW8vaizVG/05RQ9aNOmGHJNCIT3Mi3m9T3xcjSAvXgXG eSeM0SyBcOBxe1cnRn6ntVTlWoS86s6ZxwqV/DYWxXjHwc7HKcFwVA0dEzQMLbuBJS5YAnG7 PK+eXo6vRNk36qisa1VWhHEEFsU+F5gkRisR8O1Y2eSE6tsAQmZZ5/i39ghUUN+UEpmZM8xX x0QtAMiXGEJBoS4MUmKCYWCOhre/1gMAlhNcOBfMBpR3911+cPKpHgoToQBAEKmkIDZUDqlR QXBBdwg6Ru3QOrImNFGA8ADSV2ZtsQQZ6prOKpIye4XqGPntKyQYGku0oJRJoLb9D8zT7j0A sqGtV1FaeUOUKq1gCRM1Eo/BX8FFXTjcyk+EcdsG1LRNCkIQ7YsQjlkwnRD6ndlgHGARzg44 QPC8VA/pcWHw5OsU2Bwu6Jv+Kwh9DhDHTgzeO5csAPR1n4pZiboViQCVnALjHQNQ5PG0/ViA 7jE79E85eP/bEAKgwFsx+vDSa/+NJ9R2fuQQVcY3QCbF8Kg0FWjXFrzpnYXZVacoBuGPgOly mrquLPe85PwATX61TKbNbeIAXxeeMto1JiCqSRO87cySqWyl475z3zDxS51bU0yFHqmnNAkZ /MX65Mz6rHlz9PMzxm5+4V2b2zpwGgMziiQoSBODQ6KPEa2EpS0WzuWwkMg/fjB3pv4HvQJH bUDKnS4AAAAASUVORK5CYII=", "target": b"iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAG7AAABuwBHnU4NQAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAI8SURBVEiJ5VVNaxNRFD33jf3yAzctLtwLKsaVdFWUbp24qvWjJZnZuBETkJm0aoaOtGrIDIWErlwkraRQoiubbqWCK12IEQv+ARHajYuStCHvujBJG51MJwU3ejbz3p13z7nn8ngX+K/ARjjFRjjVTc6RrhSIpxqr6aApoiuBQ4AOOhB5NH9aETIGcDhXfXsWAPT+yxsgrAohszl76ptfvq8DPemMClH/xCT7GBRtxqWUOjEGZF2UtaRz5VAONCs90q+c+FqtV04tzt7/DABsqgwA5JQIACK2EzqK4993a9tnck+Md4Ed6A+eDgFU3MH2YJO8UU4WhGxz+8I2y5X6jyEp+OUd2x0MLICenjgBxbxtbLTZTZfilC7F98eWZqe/MPBqV3IssAAzX2NQoS0GEBtXb7Kp3uDfWiuAAiTCXlytg5xQbTBm/lTDArmle2yEb4N4uVHBLXLXVthUFwDc9WB9TOmS3dFBGwTkry/vVU2isSZ5YL4XNCtd1iz3UruRzi3SLWdYSzofvbg8nwoieg3mSQAfWjGA4a6teJ2XwCQJrHr9825RrZZhYFy33XNtLhJqhhNqZn8saqXOEzDWKygLD3gK5J893AR4vA/HtjRr/sKeAmJgtK5jxHZCA8rJTSHp+nPb2PLi8n2L9KQzyoQiiAtgsZyvrr8HgGjvyLAixAQDE2CMLc6Z6504fG9Rfs58I6VykVjsEHiplSREngkVociQH3nXYFPl5nsUFH99HnQ70QJPsn8HPwGaGNNZ8MsV4wAAAABJRU5ErkJggg==", "pin": b"iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAG7AAABuwBHnU4NQAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAKGSURBVEiJ1ZXPS1RRFIC/648ZnVHG9yatxCyNhMIoyqj+hCBrhNq0cRNtsqiw2kUQ2EKwbGMt2rTrh2gEBe4KCSrToCTDTELDLObdMXXQ0Xmnhc44Ph/juBE8u3fvOd93uJd3Lmz0UOk2RcSrtT4tInVKqf2AsbillVJ9ItJhGMYzpdTsmgXhcPiUUqoZ2LFKk8MicjUYDLZnJBCRbMuyWpRSF1cBO+vumqbZqJSKp65nORO11q2ZwL3dHeR33FvqVKlLlmW1OPOWCbTWIeB8Jh17errI63pEblM9iCQkFyzLqnUVDA8P54nInUzgEp1kfCbOnC0UjPTjuXUGxIaFI28VEe8KQSAQCLH6hTI30Ev4ynGmPr5mdCLKvG3jHxvE97g5kVJhWdaJxEdOSm3IDaii//C8e4m9qYzIwCei7W1IfB6AWNxmvKKGEr+X+e17lmqUCgFPnYKDboKC+43kDPYCMBuNMb0IB8guKSOvoZmpgoCzLMlKveQtboLp+pvYwVIAgj4PJf6F41WePALX28haCQcodRO4/nR2sJTJhlZmfEUAGPkeiv1eCs/eIKe8yq1kWSQFSqlfbgkiQvffGE8On2PGvzApzHwPhUYwHTfJSgpEpM8N/vbzN76PjLG5+gCxaw+xi4oByP7zM52gd4UA6HRm9XwdYmj0N7u2beXQ7p3ES8qZvPyA+cq9zB6tdaanNpZkqZTF3Egk8kVEqgAmpqI8f/OeqvJSjlSvftYp0W8Yxr7ETFp2sVrrkIh0JAQiQlGhfy1wAU6apvkisbBsFhmG0QncBggU+NYKB2hKhcM6jOv1f3AcXXm11nXAMaAGqFzc+iEiH4BXpml2pnsyN378B2mABOAjSpYvAAAAAElFTkSuQmCC", } # recipe for converting png file to icon: # import base64 # f = open('my.png', 'rb') # print(base64.b64encode(f.read())) aliases = dict(ww='leftarrow', ee='rightarrow', nn='uparrow', ss='downarrow') for old, new in aliases.items(): RAW_ICONS[new] = RAW_ICONS[old] def get_icon(name): if name in RAW_ICONS: return wx.Bitmap(PyEmbeddedImage(RAW_ICONS[name]).GetImage()) wxutils-0.3.0/wxutils/listbox.py000066400000000000000000000041541441045704400170150ustar00rootroot00000000000000import wx class EditableListBox(wx.ListBox): """ A ListBox with pop-up menu to arrange order of items and remove items from list supply select_action for EVT_LISTBOX selection action """ def __init__(self, parent, select_action, right_click=True, remove_action=None, color=(10, 10, 20), bgcolor=(240, 240, 230), **kws): wx.ListBox.__init__(self, parent, **kws) if isinstance(bgcolor, (tuple, list)): bgcolor = wx.Colour(bgcolor) if isinstance(color, (tuple, list)): color = wx.Colour(color) self.SetBackgroundColour(bgcolor) self.SetOwnBackgroundColour(bgcolor) self.SetForegroundColour(color) self.SetOwnForegroundColour(color) self.Bind(wx.EVT_LISTBOX, select_action) self.remove_action = remove_action if right_click: self.Bind(wx.EVT_RIGHT_DOWN, self.onRightClick) def onRightClick(self, evt=None): menu = wx.Menu() self.pmenu_labels = {} for menulabel in ('Move up', 'Move down', 'Move to top', 'Move to bottom', 'Remove from list'): item = menu.Append(-1, menulabel) self.pmenu_labels[item.Id] = menulabel self.Bind(wx.EVT_MENU, self.onRightEvent, item) self.PopupMenu(menu) menu.Destroy() def onRightEvent(self, event=None): idx = self.GetSelection() mlabel = self.pmenu_labels.get(event.GetId(), None) if idx < 0 or mlabel is None: return names = self.GetItems() this = names.pop(idx) if mlabel == 'Move up' and idx > 0: names.insert(idx-1, this) elif mlabel == 'Move down' and idx < len(names): names.insert(idx+1, this) elif mlabel == 'Move to top': names.insert(0, this) elif mlabel == 'Move to bottom': names.append(this) elif mlabel == 'Remove from list' and self.remove_action is not None: self.remove_action(this) self.Clear() for name in names: self.Append(name) wxutils-0.3.0/wxutils/notebooks.py000066400000000000000000000025561441045704400173400ustar00rootroot00000000000000import wx import wx.lib.agw.flatnotebook as flat_nb FNB_STYLE = flat_nb.FNB_NO_X_BUTTON|flat_nb.FNB_NODRAG def flatnotebook(parent, paneldict, panelkws={}, on_change=None, selection=0, style=None, with_dropdown=False, with_nav_buttons=False, with_smart_tabs=False, **kws): if style is None: style = FNB_STYLE if with_dropdown: style |= flat_nb.FNB_DROPDOWN_TABS_LIST if not with_nav_buttons: style |= flat_nb.FNB_NO_NAV_BUTTONS if with_smart_tabs: style |= flat_nb.FNB_SMART_TABS nb = flat_nb.FlatNotebook(parent, agwStyle=style, **kws) nb.SetTabAreaColour(wx.Colour(250, 250, 250)) nb.SetActiveTabColour(wx.Colour(254, 254, 195)) nb.SetNonActiveTabTextColour(wx.Colour(10, 10, 128)) nb.SetActiveTabTextColour(wx.Colour(128, 0, 0)) nb.SetPadding(wx.Size(5, 5)) nb.SetFont(wx.Font(12, wx.SWISS, wx.NORMAL, wx.BOLD, 0, "")) nb.DeleteAllPages() nb.pagelist = [] grandparent = parent.GetParent() if grandparent is None: grandparent = parent for name, creator in paneldict.items(): _page = creator(parent=grandparent, **panelkws) nb.AddPage(_page," %s " % name, True) nb.pagelist.append(_page) if callable(on_change): nb.Bind(wx.EVT_NOTEBOOK_PAGE_CHANGED, on_change) nb.SetSelection(selection) return nb wxutils-0.3.0/wxutils/paths.py000066400000000000000000000050471441045704400164520ustar00rootroot00000000000000import os import sys HAS_PWD = True try: import pwd except ImportError: HAS_PWD = False platform = sys.platform if os.name == 'nt': platform = 'win' if platform == 'linux2': platform = 'linux' def unixpath(d): "unix path" return d.replace('\\', '/') def winpath(d): "ensure path uses windows delimiters" if d.startswith('//'): d = d[1:] d = d.replace('/', '\\') return d nativepath = unixpath if platform == 'win': nativepath = winpath def get_homedir(): "determine home directory of current user" home = None def check(method, s): "check that os.path.expanduser / expandvars gives a useful result" try: if method(s) not in (None, s): return method(s) except: pass return None # for Unixes, allow for sudo case susername = os.environ.get("SUDO_USER", None) if home is None and HAS_PWD and susername is not None: home = pwd.getpwnam(susername).pw_dir # try expanding '~' -- should work on most Unixes if home is None: home = check(os.path.expanduser, '~') # try the common environmental variables if home is None: for var in ('$HOME', '$HOMEPATH', '$USERPROFILE', '$ALLUSERSPROFILE'): home = check(os.path.expandvars, var) if home is not None: break # For Windows, ask for parent of Roaming 'Application Data' directory if home is None and platform == 'win': try: from win32com.shell import shellcon, shell home = shell.SHGetFolderPath(0, shellcon.CSIDL_APPDATA, 0, 0) except ImportError: pass # finally, use current folder if home is None: home = os.path.abspath('.') return nativepath(home) def get_cwd(): """get current working directory Note: os.getcwd() can fail with permission error. when that happens, this changes to the users `HOME` directory and returns that directory so that it always returns an existing and readable directory. """ try: return os.getcwd() except: home = get_homedir() os.chdir(home) return home def get_configfile(configfile): """get configuration file from home dir""" cfile = os.path.join(get_homedir(), configfile) if os.path.exists(cfile): return cfile def save_configfile(configfile, text): """save text to configuration file in home dir""" cfile = os.path.join(get_homedir(), configfile) with open(cfile, 'w') as fh: fh.write(text) wxutils-0.3.0/wxutils/periodictable.py000066400000000000000000000302061441045704400201340ustar00rootroot00000000000000import sys import wx import wx.lib.mixins.inspection from wxutils import SetTip FRAME_BG = (253, 253, 250) ## light grey TITLE_BG = (253, 253, 250) ## light grey FGCOL = ( 20, 20, 120) ## blue BGCOL = (253, 253, 250) ## light grey BGSEL = (250, 250, 200) ## yellow FGSEL = (200, 0, 0) ## red class PeriodicTablePanel(wx.Panel): """periodic table of the elements""" elems = {'H': ( 0, 0), 'He': ( 0, 17), 'Li': ( 1, 0), 'Be': ( 1, 1), 'B': ( 1, 12), 'C': ( 1, 13), 'N': ( 1, 14), 'O': ( 1, 15), 'F': ( 1, 16), 'Ne': ( 1, 17), 'Na': ( 2, 0), 'Mg': ( 2, 1), 'Al': ( 2, 12), 'Si': ( 2, 13), 'P': ( 2, 14), 'S': ( 2, 15), 'Cl': ( 2, 16), 'Ar': ( 2, 17), 'K': ( 3, 0), 'Ca': ( 3, 1), 'Sc': ( 3, 2), 'Ti': ( 3, 3), 'V': ( 3, 4), 'Cr': ( 3, 5), 'Mn': ( 3, 6), 'Fe': ( 3, 7), 'Co': ( 3, 8), 'Ni': ( 3, 9), 'Cu': ( 3, 10), 'Zn': ( 3, 11), 'Ga': ( 3, 12), 'Ge': ( 3, 13), 'As': ( 3, 14), 'Se': ( 3, 15), 'Br': ( 3, 16), 'Kr': ( 3, 17), 'Rb': ( 4, 0), 'Sr': ( 4, 1), 'Y': ( 4, 2), 'Zr': ( 4, 3), 'Nb': ( 4, 4), 'Mo': ( 4, 5), 'Tc': ( 4, 6), 'Ru': ( 4, 7), 'Rh': ( 4, 8), 'Pd': ( 4, 9), 'Ag': ( 4, 10), 'Cd': ( 4, 11), 'In': ( 4, 12), 'Sn': ( 4, 13), 'Sb': ( 4, 14), 'Te': ( 4, 15), 'I': ( 4, 16), 'Xe': ( 4, 17), 'Cs': ( 5, 0), 'Ba': ( 5, 1), 'La': ( 5, 2), 'Ce': ( 7, 3), 'Pr': ( 7, 4), 'Nd': ( 7, 5), 'Pm': ( 7, 6), 'Sm': ( 7, 7), 'Eu': ( 7, 8), 'Gd': ( 7, 9), 'Tb': ( 7, 10), 'Dy': ( 7, 11), 'Ho': ( 7, 12), 'Er': ( 7, 13), 'Tm': ( 7, 14), 'Yb': ( 7, 15), 'Lu': ( 7, 16), 'Hf': ( 5, 3), 'Ta': ( 5, 4), 'W': ( 5, 5), 'Re': ( 5, 6), 'Os': ( 5, 7), 'Ir': ( 5, 8), 'Pt': ( 5, 9), 'Au': ( 5, 10), 'Hg': ( 5, 11), 'Tl': ( 5, 12), 'Pb': ( 5, 13), 'Bi': ( 5, 14), 'Po': ( 5, 15), 'At': ( 5, 16), 'Rn': ( 5, 17), 'Fr': ( 6, 0), 'Ra': ( 6, 1), 'Ac': ( 6, 2), 'Th': ( 8, 3), 'Pa': ( 8, 4), 'U': ( 8, 5), 'Np': ( 8, 6), 'Pu': ( 8, 7), 'Am': ( 8, 8), 'Cm': ( 8, 9), 'Bk': ( 8, 10), 'Cf': ( 8, 11), 'Es': ( 8, 12), 'Fm': ( 8, 13), 'Md': ( 8, 14), 'No': ( 8, 15), 'Lr': ( 8, 16)} syms = ['H', 'He', 'Li', 'Be', 'B', 'C', 'N', 'O', 'F', 'Ne', 'Na', 'Mg', 'Al', 'Si', 'P', 'S', 'Cl', 'Ar', 'K', 'Ca', 'Sc', 'Ti', 'V', 'Cr', 'Mn', 'Fe', 'Co', 'Ni', 'Cu', 'Zn', 'Ga', 'Ge', 'As', 'Se', 'Br', 'Kr', 'Rb', 'Sr', 'Y', 'Zr', 'Nb', 'Mo', 'Tc', 'Ru', 'Rh', 'Pd', 'Ag', 'Cd', 'In', 'Sn', 'Sb', 'Te', 'I', 'Xe', 'Cs', 'Ba', 'La', 'Ce', 'Pr', 'Nd', 'Pm', 'Sm', 'Eu', 'Gd', 'Tb', 'Dy', 'Ho', 'Er', 'Tm', 'Yb', 'Lu', 'Hf', 'Ta', 'W', 'Re', 'Os', 'Ir', 'Pt', 'Au', 'Hg', 'Tl', 'Pb', 'Bi', 'Po', 'At', 'Rn', 'Fr', 'Ra', 'Ac', 'Th', 'Pa', 'U', 'Np', 'Pu', 'Am', 'Cm', 'Bk', 'Cf', 'Es', 'Fm', 'Md', 'No', 'Lr'] names = ['hydrogen', 'helium', 'lithium', 'beryllium', 'boron', 'carbon', 'nitrogen', 'oxygen', 'fluorine', 'neon', 'sodium', 'magnesium', 'aluminum', 'silicon', 'phosphorus', 'sulfur', 'chlorine', 'argon', 'potassium', 'calcium', 'scandium', 'titanium', 'vanadium', 'chromium', 'manganese', 'iron', 'cobalt', 'nickel', 'copper', 'zinc', 'gallium', 'germanium', 'arsenic', 'selenium', 'bromine', 'krypton', 'rubidium', 'strontium', 'yttrium', 'zirconium', 'niobium', 'molybdenum', 'technetium', 'ruthenium', 'rhodium', 'palladium', 'silver', 'cadmium', 'indium', 'tin', 'antimony', 'tellurium', 'iodine', 'xenon', 'cesium', 'barium', 'lanthanum', 'cerium', 'praseodymium', 'neodymium', 'promethium', 'samarium', 'europium', 'gadolinium', 'terbium', 'dysprosium', 'holmium', 'erbium', 'thulium', 'ytterbium', 'lutetium', 'hafnium', 'tantalum', 'tungsten', 'rhenium', 'osmium', 'iridium', 'platinum', 'gold', 'mercury', 'thallium', 'lead', 'bismuth', 'polonium', 'astatine', 'radon', 'francium', 'radium', 'actinium', 'thorium', 'protactinium', 'uranium', 'neptunium', 'plutonium', 'americium', 'curium', 'berkelium', 'californium', 'einsteinium', 'fermium', 'mendelevium', 'nobelium', 'lawrencium'] def __init__(self, parent, title='Select Element', multi_select=False, onselect=None, tooltip_msg=None, size=(-1, -1), fontsize=10, fgcol=None, bgcol=None, fgsel=None, bgsel=None, **kws): wx.Panel.__init__(self, parent, -1, size=size, name='PeriodicTable', **kws) self.parent = parent self.onselect = onselect self.tooltip_msg = tooltip_msg self.wids = {} self.ctrls = {} self.SetBackgroundColour(FRAME_BG) self.selected = [] if fgcol is None: fgcol = FGCOL if bgcol is None: bgcol = BGCOL if fgsel is None: fgsel = FGSEL if bgsel is None: bgsel = BGSEL self.fgcol = fgcol self.bgcol = bgcol self.fgsel = fgsel self.bgsel = bgsel self.current = None self.multi_select = multi_select if sys.platform.lower().startswith('win'): fonstize = fontsize - 1 subfontsize = fontsize if sys.platform.lower().startswith('lin'): subfontsize -= 1 self.titlefont = wx.Font(fontsize, wx.DEFAULT, wx.NORMAL, wx.BOLD) self.elemfont = wx.Font(fontsize, wx.SWISS, wx.NORMAL, wx.BOLD) self.subtitlefont = wx.Font(subfontsize, wx.DEFAULT, wx.NORMAL, wx.BOLD) self.BuildPanel() def onKey(self, event=None, name=None): """support browsing through elements with arrow keys""" if self.current is None: return selname = self.current.GetLabel() if selname in self.elems: coords = self.elems[selname] if name is None and event is not None: thiskey = event.GetKeyCode() if name == 'left': thiskey = wx.WXK_LEFT elif name == 'right': thiskey = wx.WXK_RIGHT elif name == 'up': thiskey = wx.WXK_UP elif name == 'down': thiskey = wx.WXK_DOWN newcoords = None if thiskey == wx.WXK_UP: newcoords = (coords[0]-1, coords[1]) elif thiskey == wx.WXK_DOWN: newcoords = (coords[0]+1, coords[1]) elif thiskey in (wx.WXK_LEFT, wx.WXK_RIGHT): newcoords = None # try to support jumping to/from lanthanide, # and wrapping around elements if newcoords not in self.elems.values(): if thiskey == wx.WXK_DOWN: newcoords = (coords[0]+2, coords[1]) elif thiskey == wx.WXK_UP: newcoords = (coords[0]-2, coords[1]) elif thiskey in (wx.WXK_LEFT, wx.WXK_RIGHT): try: znum = self.syms.index(selname) except: return if thiskey == wx.WXK_LEFT and znum > 0: newcoords = self.elems[self.syms[znum-1]] elif thiskey == wx.WXK_RIGHT and znum < len(self.syms)-1: newcoords = self.elems[self.syms[znum+1]] if newcoords in self.elems.values(): newlabel = None for xlabel, xcoords in self.elems.items(): if newcoords == xcoords: newlabel = xlabel if newlabel is not None: self.onclick(label=newlabel) # event.Skip() def on_clear_all(self, event=None): for wid in list(self.ctrls.values()) + list(self.wids.values()): wid.SetBackgroundColour(self.bgcol) wid.SetForegroundColour(self.fgcol) self.selected = [] self.current = None def onclick(self, event=None, label=None): if event is None and label is None: return textwid = None if label is None and event.Id in self.wids: textwid = self.wids[event.Id] label = textwid.GetLabel() if label is None: return if textwid is None and label is not None: textwid = self.ctrls[label] if self.multi_select: if label in self.selected: # already selected textwid.SetBackgroundColour(self.bgcol) textwid.SetForegroundColour(self.fgcol) self.selected.remove(label) else: textwid.SetBackgroundColour(self.bgsel) textwid.SetForegroundColour(self.fgsel) self.selected.append(label) else: textwid.SetBackgroundColour(self.bgsel) textwid.SetForegroundColour(self.fgsel) if self.current is not None and self.current != textwid: self.current.SetBackgroundColour(self.bgcol) self.current.SetForegroundColour(self.fgcol) self.current = textwid self.selected = [textwid] znum = self.syms.index(label) name = self.names[znum] self.tsym.SetLabel(label) self.title.SetLabel(name) self.tznum.SetLabel("{:d}".format(znum+1)) if self.onselect is not None: self.onselect(elem=label, event=event) self.Refresh() def BuildPanel(self): sizer = wx.GridBagSizer() sizer.SetHGap(0) sizer.SetVGap(1) for name, coords in self.elems.items(): tw = wx.StaticText(self, -1, label=name) tw.SetFont(self.elemfont) tw.SetForegroundColour(self.fgcol) tw.SetBackgroundColour(self.bgcol) tw.SetMinSize((20, 18)) tw.Bind(wx.EVT_LEFT_DOWN, self.onclick) if self.tooltip_msg is not None: SetTip(tw, self.tooltip_msg) self.wids[tw.Id] = tw self.ctrls[name] = tw sizer.Add(tw, coords, (1, 1), wx.ALIGN_LEFT, 0) self.title = wx.StaticText(self, -1, label=' Select Element ') self.tsym = wx.StaticText(self, -1, label='__') self.tznum = wx.StaticText(self, -1, label='__') for a in (self.title, self.tsym, self.tznum): a.SetFont(self.titlefont) a.SetBackgroundColour(TITLE_BG) sizer.Add(self.title, (0, 4), (1, 8), wx.ALIGN_CENTER, 5) sizer.Add(self.tsym, (0, 2), (1, 2), wx.ALIGN_LEFT, 5) sizer.Add(self.tznum, (0, 12), (1, 3), wx.ALIGN_LEFT, 5) self.subtitle = [None, None] self.subtitle[0] = wx.StaticText(self, -1, label=' ') self.subtitle[0].SetFont(self.subtitlefont) self.subtitle[1] = wx.StaticText(self, -1, label=' ') self.subtitle[1].SetFont(self.subtitlefont) sizer.Add(self.subtitle[0], (1, 2), (1, 9), wx.ALIGN_LEFT) sizer.Add(self.subtitle[1], (2, 2), (1, 9), wx.ALIGN_LEFT) sizer.SetEmptyCellSize((2, 2)) self.Bind(wx.EVT_KEY_UP, self.onKey) self.SetSizer(sizer) ix, iy = self.GetBestSize() self.SetSize((ix+2, iy+2)) sizer.Fit(self) def set_subtitle(self, label, index=0): if index not in (0, 1): index = 0 self.subtitle[index].SetLabel(label) class PTableFrame(wx.Frame): def __init__(self, size=(-1, -1), fontsize=10): wx.Frame.__init__(self, parent=None, size=size) ptab = PeriodicTablePanel(self, title='Periodic Table', tooltip_msg='Select Element', fontsize=fontsize, onselect=self.onElement) sx, sy = ptab.GetBestSize() sizer = wx.BoxSizer(wx.VERTICAL) sizer.Add(ptab, 1, wx.EXPAND|wx.ALL, 5) self.SetSizer(sizer) sizer.Fit(self) self.SetMinSize((sx+10, sy+10)) self.Raise() def onElement(self, elem=None, event=None): print( 'Element Selected: ', elem) wxutils-0.3.0/wxutils/readlinetextctrl.py000066400000000000000000000146331441045704400207110ustar00rootroot00000000000000#!/usr/bin/env python # from __future__ import print_function import sys import wx import os import time MAX_HISTORY = 5000 class ReadlineTextCtrl(wx.TextCtrl): """ TextCtrl with a readline-like interaction and text history that can be persisted between sessions. """ def __init__(self, parent=None, value='', size=(400,-1), historyfile=None, style=wx.ALIGN_LEFT|wx.TE_PROCESS_ENTER, appname='wxapp', **kws): wx.TextCtrl.__init__(self, parent, -1, value=value, size=size, style=style, **kws) self._val = value self.appname = appname self.historyfile = historyfile self.hist_buff = [] if self.historyfile is None: self.historyfile= os.path.join(os.environ.get('HOME','.'), '.%s_hist' % appname) self.LoadHistory() self.hist_mark = len(self.hist_buff) self.hist_sessionstart = self.hist_mark if sys.platform == 'darwin': self.Bind(wx.EVT_KEY_UP, self.onChar) else: self.Bind(wx.EVT_CHAR, self.onChar) self.Bind(wx.EVT_SET_FOCUS, self.onSetFocus) self.Bind(wx.EVT_KILL_FOCUS, self.onKillFocus) self.__GetMark() self.notebooks = None def __GetMark(self, event=None): " keep track of cursor position within text" try: self.__mark = min(wx.TextCtrl.GetSelection(self)[0], len(wx.TextCtrl.GetValue(self).strip())) except: self.__mark = 0 def __SetMark(self, m=None): "set position of mark" if m is None: m = self.__mark self.SetSelection(m,m) def onKillFocus(self, event=None): self.__GetMark() def onSetFocus(self, event=None): self.__SetMark() def onChar(self, event): """ on Character event""" key = event.GetKeyCode() entry = wx.TextCtrl.GetValue(self).strip() pos = wx.TextCtrl.GetSelection(self) do_skip = True ctrl = event.ControlDown() # really, the order here is important: # 1. return sends to ValidateEntry if key == wx.WXK_RETURN and len(entry) > 0: pass # 2. other non-text characters are passed without change if key == wx.WXK_UP: self.hist_mark = max(0, self.hist_mark-1) try: self.SetValue(self.hist_buff[self.hist_mark]) except: pass self.SetInsertionPointEnd() do_skip = False elif key == wx.WXK_DOWN: self.hist_mark += 1 if self.hist_mark >= len(self.hist_buff): self.SetValue('') else: self.SetValue(self.hist_buff[self.hist_mark]) self.SetInsertionPointEnd() elif key == wx.WXK_TAB: if self.notebooks is not None: self.notebooks.AdvanceSelection() self.SetFocus() elif key == wx.WXK_HOME or (ctrl and key == 1): # ctrl-a self.SetInsertionPoint(0) self.SetSelection(0,0) do_skip = False elif key == wx.WXK_END or (ctrl and key == 5): self.SetInsertionPointEnd() elif ctrl and key == 2: # b mark = max(1, self.GetSelection()[1]) self.SetSelection(mark-1, mark-1) elif ctrl and key== 3: # c cb_txt = wx.TextDataObject() wx.TheClipboard.Open() if wx.TheClipboard.IsOpened(): cb_txt.SetData(str(entry)) wx.TheClipboard.SetData(cb_txt) wx.TheClipboard.Close() elif ctrl and key == 4: # d mark = self.GetSelection()[1] self.SetValue("%s%s" % (entry[:mark], entry[mark+1:])) self.SetSelection(mark, mark) do_skip = False elif ctrl and key == 6: # f mark = self.GetSelection()[1] self.SetSelection(mark+1, mark+1) elif ctrl and key == 8: # h mark = self.GetSelection()[1] self.SetValue("%s%s" % (entry[:mark-1], entry[mark:])) self.SetSelection(mark-1, mark-1) elif ctrl and key == 11: # k mark = self.GetSelection()[1] self.SetValue("%s" % (entry[:mark])) self.SetSelection(mark, mark) elif ctrl and key == 22: # v cb_txt = wx.TextDataObject() wx.TheClipboard.Open() if wx.TheClipboard.IsOpened(): wx.TheClipboard.GetData(cb_txt) wx.TheClipboard.Close() try: self.SetValue(str(cb_txt.GetText())) except TypeError: pass do_skip = False elif ctrl and key == 24: # x cb_txt = wx.TextDataObject() wx.TheClipboard.Open() if wx.TheClipboard.IsOpened(): cb_txt.SetData(str(entry)) wx.TheClipboard.GetData(cb_txt) wx.TheClipboard.Close() self.SetValue('') elif ctrl: pass self.Refresh() return def AddToHistory(self, text=''): if len(text.strip()) > 0: self.hist_buff.append(text) self.hist_mark = len(self.hist_buff) def SaveHistory(self, filename=None, session_only=False): if filename is None: filename = self.historyfile try: fout = open(filename,'w') except IOError: print( 'Cannot save history ', filename) fout.write("# %s history saved %s\n\n" % (self.appname, time.ctime())) start_entry = -MAX_HISTORY if session_only: start_entry = self.hist_sessionstart fout.write('\n'.join(self.hist_buff[start_entry:])) fout.write("\n") fout.close() def LoadHistory(self): if os.path.exists(self.historyfile): self.hist_buff = [] for txt in open(self.historyfile, 'r').readlines(): stxt = txt.strip() if len(stxt) > 0 and not stxt.startswith('#'): self.hist_buff.append(txt[:-1]) def def_onText(self, event=None): if event is None: return txt = event.GetString() if len(txt.strip()) > 0: self.hist_buff.append(txt) self.hist_mark = len(self.hist_buff) event.Skip() wxutils-0.3.0/wxutils/text.py000066400000000000000000000116171441045704400163170ustar00rootroot00000000000000import wx from functools import partial from .utils import LEFT, CEN class SimpleText(wx.StaticText): "simple static text wrapper" def __init__(self, parent, label, minsize=None, font=None, colour=None, bgcolour=None, style=CEN, tooltip=None, **kws): wx.StaticText.__init__(self, parent, -1, label=label, style=style, **kws) # if tooltip is not None: # self.SetTooltip(tooltip) if minsize is not None: self.SetMinSize(minsize) if font is not None: self.SetFont(font) if colour is not None: self.SetForegroundColour(colour) if bgcolour is not None: self.SetBackgroundColour(bgcolour) class TextCtrl(wx.TextCtrl): """simple TextCtrl t = TextCtrl(parent, value, font=None colour=None, bgcolour=None, action=None, action_kws=None, act_on_losefocus=True, **kws) has a method SetAction(action, action_kws) for setting action on RETURN (and optionally LoseFocus) """ def __init__(self, parent, value, font=None, colour=None, bgcolour=None, action=None, action_kws=None, act_on_losefocus=True, tooltip=None, **kws): self.act_on_losefocus = act_on_losefocus this_sty = wx.TE_PROCESS_ENTER if 'style' in kws: this_sty = this_sty | kws['style'] kws['style'] = this_sty wx.TextCtrl.__init__(self, parent, -1, **kws) self.SetValue(value) if font is not None: self.SetFont(font) if colour is not None: self.SetForegroundColour(colour) if bgcolour is not None: self.SetBackgroundColour(bgcolour) if tooltip is not None: self.SetTooltip(tooltip) if action_kws is None: action_kws = {} self.SetAction(action, **action_kws) self.Bind(wx.EVT_CHAR, self.onChar) self.Bind(wx.EVT_KILL_FOCUS, self.onFocus) def SetAction(self, action, **kws): "set action callback" self.__act = None if callable(action): self.__act = partial(action, **kws) def onFocus(self, evt=None): "focus events -- may act on KillFocus" if self.act_on_losefocus and self.__act is not None: self.__act(self.GetValue()) evt.Skip() def onChar(self, evt=None): "character events -- may act on RETURN" if evt.GetKeyCode() == wx.WXK_RETURN and self.__act is not None: self.__act(self.GetValue()) evt.Skip() class LabeledTextCtrl(TextCtrl): """ simple extension of TextCtrl with a .label attribute holding a SimpleText Typical usage: entry = LabeledTextCtrl(self, value='22', labeltext='X:') row = wx.BoxSizer(wx.HORIZONTAL) row.Add(entry.label, 1,wx.ALIGN_LEFT|wx.EXPAND) row.Add(entry, 1,wx.ALIGN_LEFT|wx.EXPAND) """ def __init__(self, parent, value, font=None, action=None, action_kws=None, act_on_losefocus=True, size=(-1, -1), bgcolour=None, colour=None, style=LEFT, labeltext=None, labelsize=(-1, -1), labelcolour=None, labelbgcolour=None, **kws): if labeltext is not None: self.label = SimpleText(parent, labeltext, size=labelsize, font=font, style=style, colour=labelcolour, bgcolour=labelbgcolour) try: value = str(value) except: value = ' ' TextCtrl.__init__(self, parent, value, font=font, colour=colour, bgcolour=bgcolour, style=style, size=size, action=action, action_kws=action_kws, act_on_losefocus=act_on_losefocus, **kws) class HyperText(wx.StaticText): """HyperText is a simple extension of wx.StaticText that 1. adds an underscore to the label to appear to be a hyperlink 2. performs the supplied action on Left-Up button events """ def __init__(self, parent, label, action=None, colour=(50, 50, 180), bgcolour=None, underline=True, **kws): wx.StaticText.__init__(self, parent, -1, label=label, **kws) self.SetForegroundColour(colour) if bgcolour is not None: self.SetBackgroundColour(bgcolour) if underline: font = self.GetFont() try: font.SetUnderlined(True) except: pass self.SetFont(font) self.action = action self.Bind(wx.EVT_LEFT_UP, self.OnSelect) def OnSelect(self, event=None): "Left-Up Event Handler" if hasattr(self.action,'__call__'): self.action(label=self.GetLabel(), event=event) event.Skip() wxutils-0.3.0/wxutils/utils.py000066400000000000000000000161041441045704400164670ustar00rootroot00000000000000#!/usr/bin/env python """ A collection of wx utility functions, mostly simplified wrappers around existing widgets. """ import os import sys from traceback import format_tb import wx from wx.lib.dialogs import ScrolledMessageDialog # some common abbrevs for wx ALIGNMENT styles RIGHT = RCEN = wx.ALIGN_RIGHT LEFT = LCEN = wx.ALIGN_LEFT CEN = CCEN = wx.ALIGN_CENTER LTEXT = wx.ST_NO_AUTORESIZE|wx.ALIGN_CENTER FRAMESTYLE = wx.DEFAULT_FRAME_STYLE|wx.TAB_TRAVERSAL def SetTip(wid, tip=''): wid.SetToolTip(tip) def set_sizer(panel, sizer=None, style=wx.VERTICAL, fit=False): """ utility for setting wx Sizer """ if sizer is None: sizer = wx.BoxSizer(style) panel.SetAutoLayout(1) panel.SetSizer(sizer) if fit: sizer.Fit(panel) def pack(window, sizer, expand=1.1): "simple wxPython pack function" tsize = window.GetSize() msize = window.GetMinSize() window.SetSizer(sizer) sizer.Fit(window) nsize = (10*int(expand*(max(msize[0], tsize[0])/10)), 10*int(expand*(max(msize[1], tsize[1])/10.))) window.SetSize(nsize) def Font(size, serif=False): """define a font by size and serif/ non-serif f = Font(10, serif=True) """ family = wx.DEFAULT if not serif: family = wx.SWISS return wx.Font(size, family, wx.NORMAL, wx.BOLD, 0, "") def SetChildrenFont(widget, font, dsize=None): "set font for a widget and all children" cfont = widget.GetFont() font.SetWeight(cfont.GetWeight()) if dsize == None: dsize = font.PointSize - cfont.PointSize else: font.PointSize = cfont.PointSize + dsize widget.SetFont(font) for child in widget.GetChildren(): set_font_with_children(child, font, dsize=dsize) def HLine(parent, size=(700, 3)): """Simple horizontal line h = HLine(parent, size=(700, 3) """ return wx.StaticLine(parent, size=size, style=wx.LI_HORIZONTAL|wx.GROW) def HLineText(panel, text, colour='#222288'): """draw an Horizontal line, then SimpleText underneath HLineText(panel, text, **kws) keywords are passed to SimpleText """ p = wx.Panel(panel) s = wx.BoxSizer(wx.HORIZONTAL) s.Add(wx.StaticLine(p, size=(50, 5), style=wx.LI_HORIZONTAL), 0, LEFT, 5) s.Add(SimpleText(p, text, **kws), 0, LEFT, 5) pack(p, s) return p class Check(wx.CheckBox): """Simple Checkbox c = Check(parent, default=True, label=None, **kws) kws passed to wx.CheckBox """ def __init__(self, parent, label='', default=True, action=None, **kws): wx.CheckBox.__init__(self, parent, -1, label=label, **kws) self.SetValue({True: 1, False:0}[default]) if action is not None: self.Bind(wx.EVT_CHECKBOX, action) def MenuItem(parent, menu, label='', longtext='', action=None, kind='normal', checked=False): """Add Item to a Menu, with action m = Menu(parent, menu, label, longtext, action=None, kind='normal') """ kinds_map = {'normal': wx.ITEM_NORMAL, 'radio': wx.ITEM_RADIO, 'check': wx.ITEM_CHECK} menu_kind = wx.ITEM_NORMAL if kind in kinds_map.values(): menu_kind = kind elif kind in kinds_map: menu_kind = kinds_map[kind] item = menu.Append(-1, label, longtext, kind=menu_kind) if menu_kind == wx.ITEM_CHECK and checked: item.Check(True) if callable(action): parent.Bind(wx.EVT_MENU, action, item) return item def Popup(parent, message, title, style=None, **kws): """Simple popup message dialog p = Popup(parent, message, title, **kws) returns output of MessageDialog.ShowModal() """ if style is None: style = wx.OK|wx.ICON_INFORMATION dlg = wx.MessageDialog(parent, message, title, style=style, **kws) ret = dlg.ShowModal() dlg.Destroy() return ret def ExceptionPopup(parent, title, lines, with_traceback=True, style=None, **kws): """Modal message dialog with current Python Exception""" if style is None: style = wx.OK|wx.ICON_INFORMATION etype, exc, tb = sys.exc_info() if with_traceback: lines.append("Traceback (most recent calls last):") for tline in format_tb(tb): if tline.endswith('\n'): tline = tline[:-1] lines.append(tline) lines.append(f"{etype.__name__}: {exc}") message = '\n'.join(lines) dkws = {'size': (700, 350)} dkws.update(kws) dlg = ScrolledMessageDialog(parent, message, title, **dkws) dlg.ShowModal() dlg.Destroy() def get_homedir(): "determine home directory" homedir = None def check(method, s): "check that os.path.expanduser / expandvars gives a useful result" try: if method(s) not in (None, s): return method(s) except: pass return None # try expanding '~' -- should work on most Unixes homedir = check(os.path.expanduser, '~') # try the common environmental variables if homedir is None: for var in ('$HOME', '$HOMEPATH', '$USERPROFILE', '$ALLUSERSPROFILE'): homedir = check(os.path.expandvars, var) if homedir is not None: break # For Windows, ask for parent of Roaming 'Application Data' directory if homedir is None and os.name == 'nt': try: from win32com.shell import shellcon, shell homedir = shell.SHGetFolderPath(0, shellcon.CSIDL_APPDATA, 0, 0) except ImportError: pass # finally, use current folder if homedir is None: homedir = os.path.abspath('.') return homedir def get_cwd(): """get current working directory Note: os.getcwd() can fail with permission error. when that happens, this changes to the users `HOME` directory and returns that directory so that it always returns an existing and readable directory. """ try: return os.getcwd() except: curdir = os.path.abspath('.') os.chdir(curdir) return home def gcd(parent=None, **kws): """Directory Browser to Change Directory""" if parent is None: parent = wx.GetApp() dlg = wx.DirDialog(parent, 'Choose Directory', defaultPath = get_cwd(), style = wx.DD_DEFAULT_STYLE) if dlg.ShowModal() == wx.ID_OK: os.chdir(dlg.GetPath()) dlg.Destroy() return get_cwd() def panel_pack(window, panel, pad=10): """ a simple method to pack a single panel to a single frame """ sizer = wx.BoxSizer(wx.VERTICAL) sizer.Add(panel, 1, wx.LEFT, 5) window.SetSizer(sizer) sizer.Fit(window) w0, h0 = window.GetSize() w1, h1 = window.GetBestSize() window.SetSize((max(w0, w1)+pad, max(h0, h1)+pad)) def show_wxsizes(obj): """recursively show sizes of wxPython objects -- useful for avoiding size<1 errors""" for child in obj.GetChildren(): try: csize = child.GetSize() if csize[0] < 1 or csize[1] < 1: print(child, csize) except: pass try: show_wxsizes(child) except: pass