first prototype
This commit is contained in:
129
.gitignore
vendored
Normal file
129
.gitignore
vendored
Normal file
@ -0,0 +1,129 @@
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
pip-wheel-metadata/
|
||||
share/python-wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.nox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
*.py,cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
db.sqlite3-journal
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# IPython
|
||||
profile_default/
|
||||
ipython_config.py
|
||||
|
||||
# pyenv
|
||||
.python-version
|
||||
|
||||
# pipenv
|
||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||
# install all needed dependencies.
|
||||
#Pipfile.lock
|
||||
|
||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
|
||||
__pypackages__/
|
||||
|
||||
# Celery stuff
|
||||
celerybeat-schedule
|
||||
celerybeat.pid
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
.dmypy.json
|
||||
dmypy.json
|
||||
|
||||
# Pyre type checker
|
||||
.pyre/
|
78
eval.py
Normal file
78
eval.py
Normal file
@ -0,0 +1,78 @@
|
||||
import ast
|
||||
import operator
|
||||
|
||||
from utils import typename
|
||||
|
||||
|
||||
BIN_OPS = {
|
||||
ast.Add: operator.add,
|
||||
ast.Sub: operator.sub,
|
||||
ast.Mult: operator.mul,
|
||||
ast.Div: operator.truediv,
|
||||
ast.Mod: operator.mod
|
||||
}
|
||||
|
||||
UNARY_OPS = {
|
||||
ast.UAdd: operator.pos,
|
||||
ast.USub: operator.neg
|
||||
}
|
||||
|
||||
|
||||
def forgiving_eval(value):
|
||||
try:
|
||||
return arithmetic_eval(value)
|
||||
except:
|
||||
return value
|
||||
|
||||
|
||||
def arithmetic_eval(s):
|
||||
node = ast.parse(s, mode="eval")
|
||||
return ast_node_eval(node.body)
|
||||
|
||||
|
||||
def ast_node_eval(node):
|
||||
if isinstance(node, ast.Expression):
|
||||
return ast_node_eval(node.body)
|
||||
elif isinstance(node, ast.Str):
|
||||
return node.s
|
||||
elif isinstance(node, ast.Num):
|
||||
return node.n
|
||||
elif isinstance(node, ast.BinOp):
|
||||
op = get_operator(node, BIN_OPS)
|
||||
left = ast_node_eval(node.left)
|
||||
right = ast_node_eval(node.right)
|
||||
return op(left, right)
|
||||
elif isinstance(node, ast.UnaryOp):
|
||||
op = get_operator(node, UNARY_OPS)
|
||||
operand = ast_node_eval(node.operand)
|
||||
return op(operand)
|
||||
else:
|
||||
tn = typename(node)
|
||||
raise ArithmeticEvalError(f"Unsupported node type {tn}")
|
||||
|
||||
|
||||
def get_operator(node, ops):
|
||||
op_type = type(node.op)
|
||||
try:
|
||||
op = ops[op_type]
|
||||
except KeyError as e:
|
||||
nn = typename(node)
|
||||
on = typename(node.op)
|
||||
raise ArithmeticEvalError(f"Unsupported {nn} {on}") from e
|
||||
else:
|
||||
return op
|
||||
|
||||
|
||||
class ArithmeticEvalError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
|
||||
#TODO:
|
||||
#print like SyntaxError:
|
||||
# "something with an error here"
|
||||
# ^
|
||||
#this needs full string and offset of current node within full string stored
|
||||
|
||||
|
||||
|
41
fake.py
Normal file
41
fake.py
Normal file
@ -0,0 +1,41 @@
|
||||
|
||||
data = {
|
||||
"TEST0_psen_db": {
|
||||
"x": None,
|
||||
"aaa": "aaa",
|
||||
"bbbbbb": 2,
|
||||
"c": "cccccc"
|
||||
},
|
||||
|
||||
"TEST1_psen_db1": {
|
||||
"x": None,
|
||||
"bbbbbb": 2,
|
||||
"c": "cccccc"
|
||||
},
|
||||
|
||||
"TEST2TEST2_psen_db": {
|
||||
"x": None,
|
||||
"y": [1,2,3],
|
||||
"aaa": "aaa",
|
||||
"c": "cccccc"
|
||||
},
|
||||
|
||||
"ignored": {}
|
||||
}
|
||||
|
||||
|
||||
server_info = {
|
||||
"active_instances": list(data.keys())
|
||||
}
|
||||
|
||||
|
||||
class PipelineClient:
|
||||
def __init__(*args, **kwargs):
|
||||
pass
|
||||
def get_server_info(self):
|
||||
return server_info
|
||||
def get_instance_config(self, name):
|
||||
return data[name]
|
||||
|
||||
|
||||
|
34
listentry.py
Normal file
34
listentry.py
Normal file
@ -0,0 +1,34 @@
|
||||
import wx
|
||||
from mathentry import MathEntry
|
||||
|
||||
|
||||
class ListEntry(wx.BoxSizer):
|
||||
|
||||
def __init__(self, parent, id=wx.ID_ANY, value=(), style=wx.TE_RIGHT):
|
||||
super().__init__(wx.HORIZONTAL)
|
||||
self.parent = parent
|
||||
self.style = style
|
||||
self.entries = []
|
||||
self.SetValue(value)
|
||||
|
||||
|
||||
def SetValue(self, value):
|
||||
self.entries.clear()
|
||||
self.Clear(True)
|
||||
for v in value:
|
||||
new = MathEntry(self.parent, value=v, style=self.style)
|
||||
self.entries.append(new)
|
||||
self.Add(new, flag=wx.EXPAND)
|
||||
|
||||
|
||||
def GetValue(self):
|
||||
return [e.GetValue() for e in self.entries]
|
||||
|
||||
def Disable(self):
|
||||
return [e.Disable() for e in self.entries]
|
||||
|
||||
def Enable(self):
|
||||
return [e.Enable() for e in self.entries]
|
||||
|
||||
|
||||
|
87
mathentry.py
Normal file
87
mathentry.py
Normal file
@ -0,0 +1,87 @@
|
||||
import wx
|
||||
from eval import arithmetic_eval
|
||||
from utils import typename
|
||||
|
||||
|
||||
class MathEntry(wx.TextCtrl):
|
||||
|
||||
def __init__(self, *args, value="", **kwargs):
|
||||
if "style" in kwargs:
|
||||
kwargs["style"] |= wx.TE_PROCESS_ENTER
|
||||
else:
|
||||
kwargs["style"] = wx.TE_PROCESS_ENTER
|
||||
|
||||
self.value_type = type(value)
|
||||
value = str(value)
|
||||
wx.TextCtrl.__init__(self, *args, value=value, **kwargs)
|
||||
|
||||
self._alarm = False
|
||||
self._last_good_value = self.GetValue()
|
||||
|
||||
self.Bind(wx.EVT_TEXT_ENTER, self.on_enter)
|
||||
self.Bind(wx.EVT_KEY_UP, self.on_escape)
|
||||
|
||||
|
||||
def GetValue(self):
|
||||
val = super().GetValue()
|
||||
try:
|
||||
val = self.value_type(val)
|
||||
except ValueError:
|
||||
pass
|
||||
return val
|
||||
|
||||
|
||||
def SetValue(self, val):
|
||||
val = str(val)
|
||||
super().SetValue(val)
|
||||
|
||||
|
||||
def on_enter(self, event):
|
||||
val = super().GetValue()
|
||||
|
||||
self._unset_alarm()
|
||||
|
||||
try:
|
||||
val = arithmetic_eval(val)
|
||||
self.value_type = type(val)
|
||||
except SyntaxError as e:
|
||||
en = typename(e)
|
||||
msg = e.args[0]
|
||||
msg = f"{en}: {msg}"
|
||||
self._set_alarm(msg)
|
||||
self.SetInsertionPoint(e.offset)
|
||||
except Exception as e:
|
||||
en = typename(e)
|
||||
msg = f"{en}: {e}"
|
||||
self._set_alarm(msg)
|
||||
self.SetInsertionPointEnd()
|
||||
else:
|
||||
self.SetValue(val)
|
||||
self._last_good_value = val
|
||||
|
||||
event.Skip()
|
||||
|
||||
|
||||
def on_escape(self, event):
|
||||
code = event.GetKeyCode()
|
||||
if code != wx.WXK_ESCAPE:
|
||||
event.Skip()
|
||||
return
|
||||
|
||||
if self._alarm:
|
||||
self.SetValue(self._last_good_value)
|
||||
self._unset_alarm()
|
||||
|
||||
|
||||
def _set_alarm(self, msg):
|
||||
self._alarm = True
|
||||
self.SetToolTip(msg)
|
||||
self.SetForegroundColour(wx.RED)
|
||||
|
||||
def _unset_alarm(self):
|
||||
self._alarm = False
|
||||
self.SetToolTip(None)
|
||||
self.SetForegroundColour(wx.NullColour)
|
||||
|
||||
|
||||
|
150
scam.py
Executable file
150
scam.py
Executable file
@ -0,0 +1,150 @@
|
||||
#!/usr/bin/env python3
|
||||
import wx
|
||||
|
||||
#from cam_server_client import PipelineClient
|
||||
from fake import PipelineClient
|
||||
|
||||
from tools import EXPANDING, STRETCH, make_filled_vbox, make_filled_hbox
|
||||
from mathentry import MathEntry
|
||||
from listentry import ListEntry
|
||||
|
||||
|
||||
|
||||
pc = PipelineClient("http://sf-daqsync-01:8889")
|
||||
si = pc.get_server_info()
|
||||
ai = si["active_instances"]
|
||||
pls = (i for i in ai if "psen_db" in i)
|
||||
pls = sorted(pls)
|
||||
|
||||
|
||||
|
||||
class MainFrame(wx.Frame):
|
||||
|
||||
def __init__(self, parent=None, title="test"):
|
||||
super().__init__(parent, title=title)
|
||||
|
||||
panel_main = MainPanel(self)
|
||||
|
||||
self.sizer = sizer = make_filled_vbox([panel_main])
|
||||
self.SetSizerAndFit(sizer)
|
||||
|
||||
|
||||
|
||||
class MainPanel(wx.Panel):
|
||||
|
||||
def __init__(self, parent):
|
||||
super().__init__(parent)
|
||||
|
||||
self.cb_pls = cb_pls = wx.ComboBox(self, choices=pls)
|
||||
self.entries = entries = SettingsList(self)
|
||||
self.btn_print = btn_print = wx.Button(self, label="Print")
|
||||
|
||||
cb_pls.Bind(wx.EVT_COMBOBOX, self.on_select)
|
||||
btn_print.Bind(wx.EVT_BUTTON, self.on_print)
|
||||
|
||||
widgets = [cb_pls, entries, btn_print]
|
||||
sizer = make_filled_vbox(widgets)
|
||||
self.SetSizer(sizer)
|
||||
|
||||
|
||||
def on_select(self, event):
|
||||
instance = self.cb_pls.GetValue()
|
||||
cfg = pc.get_instance_config(instance)
|
||||
self.entries.update(cfg)
|
||||
self._adjust_size()
|
||||
|
||||
|
||||
def _adjust_size(self):
|
||||
parent = self.GetParent()
|
||||
parent.sizer.Layout()
|
||||
parent.Fit()
|
||||
|
||||
|
||||
def on_print(self, event):
|
||||
data = self.entries.get()
|
||||
print(data)
|
||||
|
||||
|
||||
|
||||
class SettingsList(wx.GridSizer):
|
||||
|
||||
def __init__(self, parent, hgap=5, vgap=5):
|
||||
super().__init__(cols=2, hgap=hgap, vgap=vgap)
|
||||
self.parent = parent
|
||||
self.children = []
|
||||
|
||||
def update(self, cfg):
|
||||
self.clear()
|
||||
for k, v in sorted(cfg.items()):
|
||||
self.add(k, v)
|
||||
|
||||
def clear(self):
|
||||
self.Clear(True)
|
||||
|
||||
def add(self, *args):
|
||||
new = Setting(self.parent, *args)
|
||||
self.children.append(new)
|
||||
self.Add(new.state)
|
||||
self.Add(new.text, 0, wx.EXPAND|wx.ALL)
|
||||
|
||||
def get(self):
|
||||
res = {}
|
||||
for i in self.children:
|
||||
name = i.get_name()
|
||||
print(name)
|
||||
if i.get_state():
|
||||
value = i.get_value()
|
||||
else:
|
||||
value = None
|
||||
res[name] = value
|
||||
return res
|
||||
|
||||
|
||||
|
||||
class Setting:
|
||||
|
||||
def __init__(self, parent, label, value):
|
||||
self.state = state = wx.CheckBox(parent, label=label)
|
||||
|
||||
if isinstance(value, list):
|
||||
self.text = text = ListEntry(parent, style=wx.TE_RIGHT)
|
||||
else:
|
||||
self.text = text = MathEntry(parent, style=wx.TE_RIGHT)
|
||||
|
||||
state.Bind(wx.EVT_CHECKBOX, self.on_state_change)
|
||||
|
||||
if value is None:
|
||||
state.SetValue(False)
|
||||
text.Disable()
|
||||
else:
|
||||
state.SetValue(True)
|
||||
text.SetValue(value)
|
||||
|
||||
|
||||
def get_name(self):
|
||||
return self.state.GetLabel()
|
||||
|
||||
def get_state(self):
|
||||
return self.state.GetValue()
|
||||
|
||||
def get_value(self):
|
||||
return self.text.GetValue()
|
||||
|
||||
|
||||
def on_state_change(self, event):
|
||||
if self.get_state():
|
||||
self.text.Enable()
|
||||
else:
|
||||
self.text.Disable()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
app = wx.App()
|
||||
frame = MainFrame()
|
||||
frame.Show()
|
||||
app.MainLoop()
|
||||
|
||||
|
||||
|
59
tools.py
Normal file
59
tools.py
Normal file
@ -0,0 +1,59 @@
|
||||
import wx
|
||||
|
||||
|
||||
WX_DEFAULT_RESIZABLE_DIALOG_STYLE = wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER|wx.MINIMIZE_BOX|wx.MAXIMIZE_BOX
|
||||
|
||||
|
||||
class EXPANDING: pass
|
||||
class STRETCH: pass
|
||||
|
||||
|
||||
|
||||
def post_event(event, source):
|
||||
evt = wx.PyCommandEvent(event.typeId, source.GetId())
|
||||
wx.PostEvent(source, evt)
|
||||
|
||||
|
||||
def copy_to_clipboard(val):
|
||||
clipdata = wx.TextDataObject()
|
||||
clipdata.SetText(val)
|
||||
wx.TheClipboard.Open()
|
||||
wx.TheClipboard.SetData(clipdata)
|
||||
wx.TheClipboard.Close()
|
||||
|
||||
|
||||
|
||||
def make_filled_vbox(widgets, proportion=0, flag=wx.ALL|wx.EXPAND, border=0, box=None):
|
||||
return make_filled_box(wx.VERTICAL, widgets, proportion, flag, border, box)
|
||||
|
||||
def make_filled_hbox(widgets, proportion=1, flag=wx.ALL|wx.EXPAND, border=0, box=None):
|
||||
return make_filled_box(wx.HORIZONTAL, widgets, proportion, flag, border, box)
|
||||
|
||||
|
||||
def make_filled_box(orient, widgets, proportion, flag, border, box):
|
||||
if box is None:
|
||||
box = wx.BoxSizer(orient)
|
||||
|
||||
OTHER_PROP = {
|
||||
0: 1,
|
||||
1: 0
|
||||
}
|
||||
|
||||
expand = False
|
||||
|
||||
for i in widgets:
|
||||
if i is STRETCH:
|
||||
box.AddStretchSpacer()
|
||||
elif i is EXPANDING:
|
||||
expand = True # store for (and then apply to) next widget
|
||||
else:
|
||||
prop = proportion
|
||||
if expand:
|
||||
expand = False # apply only once
|
||||
prop = OTHER_PROP[prop] # other proportion makes widget expanding
|
||||
box.Add(i, proportion=prop, flag=flag, border=border)
|
||||
|
||||
return box
|
||||
|
||||
|
||||
|
Reference in New Issue
Block a user