unisi 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- unisi/__init__.py +7 -0
- unisi/autotest.py +210 -0
- unisi/common.py +28 -0
- unisi/containers.py +97 -0
- unisi/guielements.py +160 -0
- unisi/proxy.py +159 -0
- unisi/reloader.py +150 -0
- unisi/server.py +118 -0
- unisi/tables.py +116 -0
- unisi/users.py +271 -0
- unisi/utils.py +108 -0
- unisi/web/css/169.3abcdd94.css +1 -0
- unisi/web/css/app.31d6cfe0.css +0 -0
- unisi/web/css/vendor.9ed7638d.css +6 -0
- unisi/web/favicon.ico +0 -0
- unisi/web/fonts/KFOkCnqEu92Fr1MmgVxIIzQ.68bb21d0.woff +0 -0
- unisi/web/fonts/KFOlCnqEu92Fr1MmEU9fBBc-.48af7707.woff +0 -0
- unisi/web/fonts/KFOlCnqEu92Fr1MmSU5fBBc-.c2f7ab22.woff +0 -0
- unisi/web/fonts/KFOlCnqEu92Fr1MmWUlfBBc-.77ecb942.woff +0 -0
- unisi/web/fonts/KFOlCnqEu92Fr1MmYUtfBBc-.f5677eb2.woff +0 -0
- unisi/web/fonts/KFOmCnqEu92Fr1Mu4mxM.f1e2a767.woff +0 -0
- unisi/web/fonts/flUhRq6tzZclQEJ-Vdg-IuiaDsNa.4d73cb90.woff +0 -0
- unisi/web/fonts/flUhRq6tzZclQEJ-Vdg-IuiaDsNcIhQ8tQ.c5371cfb.woff2 +0 -0
- unisi/web/icons/favicon-128x128.png +0 -0
- unisi/web/icons/favicon-16x16.png +0 -0
- unisi/web/icons/favicon-32x32.png +0 -0
- unisi/web/icons/favicon-96x96.png +0 -0
- unisi/web/index.html +1 -0
- unisi/web/js/169.f952e294.js +1 -0
- unisi/web/js/193.283445be.js +1 -0
- unisi/web/js/430.591e9a73.js +1 -0
- unisi/web/js/app.ba94719f.js +1 -0
- unisi/web/js/vendor.d6797c01.js +45 -0
- unisi-0.1.0.dist-info/METADATA +422 -0
- unisi-0.1.0.dist-info/RECORD +37 -0
- unisi-0.1.0.dist-info/WHEEL +4 -0
- unisi-0.1.0.dist-info/licenses/LICENSE +201 -0
unisi/reloader.py
ADDED
@@ -0,0 +1,150 @@
|
|
1
|
+
from .autotest import config
|
2
|
+
|
3
|
+
empty_app = {
|
4
|
+
"blocks": [],
|
5
|
+
"header": "No screens",
|
6
|
+
"icon": None,
|
7
|
+
"menu": [["You need to put at least 1 file in the 'screens' folder.",'exclamation']],
|
8
|
+
"name": "",
|
9
|
+
"order": 0,
|
10
|
+
"toolbar": [],
|
11
|
+
"type": "screen"
|
12
|
+
}
|
13
|
+
|
14
|
+
if config.hot_reload:
|
15
|
+
import os, sys, traceback
|
16
|
+
from watchdog.observers import Observer
|
17
|
+
from watchdog.events import PatternMatchingEventHandler
|
18
|
+
from .users import User
|
19
|
+
from .utils import divpath, Redesign, app_dir
|
20
|
+
from .autotest import check_screen
|
21
|
+
import re, collections
|
22
|
+
|
23
|
+
#for removing message duplicates
|
24
|
+
file_content = collections.defaultdict(str)
|
25
|
+
|
26
|
+
busy = False
|
27
|
+
cwd = os.getcwd()
|
28
|
+
|
29
|
+
def free():
|
30
|
+
global busy
|
31
|
+
if request_file:
|
32
|
+
reload(request_file)
|
33
|
+
else:
|
34
|
+
busy = False
|
35
|
+
|
36
|
+
def reload(sname):
|
37
|
+
user = User.last_user
|
38
|
+
if user:
|
39
|
+
file = open(f'screens{divpath}{sname}', "r")
|
40
|
+
content = file.read()
|
41
|
+
if file_content[sname] == content:
|
42
|
+
return
|
43
|
+
file_content[sname] = content
|
44
|
+
|
45
|
+
global busy, request_file
|
46
|
+
busy = True
|
47
|
+
request_file = None
|
48
|
+
|
49
|
+
try:
|
50
|
+
module = user.load_screen(sname)
|
51
|
+
errors = check_screen(module)
|
52
|
+
if errors:
|
53
|
+
print('\n'.join(errors))
|
54
|
+
busy = False
|
55
|
+
return
|
56
|
+
print('Reloaded.')
|
57
|
+
except:
|
58
|
+
traceback.print_exc()
|
59
|
+
busy = False
|
60
|
+
return
|
61
|
+
|
62
|
+
for i, s in enumerate(user.screens):
|
63
|
+
if s.__file__ == module.__file__:
|
64
|
+
same = user.screen_module.__file__ == module.__file__
|
65
|
+
user.screens[i] = module
|
66
|
+
if same:
|
67
|
+
user.set_screen(module.screen.name)
|
68
|
+
break
|
69
|
+
else:
|
70
|
+
user.screens.append(module)
|
71
|
+
if len(user.screens) == 1:
|
72
|
+
user.set_screen(module.name)
|
73
|
+
|
74
|
+
user.update_menu()
|
75
|
+
user.set_clean()
|
76
|
+
user.sync_send(Redesign)
|
77
|
+
|
78
|
+
free()
|
79
|
+
return module
|
80
|
+
|
81
|
+
class ScreenEventHandler(PatternMatchingEventHandler):
|
82
|
+
def on_modified(self, event):
|
83
|
+
if not event.is_directory and User.last_user:
|
84
|
+
short_path = event.src_path[len(cwd) + 1:]
|
85
|
+
arr = short_path.split(divpath)
|
86
|
+
name = arr[-1]
|
87
|
+
dir = arr[0] if len(arr) > 1 else ''
|
88
|
+
|
89
|
+
if name.endswith('.py'):
|
90
|
+
user = User.last_user
|
91
|
+
|
92
|
+
if user.screen_module and dir not in ['screens','blocks']:
|
93
|
+
#analyze if dependency exist
|
94
|
+
file = open(user.screen_module.__file__, "r")
|
95
|
+
arr[-1] = arr[-1][:-3]
|
96
|
+
module_name = '.'.join(arr)
|
97
|
+
module_pattern = '\.'.join(arr)
|
98
|
+
|
99
|
+
if re.search(f"((import|from)[ \t]*{module_pattern}[ \t\n]*)",file.read()):
|
100
|
+
if module_name in sys.modules:
|
101
|
+
del sys.modules[module_name]
|
102
|
+
short_path = user.screen_module.__file__
|
103
|
+
if short_path.startswith(app_dir):
|
104
|
+
short_path = short_path[len(app_dir) + 1:]
|
105
|
+
dir, name = short_path.split(divpath)
|
106
|
+
|
107
|
+
if dir in ['screens','blocks']:
|
108
|
+
if busy:
|
109
|
+
global request_file
|
110
|
+
request_file = short_path
|
111
|
+
else:
|
112
|
+
fresh_module = reload(name) if dir == 'screens' else None
|
113
|
+
module = user.screen_module
|
114
|
+
if module:
|
115
|
+
current = module.__file__
|
116
|
+
if not fresh_module or current != fresh_module.__file__:
|
117
|
+
reload(current.split(divpath)[-1])
|
118
|
+
|
119
|
+
def on_deleted(self, event):
|
120
|
+
if not event.is_directory and User.last_user:
|
121
|
+
user = User.last_user
|
122
|
+
arr = event.src_path.split(divpath)
|
123
|
+
name = arr[-1]
|
124
|
+
dir = arr[-2]
|
125
|
+
if name.endswith('.py') and dir == 'screens':
|
126
|
+
delfile = f'{dir}{divpath}{name}'
|
127
|
+
for i, s in enumerate(user.screens):
|
128
|
+
if s.__file__ == event.src_path:
|
129
|
+
user.screens.remove(s)
|
130
|
+
if user.screen_module is s:
|
131
|
+
if user.screens:
|
132
|
+
fname = user.screens[0].__file__.split(divpath)[-1]
|
133
|
+
module = reload(fname)
|
134
|
+
user.set_screen(module.name)
|
135
|
+
user.update_menu()
|
136
|
+
user.sync_send(Redesign)
|
137
|
+
else:
|
138
|
+
user.sync_send(empty_app)
|
139
|
+
else:
|
140
|
+
reload(user.screen_module.__file__.split(divpath)[-1])
|
141
|
+
user.update_menu()
|
142
|
+
user.sync_send(Redesign)
|
143
|
+
break
|
144
|
+
|
145
|
+
event_handler = ScreenEventHandler()
|
146
|
+
observer = Observer()
|
147
|
+
path = os.getcwd()
|
148
|
+
observer.schedule(event_handler, path, recursive = True)
|
149
|
+
observer.start()
|
150
|
+
|
unisi/server.py
ADDED
@@ -0,0 +1,118 @@
|
|
1
|
+
from aiohttp import web, WSMsgType
|
2
|
+
from .users import *
|
3
|
+
from pathlib import Path
|
4
|
+
from .reloader import empty_app
|
5
|
+
from .autotest import recorder, jsonString, run_tests
|
6
|
+
from .common import *
|
7
|
+
from config import port, upload_dir
|
8
|
+
import traceback
|
9
|
+
|
10
|
+
async def post_handler(request):
|
11
|
+
reader = await request.multipart()
|
12
|
+
field = await reader.next()
|
13
|
+
filename = upload_path(field.filename)
|
14
|
+
size = 0
|
15
|
+
with open(filename, 'wb') as f:
|
16
|
+
while True:
|
17
|
+
chunk = await field.read_chunk()
|
18
|
+
if not chunk:
|
19
|
+
break
|
20
|
+
size += len(chunk)
|
21
|
+
f.write(chunk)
|
22
|
+
|
23
|
+
return web.Response(text=filename)
|
24
|
+
|
25
|
+
async def static_serve(request):
|
26
|
+
rpath = request.path
|
27
|
+
file_path = Path(f"{webpath}{rpath}" )
|
28
|
+
if request.path == '/':
|
29
|
+
file_path /= 'index.html'
|
30
|
+
|
31
|
+
if not file_path.exists():
|
32
|
+
file_path = None
|
33
|
+
#unmask win path
|
34
|
+
if rpath.startswith('/') and rpath[2] == ':':
|
35
|
+
rpath = rpath[1:]
|
36
|
+
dirs = getattr(config, public_dirs, [])
|
37
|
+
for dir in dirs:
|
38
|
+
if rpath.startswith(dir):
|
39
|
+
if os.path.exists(rpath):
|
40
|
+
file_path = Path(rpath)
|
41
|
+
break
|
42
|
+
|
43
|
+
return web.FileResponse(file_path) if file_path else web.HTTPNotFound()
|
44
|
+
|
45
|
+
def broadcast(message, message_user):
|
46
|
+
screen = message_user.screen_module
|
47
|
+
for user in User.reflections:
|
48
|
+
if user is not message_user and screen is user.screen_module:
|
49
|
+
user.sync_send(message)
|
50
|
+
|
51
|
+
async def websocket_handler(request):
|
52
|
+
ws = web.WebSocketResponse()
|
53
|
+
await ws.prepare(request)
|
54
|
+
user, ok = make_user()
|
55
|
+
user.transport = ws._writer.transport if divpath != '/' else None
|
56
|
+
|
57
|
+
async def send(res):
|
58
|
+
if type(res) != str:
|
59
|
+
res = jsonString(user.prepare_result(res))
|
60
|
+
await ws.send_str(res)
|
61
|
+
|
62
|
+
user.send = send
|
63
|
+
user.session = request.remote
|
64
|
+
await send(user.screen if ok else empty_app)
|
65
|
+
try:
|
66
|
+
async for msg in ws:
|
67
|
+
if msg.type == WSMsgType.TEXT:
|
68
|
+
if msg.data == 'close':
|
69
|
+
await ws.close()
|
70
|
+
else:
|
71
|
+
raw_message = json.loads(msg.data)
|
72
|
+
if isinstance(raw_message, list):
|
73
|
+
for raw_submessage in raw_message:
|
74
|
+
message = ReceivedMessage(raw_submessage)
|
75
|
+
result = user.result4message(message)
|
76
|
+
else:
|
77
|
+
message = None
|
78
|
+
result = Error('Empty command batch!')
|
79
|
+
else:
|
80
|
+
message = ReceivedMessage(raw_message)
|
81
|
+
result = user.result4message(message)
|
82
|
+
await send(result)
|
83
|
+
if message:
|
84
|
+
if recorder.record_file:
|
85
|
+
recorder.accept(message, result)
|
86
|
+
if config.mirror and not is_screen_switch(message):
|
87
|
+
if result:
|
88
|
+
broadcast(result, user)
|
89
|
+
msg_object = user.find_element(message)
|
90
|
+
if not isinstance(result, Message) or not result.contains(msg_object):
|
91
|
+
broadcast(jsonString(user.prepare_result(msg_object)), user)
|
92
|
+
elif msg.type == WSMsgType.ERROR:
|
93
|
+
user.log('ws connection closed with exception %s' % ws.exception())
|
94
|
+
except:
|
95
|
+
user.log(traceback.format_exc())
|
96
|
+
|
97
|
+
if User.reflections:
|
98
|
+
User.reflections.remove(user)
|
99
|
+
return ws
|
100
|
+
|
101
|
+
def start(appname = None, user_type = User, http_handlers = []):
|
102
|
+
if appname is not None:
|
103
|
+
config.appname = appname
|
104
|
+
|
105
|
+
User.UserType = user_type
|
106
|
+
|
107
|
+
if config.autotest:
|
108
|
+
run_tests()
|
109
|
+
|
110
|
+
http_handlers.insert(0, web.get('/ws', websocket_handler))
|
111
|
+
http_handlers += [web.static(f'/{config.upload_dir}', upload_dir),
|
112
|
+
web.get('/{tail:.*}', static_serve), web.post('/', post_handler)]
|
113
|
+
|
114
|
+
print(f'Start {appname} web server..')
|
115
|
+
app = web.Application()
|
116
|
+
app.add_routes(http_handlers)
|
117
|
+
web.run_app(app, port=port)
|
118
|
+
|
unisi/tables.py
ADDED
@@ -0,0 +1,116 @@
|
|
1
|
+
from .guielements import Gui
|
2
|
+
|
3
|
+
def accept_cell_value(table, val):
|
4
|
+
value, position = val
|
5
|
+
if not isinstance(value, bool):
|
6
|
+
try:
|
7
|
+
value = float(value)
|
8
|
+
except ValueError:
|
9
|
+
pass
|
10
|
+
table.rows[position[0]][position[1]] = value
|
11
|
+
|
12
|
+
def delete_table_row(table, value):
|
13
|
+
if table.rows:
|
14
|
+
keyed = len(table.headers) < len(table.rows[0])
|
15
|
+
table.value = value
|
16
|
+
if isinstance(value, list):
|
17
|
+
if keyed:
|
18
|
+
table.rows = [row for row in table.rows if row[-1] not in value]
|
19
|
+
else:
|
20
|
+
value.sort(reverse=True)
|
21
|
+
for v in value:
|
22
|
+
del table.rows[v]
|
23
|
+
table.value = []
|
24
|
+
else:
|
25
|
+
if keyed:
|
26
|
+
table.rows = [row for row in table.rows if row[-1] != value]
|
27
|
+
else:
|
28
|
+
del table.rows[value]
|
29
|
+
table.value = None
|
30
|
+
|
31
|
+
def append_table_row(table, value):
|
32
|
+
''' append has to return new row or error string, val is search string in the table'''
|
33
|
+
new_id_row, search = value #new_id_row == rows count
|
34
|
+
new_row = [''] * len(table.headers)
|
35
|
+
if search:
|
36
|
+
new_row[0] = search
|
37
|
+
table.rows.append(new_row)
|
38
|
+
return new_row
|
39
|
+
|
40
|
+
class Table(Gui):
|
41
|
+
def __init__(self, *args, panda = None, **kwargs):
|
42
|
+
if panda is not None:
|
43
|
+
self.mutate(PandaTable(*args, panda=panda, **kwargs))
|
44
|
+
else:
|
45
|
+
super().__init__(*args, **kwargs)
|
46
|
+
if not hasattr(self,'headers'):
|
47
|
+
self.headers = []
|
48
|
+
if not hasattr(self,'type'):
|
49
|
+
self.type = 'table'
|
50
|
+
if not hasattr(self,'value'):
|
51
|
+
self.value = None
|
52
|
+
if not hasattr(self,'rows'):
|
53
|
+
self.rows = []
|
54
|
+
if not hasattr(self,'dense'):
|
55
|
+
self.dense = True
|
56
|
+
|
57
|
+
if getattr(self,'edit', True):
|
58
|
+
if not hasattr(self,'delete'):
|
59
|
+
self.delete = delete_table_row
|
60
|
+
if not hasattr(self,'append'):
|
61
|
+
self.append = append_table_row
|
62
|
+
if not hasattr(self,'modify'):
|
63
|
+
self.modify = accept_cell_value
|
64
|
+
|
65
|
+
def selected_list(self):
|
66
|
+
return [self.value] if self.value != None else [] if type(self.value) == int else self.value
|
67
|
+
|
68
|
+
def clean(self):
|
69
|
+
self.rows = []
|
70
|
+
self.value = [] if isinstance(self.value,(tuple, list)) else None
|
71
|
+
return self
|
72
|
+
|
73
|
+
def delete_panda_row(table, row_num):
|
74
|
+
df = table.__panda__
|
75
|
+
if row_num < 0 or row_num >= len(df):
|
76
|
+
raise ValueError("Row number is out of range")
|
77
|
+
pt = table.__panda__
|
78
|
+
pt.drop(index = row_num, inplace=True)
|
79
|
+
pt.reset_index(inplace=True)
|
80
|
+
delete_table_row(table, row_num)
|
81
|
+
|
82
|
+
def accept_panda_cell(table, value_pos):
|
83
|
+
value, position = value_pos
|
84
|
+
row_num, col_num = position
|
85
|
+
table.__panda__.iloc[row_num,col_num] = value
|
86
|
+
accept_cell_value(table, value_pos)
|
87
|
+
|
88
|
+
def append_panda_row(table, row_num):
|
89
|
+
df = table.__panda__
|
90
|
+
new_row = append_table_row(table, row_num)
|
91
|
+
df.loc[len(df), df.columns] = new_row
|
92
|
+
return new_row
|
93
|
+
|
94
|
+
class PandaTable(Table):
|
95
|
+
""" panda = opened panda table"""
|
96
|
+
def __init__(self, *args, panda = None, fix_headers = True, **kwargs):
|
97
|
+
super().__init__(*args, **kwargs)
|
98
|
+
if panda is None:
|
99
|
+
raise Exception('PandaTable has to get panda = pandaTable as an argument.')
|
100
|
+
self.headers = panda.columns.tolist()
|
101
|
+
if fix_headers:
|
102
|
+
self.headers = [header.replace('_',' ') for header in self.headers]
|
103
|
+
self.rows = panda.values.tolist()
|
104
|
+
self.__panda__ = panda
|
105
|
+
|
106
|
+
if getattr(self,'edit', True):
|
107
|
+
if not hasattr(self,'delete'):
|
108
|
+
self.delete = delete_panda_row
|
109
|
+
if not hasattr(self,'append'):
|
110
|
+
self.append = append_panda_row
|
111
|
+
if not hasattr(self,'modify'):
|
112
|
+
self.modify = accept_panda_cell
|
113
|
+
@property
|
114
|
+
def panda(self):
|
115
|
+
return getattr(self,'__panda__',None)
|
116
|
+
|
unisi/users.py
ADDED
@@ -0,0 +1,271 @@
|
|
1
|
+
import importlib
|
2
|
+
from .utils import *
|
3
|
+
from .guielements import *
|
4
|
+
from .common import *
|
5
|
+
from .containers import Dialog, Screen
|
6
|
+
import sys
|
7
|
+
import asyncio
|
8
|
+
from threading import Thread
|
9
|
+
import logging
|
10
|
+
|
11
|
+
class User:
|
12
|
+
def __init__(self):
|
13
|
+
self.screens = []
|
14
|
+
self.active_dialog = None
|
15
|
+
self.screen_module = None
|
16
|
+
self.session = None
|
17
|
+
self.__handlers__ = {}
|
18
|
+
self.last_message = None
|
19
|
+
User.last_user = self
|
20
|
+
|
21
|
+
async def send_windows(self, obj):
|
22
|
+
await self.send(obj)
|
23
|
+
self.transport._write_fut = None
|
24
|
+
self.transport._loop._ready.pop()
|
25
|
+
|
26
|
+
def sync_send(self, obj):
|
27
|
+
asyncio.run_coroutine_threadsafe(self.send_windows(obj)
|
28
|
+
if self.transport else self.send(obj), self.extra_loop)
|
29
|
+
|
30
|
+
def progress(self, str, *updates):
|
31
|
+
"""open or update progress window if str != null else close it """
|
32
|
+
if not self.testing:
|
33
|
+
self.sync_send(TypeMessage('progress', str, *updates, user = self))
|
34
|
+
|
35
|
+
def load_screen(self, file):
|
36
|
+
screen_vars = {
|
37
|
+
'icon' : None,
|
38
|
+
'prepare' : None,
|
39
|
+
'blocks' : [],
|
40
|
+
'header' : config.appname,
|
41
|
+
'toolbar' : [],
|
42
|
+
'order' : 0,
|
43
|
+
'reload': config.hot_reload
|
44
|
+
}
|
45
|
+
name = file[:-3]
|
46
|
+
path = f'{screens_dir}{divpath}{file}'
|
47
|
+
spec = importlib.util.spec_from_file_location(name,path)
|
48
|
+
module = importlib.util.module_from_spec(spec)
|
49
|
+
|
50
|
+
module.user = self
|
51
|
+
|
52
|
+
spec.loader.exec_module(module)
|
53
|
+
screen = Screen(getattr(module, 'name', ''))
|
54
|
+
#set system vars
|
55
|
+
for var in screen_vars:
|
56
|
+
setattr(screen, var, getattr(module,var,screen_vars[var]))
|
57
|
+
|
58
|
+
if screen.toolbar:
|
59
|
+
screen.toolbar += User.toolbar
|
60
|
+
else:
|
61
|
+
screen.toolbar = User.toolbar
|
62
|
+
|
63
|
+
module.screen = screen
|
64
|
+
return module
|
65
|
+
|
66
|
+
def set_clean(self):
|
67
|
+
#remove user modules from sys
|
68
|
+
if os.path.exists(blocks_dir):
|
69
|
+
for file in os.listdir(blocks_dir):
|
70
|
+
if file.endswith(".py") and file != '__init__.py':
|
71
|
+
name = f'{blocks_dir}.{file[0:-3]}'
|
72
|
+
if name in sys.modules:
|
73
|
+
sys.modules[name].user = self
|
74
|
+
del sys.modules[name]
|
75
|
+
def load(self):
|
76
|
+
if os.path.exists(screens_dir):
|
77
|
+
for file in os.listdir(screens_dir):
|
78
|
+
if file.endswith(".py") and file != '__init__.py':
|
79
|
+
module = self.load_screen(file)
|
80
|
+
self.screens.append(module)
|
81
|
+
|
82
|
+
if self.screens:
|
83
|
+
self.screens.sort(key=lambda s: s.screen.order)
|
84
|
+
main = self.screens[0]
|
85
|
+
if 'prepare' in dir(main):
|
86
|
+
main.prepare()
|
87
|
+
self.screen_module = main
|
88
|
+
self.update_menu()
|
89
|
+
self.set_clean()
|
90
|
+
return True
|
91
|
+
|
92
|
+
def update_menu(self):
|
93
|
+
menu = [[getattr(s, 'name', ''),getattr(s,'icon', None)] for s in self.screens]
|
94
|
+
for s in self.screens:
|
95
|
+
s.screen.menu = menu
|
96
|
+
|
97
|
+
@property
|
98
|
+
def testing(self):
|
99
|
+
return self.session == 'autotest'
|
100
|
+
|
101
|
+
@property
|
102
|
+
def screen(self):
|
103
|
+
return self.screen_module.screen
|
104
|
+
|
105
|
+
def set_screen(self,name):
|
106
|
+
return self.process(ArgObject(block = 'root', element = None, value = name))
|
107
|
+
|
108
|
+
def result4message(self, message):
|
109
|
+
result = None
|
110
|
+
dialog = self.active_dialog
|
111
|
+
if dialog:
|
112
|
+
if message.element is None: #button pressed
|
113
|
+
self.active_dialog = None
|
114
|
+
result = dialog.changed(dialog, message.value)
|
115
|
+
else:
|
116
|
+
el = self.find_element(message)
|
117
|
+
if el:
|
118
|
+
result = self.process_element(el, message)
|
119
|
+
else:
|
120
|
+
result = self.process(message)
|
121
|
+
if result and isinstance(result, Dialog):
|
122
|
+
self.active_dialog = result
|
123
|
+
return result
|
124
|
+
|
125
|
+
@property
|
126
|
+
def blocks(self):
|
127
|
+
return [self.active_dialog] if self.active_dialog and \
|
128
|
+
self.active_dialog.value else self.screen.blocks
|
129
|
+
|
130
|
+
def find_element(self, message):
|
131
|
+
blname = message.block
|
132
|
+
elname = message.element
|
133
|
+
if blname == 'toolbar':
|
134
|
+
for e in self.screen.toolbar:
|
135
|
+
if e.name == elname:
|
136
|
+
return e
|
137
|
+
else:
|
138
|
+
for bl in flatten(self.blocks):
|
139
|
+
if bl.name == blname:
|
140
|
+
for c in bl.value:
|
141
|
+
if isinstance(c, list):
|
142
|
+
for sub in c:
|
143
|
+
if sub.name == elname:
|
144
|
+
return sub
|
145
|
+
elif c.name == elname:
|
146
|
+
return c
|
147
|
+
|
148
|
+
def find_path(self, elem):
|
149
|
+
for bl in flatten(self.blocks):
|
150
|
+
if bl == elem:
|
151
|
+
return [bl.name]
|
152
|
+
for c in bl.value:
|
153
|
+
if isinstance(c, list):
|
154
|
+
for sub in c:
|
155
|
+
if sub == elem:
|
156
|
+
return [bl.name, sub.name]
|
157
|
+
elif c == elem:
|
158
|
+
return [bl.name, c.name]
|
159
|
+
for e in self.screen.toolbar:
|
160
|
+
if e == elem:
|
161
|
+
return ['toolbar', e.name]
|
162
|
+
|
163
|
+
def prepare_result(self, raw):
|
164
|
+
if raw == UpdateScreen:
|
165
|
+
raw = self.screen
|
166
|
+
raw.reload = False
|
167
|
+
elif raw == Redesign:
|
168
|
+
raw = self.screen
|
169
|
+
raw.reload = True
|
170
|
+
else:
|
171
|
+
if isinstance(raw, Message):
|
172
|
+
raw.fill_paths4(self)
|
173
|
+
elif isinstance(raw,Gui):
|
174
|
+
raw = Message(raw, user = self)
|
175
|
+
elif isinstance(raw, (list, tuple)):
|
176
|
+
raw = Message(*raw, user = self)
|
177
|
+
return raw
|
178
|
+
|
179
|
+
def process(self, message):
|
180
|
+
self.last_message = message
|
181
|
+
screen_change_message = message.screen and self.screen.name != message.screen
|
182
|
+
if is_screen_switch(message) or screen_change_message:
|
183
|
+
for s in self.screens:
|
184
|
+
if s.name == message.value:
|
185
|
+
self.screen_module = s
|
186
|
+
if screen_change_message:
|
187
|
+
break
|
188
|
+
if getattr(s.screen,'prepare', False):
|
189
|
+
s.screen.prepare()
|
190
|
+
return True
|
191
|
+
else:
|
192
|
+
error = f'Unknown screen name: {message.value}'
|
193
|
+
self.log(error)
|
194
|
+
return Error(error)
|
195
|
+
|
196
|
+
elem = self.find_element(message)
|
197
|
+
if elem:
|
198
|
+
return self.process_element(elem, message)
|
199
|
+
|
200
|
+
error = f'Element {message.block}>>{message.element} does not exists!'
|
201
|
+
self.log(error)
|
202
|
+
return Error(error)
|
203
|
+
|
204
|
+
def process_element(self, elem, message):
|
205
|
+
event = message.event
|
206
|
+
query = event in ['complete', 'append']
|
207
|
+
|
208
|
+
handler = self.__handlers__.get((elem, event), None)
|
209
|
+
if handler:
|
210
|
+
result = handler(elem, message.value)
|
211
|
+
return result
|
212
|
+
|
213
|
+
handler = getattr(elem, event, False)
|
214
|
+
if handler:
|
215
|
+
result = handler(elem, message.value)
|
216
|
+
if query:
|
217
|
+
result = Answer(event, message, result)
|
218
|
+
return result
|
219
|
+
elif event == 'changed':
|
220
|
+
elem.value = message.value
|
221
|
+
else:
|
222
|
+
self.log(f'{elem} does not contain method for {event} event type!')
|
223
|
+
return Error(f'Invalid {event} event type for {message.block}>>{message.element} is received!')
|
224
|
+
|
225
|
+
def reflect(self):
|
226
|
+
user = User.UserType()
|
227
|
+
user.screens = self.screens
|
228
|
+
if self.screens:
|
229
|
+
user.screen_module = self.screens[0]
|
230
|
+
user.__handlers__ = self.__handlers__
|
231
|
+
return user
|
232
|
+
|
233
|
+
def log(self, str, type = 'error'):
|
234
|
+
scr = self.screen.name if self.screens else 'omitted'
|
235
|
+
str = f"session: {self.session}, screen: {scr}, message: {self.last_message} \n {str}"
|
236
|
+
if type == 'error':
|
237
|
+
logging.error(str)
|
238
|
+
else:
|
239
|
+
logging.warning(str)
|
240
|
+
|
241
|
+
def make_user():
|
242
|
+
if config.mirror and User.last_user:
|
243
|
+
user = User.last_user.reflect()
|
244
|
+
ok = user.screens
|
245
|
+
else:
|
246
|
+
user = User.UserType()
|
247
|
+
ok = user.load()
|
248
|
+
if config.mirror:
|
249
|
+
User.reflections.append(user)
|
250
|
+
return user, ok
|
251
|
+
|
252
|
+
#loop and thread is for progress window and sync interactions
|
253
|
+
loop = asyncio.new_event_loop()
|
254
|
+
|
255
|
+
def f(loop):
|
256
|
+
asyncio.set_event_loop(loop)
|
257
|
+
loop.run_forever()
|
258
|
+
|
259
|
+
async_thread = Thread(target=f, args=(loop,))
|
260
|
+
async_thread.start()
|
261
|
+
|
262
|
+
def handle(elem, event):
|
263
|
+
def h(fn):
|
264
|
+
User.last_user.__handlers__[elem, event] = fn
|
265
|
+
return h
|
266
|
+
|
267
|
+
User.extra_loop = loop
|
268
|
+
User.UserType = User
|
269
|
+
User.last_user = None
|
270
|
+
User.toolbar = []
|
271
|
+
User.reflections = []
|