mirror of
https://github.com/iperov/DeepFaceLive
synced 2025-08-21 05:53:25 -07:00
code release
This commit is contained in:
parent
b941ba41a3
commit
a902f11f74
354 changed files with 826570 additions and 1 deletions
499
xlib/mp/csw/CSWBase.py
Normal file
499
xlib/mp/csw/CSWBase.py
Normal file
|
@ -0,0 +1,499 @@
|
|||
import multiprocessing
|
||||
import threading
|
||||
import time
|
||||
import traceback
|
||||
from enum import IntEnum
|
||||
|
||||
from xlib import db as lib_db
|
||||
from xlib.python import Disposable, EventListener
|
||||
|
||||
from ..PMPI import PMPI
|
||||
|
||||
|
||||
class Control:
|
||||
"""
|
||||
Base class of control elements between 2 processes.
|
||||
"""
|
||||
class State(IntEnum):
|
||||
DISABLED = 0 # the control is not available and unusable (default)
|
||||
FREEZED = 1 # the control is available, but temporary unusable
|
||||
ENABLED = 2 # the control is available and usable
|
||||
|
||||
|
||||
def __init__(self):
|
||||
self._name = None
|
||||
self._pmpi = None
|
||||
self._pmpi_gather_call_on_msgs = []
|
||||
self._pmpi_gather_send_msgs = []
|
||||
self._state = Control.State.DISABLED
|
||||
self._state_change_evl = EventListener()
|
||||
self._call_on_msg('_state', lambda state: self._set_state(state) )
|
||||
|
||||
##########
|
||||
### PMPI
|
||||
def _call_on_msg(self, name, func):
|
||||
if self._pmpi is None:
|
||||
self._pmpi_gather_call_on_msgs.append( (name,func) )
|
||||
else:
|
||||
self._pmpi.call_on_msg(f'__{self._name}_{name}__', func)
|
||||
|
||||
def _send_msg(self, name, *args, **kwargs):
|
||||
if self._pmpi is None:
|
||||
self._pmpi_gather_send_msgs.append( (name,args,kwargs) )
|
||||
else:
|
||||
self._pmpi.send_msg(f'__{self._name}_{name}__', *args, **kwargs)
|
||||
|
||||
|
||||
def _set_pmpi(self, pmpi):
|
||||
self._pmpi = pmpi
|
||||
for name, func in self._pmpi_gather_call_on_msgs:
|
||||
self._call_on_msg(name, func)
|
||||
self._pmpi_gather_call_on_msgs = []
|
||||
|
||||
for name, args, kwargs in self._pmpi_gather_send_msgs:
|
||||
self._send_msg(name, *args, **kwargs)
|
||||
self._pmpi_gather_send_msgs = []
|
||||
|
||||
##########
|
||||
### STATE
|
||||
|
||||
def _set_state(self, state : 'Control.State'):
|
||||
if not isinstance(state, Control.State):
|
||||
raise ValueError('state must be an instance of Control.State')
|
||||
if self._state != state:
|
||||
self._state = state
|
||||
self._state_change_evl.call(state)
|
||||
return True
|
||||
return False
|
||||
|
||||
def call_on_change_state(self, func_or_list):
|
||||
"""Call when the state of the control is changed"""
|
||||
self._state_change_evl.add(func_or_list)
|
||||
|
||||
def call_on_control_info(self, func_or_list):
|
||||
"""Call when the state of the control is changed"""
|
||||
self._control_info_evl.add(func_or_list)
|
||||
|
||||
def get_state(self) -> 'Control.State': return self._state
|
||||
def is_disabled(self): return self._state == Control.State.DISABLED
|
||||
def is_freezed(self): return self._state == Control.State.FREEZED
|
||||
def is_enabled(self): return self._state == Control.State.ENABLED
|
||||
|
||||
|
||||
class ControlHost(Control):
|
||||
|
||||
def __init__(self):
|
||||
Control.__init__(self)
|
||||
self._send_msg('_reset')
|
||||
|
||||
def _set_state(self, state : 'Control.State'):
|
||||
result = super()._set_state(state)
|
||||
if result:
|
||||
self._send_msg('_state', self._state)
|
||||
return result
|
||||
|
||||
def disable(self): self._set_state(Control.State.DISABLED)
|
||||
def freeze(self): self._set_state(Control.State.FREEZED)
|
||||
def enable(self): self._set_state(Control.State.ENABLED)
|
||||
|
||||
class ControlClient(Control):
|
||||
def __init__(self):
|
||||
Control.__init__(self)
|
||||
|
||||
self._call_on_msg('_reset', self._reset)
|
||||
|
||||
def _reset(self):
|
||||
self._set_state(Control.State.DISABLED)
|
||||
self._on_reset()
|
||||
|
||||
def _on_reset(self):
|
||||
"""Implement when the Control is resetted to initial state,
|
||||
the same state like after __init__()
|
||||
"""
|
||||
raise NotImplementedError(f'You should implement {self.__class__} _on_reset')
|
||||
|
||||
|
||||
class SheetBase:
|
||||
def __init__(self):
|
||||
self._controls = []
|
||||
|
||||
def __setattr__(self, var_name, obj):
|
||||
super().__setattr__(var_name, obj)
|
||||
|
||||
if isinstance(obj, Control):
|
||||
if obj in self._controls:
|
||||
raise ValueError(f'Control with name {var_name} already in Sheet')
|
||||
self._controls.append(obj)
|
||||
obj._name = var_name
|
||||
|
||||
class Sheet:
|
||||
"""
|
||||
base sheet to control CSW
|
||||
"""
|
||||
class Host(SheetBase):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
class Worker(SheetBase):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
class WorkerState:
|
||||
def __getstate__(self):
|
||||
return self.__dict__.copy()
|
||||
|
||||
def __setstate__(self, d):
|
||||
self.__init__()
|
||||
self.__dict__.update(d)
|
||||
|
||||
class DB(lib_db.KeyValueDB):
|
||||
...
|
||||
|
||||
class Base(Disposable):
|
||||
"""
|
||||
base class for Controllable Subprocess Worker (CSW)
|
||||
"""
|
||||
def __init__(self, sheet):
|
||||
super().__init__()
|
||||
self._pmpi = PMPI()
|
||||
|
||||
if not isinstance(sheet, SheetBase):
|
||||
raise ValueError('sheet must be an instance of SheetBase')
|
||||
self._sheet = sheet
|
||||
for control in sheet._controls:
|
||||
control._set_pmpi(self._pmpi)
|
||||
|
||||
def get_control_sheet(self): return self._sheet
|
||||
def _get_name(self): return self.__class__.__name__
|
||||
def _get_pmpi(self) -> PMPI: return self._pmpi
|
||||
|
||||
class Host(Base):
|
||||
"""
|
||||
Base host class for CSW.
|
||||
|
||||
|
||||
"""
|
||||
|
||||
class _ProcessStatus:
|
||||
STOPPING = 0
|
||||
STOPPED = 1
|
||||
STARTING = 2
|
||||
STARTED = 3
|
||||
|
||||
|
||||
def __init__(self, db : lib_db.KeyValueDB = None,
|
||||
sheet_cls = None,
|
||||
worker_cls = None,
|
||||
worker_state_cls : WorkerState = None,
|
||||
worker_start_args = None,
|
||||
worker_start_kwargs = None,
|
||||
):
|
||||
sheet_host_cls = getattr(sheet_cls, 'Host', None)
|
||||
sheet_worker_cls = getattr(sheet_cls, 'Worker', None)
|
||||
|
||||
if sheet_host_cls is None or not issubclass(sheet_host_cls, Sheet.Host):
|
||||
raise ValueError('sheet_cls.Host must be an instance Sheet.Host')
|
||||
if sheet_worker_cls is None or not issubclass(sheet_worker_cls, Sheet.Worker):
|
||||
raise ValueError('sheet_cls.Worker must be an instance Sheet.Worker')
|
||||
if not issubclass(worker_cls, Worker):
|
||||
raise ValueError("worker_cls must be subclass of Worker")
|
||||
if worker_state_cls is None:
|
||||
worker_state_cls = WorkerState
|
||||
if not issubclass(worker_state_cls, WorkerState):
|
||||
raise ValueError("worker_state_cls must be subclass of WorkerState")
|
||||
if worker_start_args is None:
|
||||
worker_start_args = []
|
||||
if worker_start_kwargs is None:
|
||||
worker_start_kwargs = {}
|
||||
if db is None:
|
||||
db = DB()
|
||||
if not isinstance(db, DB ):
|
||||
raise ValueError("db must be subclass of DB")
|
||||
|
||||
super().__init__(sheet=sheet_host_cls())
|
||||
|
||||
self._worker_cls = worker_cls
|
||||
self._worker_sheet_cls = sheet_worker_cls
|
||||
self._worker_start_args = worker_start_args
|
||||
self._worker_start_kwargs = worker_start_kwargs
|
||||
self._worker_state_cls = worker_state_cls
|
||||
self._db = db
|
||||
|
||||
self._db_key_host_onoff = f'{self._get_name()}_host_onoff'
|
||||
self._db_key_worker_state = f'{self._get_name()}_worker_state'
|
||||
state = None
|
||||
if db is not None:
|
||||
# Try to load the WorkerState
|
||||
state = db.get_value (self._db_key_worker_state)
|
||||
if state is None:
|
||||
# still None - create new
|
||||
state = self._worker_state_cls()
|
||||
self._state = state
|
||||
|
||||
self._process_status = Host._ProcessStatus.STOPPED
|
||||
self._is_busy = False
|
||||
self._process = None
|
||||
self._reset_restart = False
|
||||
|
||||
self._on_state_change_evl = EventListener()
|
||||
|
||||
self.call_on_msg('_start', self._on_worker_start)
|
||||
self.call_on_msg('_stop', self._on_worker_stop )
|
||||
self.call_on_msg('_state', self._on_worker_state)
|
||||
self.call_on_msg('_busy', self._on_worker_busy)
|
||||
|
||||
def _on_dispose(self):
|
||||
self.stop()
|
||||
while self._process_status != Host._ProcessStatus.STOPPED:
|
||||
self.process_messages()
|
||||
|
||||
super()._on_dispose()
|
||||
|
||||
def call_on_msg(self, name, func): self._pmpi.call_on_msg(name, func)
|
||||
|
||||
def call_on_state_change(self, func_or_list):
|
||||
"""
|
||||
|
||||
func_or_list callable(csw, started, starting, stopping, stopped, busy)
|
||||
"""
|
||||
self._on_state_change_evl.add(func_or_list)
|
||||
|
||||
def _on_state_change_evl_call(self):
|
||||
self._on_state_change_evl.call(self, self.is_started(), self.is_starting(), self.is_stopping(), self.is_stopped(), self.is_busy() )
|
||||
|
||||
|
||||
def send_msg(self, name, *args, **kwargs): self._pmpi.send_msg(name, *args, **kwargs)
|
||||
|
||||
def reset_state(self):
|
||||
"""
|
||||
reset state to default
|
||||
"""
|
||||
if self.is_stopped():
|
||||
self._state = self._worker_state_cls()
|
||||
self._save_state()
|
||||
else:
|
||||
self._reset_restart = True
|
||||
self.stop()
|
||||
|
||||
def save_on_off_state(self):
|
||||
"""
|
||||
save current start/stop state to DB
|
||||
"""
|
||||
if self._process_status == Host._ProcessStatus.STARTED or \
|
||||
self._process_status == Host._ProcessStatus.STOPPED:
|
||||
# Save only when the process is fully started / stopped
|
||||
self._db.set_value(self._db_key_host_onoff, self._process_status == Host._ProcessStatus.STARTED )
|
||||
|
||||
def restore_on_off_state(self):
|
||||
"""
|
||||
restore saved on_off state from db. Default is on.
|
||||
"""
|
||||
is_on = self._db.get_value(self._db_key_host_onoff, True)
|
||||
if is_on:
|
||||
self.start()
|
||||
|
||||
def start(self):
|
||||
"""
|
||||
Start the worker.
|
||||
**kwargs will be passed to Worker.on_start(**kwargs)
|
||||
|
||||
returns True if operation is successfully initiated.
|
||||
"""
|
||||
if self._process_status != Host._ProcessStatus.STARTED:
|
||||
|
||||
if self._process_status == Host._ProcessStatus.STOPPED:
|
||||
pipe, worker_pipe = multiprocessing.Pipe()
|
||||
self._pmpi.set_pipe(pipe)
|
||||
|
||||
self._process_status = Host._ProcessStatus.STARTING
|
||||
self._on_state_change_evl_call()
|
||||
|
||||
process = self._process = multiprocessing.Process(target=Worker._start_proc,
|
||||
args=[self._worker_cls, self._worker_sheet_cls, worker_pipe, self._state, self._worker_start_args, self._worker_start_kwargs],
|
||||
daemon=True)
|
||||
|
||||
# Start non-blocking in subthread
|
||||
threading.Thread(target=lambda: self._process.start(), daemon=True).start()
|
||||
time.sleep(0.016) # BUG ? remove will raise ImportError: cannot import name 'Popen' tested in Python 3.6
|
||||
return True
|
||||
return False
|
||||
|
||||
def stop(self, force=False):
|
||||
"""
|
||||
Stop the module
|
||||
|
||||
arguments:
|
||||
|
||||
force(False) bool False: gracefully stop the module(deferred)
|
||||
True: force terminate(right now)
|
||||
|
||||
returns True if operation is successfully initiated.
|
||||
|
||||
WARNING !
|
||||
|
||||
Do not kill the process, if it is using any multiprocessing syncronization primivites,
|
||||
because if process is killed while any sync is acquired, it will not be released.
|
||||
"""
|
||||
|
||||
if self._process_status != Host._ProcessStatus.STOPPED:
|
||||
|
||||
if force or self._process_status == Host._ProcessStatus.STARTED:
|
||||
if not force:
|
||||
self.send_msg('_stop')
|
||||
self._process_status = Host._ProcessStatus.STOPPING
|
||||
self._on_state_change_evl_call()
|
||||
else:
|
||||
self._process.terminate()
|
||||
self._process.join()
|
||||
self._process = None
|
||||
self._pmpi.set_pipe(None)
|
||||
|
||||
# Reset client controls
|
||||
for control in self.get_control_sheet()._controls:
|
||||
if isinstance(control, ControlClient):
|
||||
control._reset()
|
||||
|
||||
# Process is physically stopped
|
||||
self._process_status = Host._ProcessStatus.STOPPED
|
||||
self._is_busy = False
|
||||
#print(f'{self._get_name()} is stopped.')
|
||||
self._on_state_change_evl_call()
|
||||
|
||||
return True
|
||||
return False
|
||||
|
||||
def is_started(self): return self._process_status == Host._ProcessStatus.STARTED
|
||||
def is_starting(self): return self._process_status == Host._ProcessStatus.STARTING
|
||||
def is_stopped(self): return self._process_status == Host._ProcessStatus.STOPPED
|
||||
def is_stopping(self): return self._process_status == Host._ProcessStatus.STOPPING
|
||||
def is_busy(self): return self._is_busy
|
||||
|
||||
def _save_state(self):
|
||||
self._db.set_value( self._db_key_worker_state, self._state)
|
||||
|
||||
def _on_worker_start(self):
|
||||
self._process_status = Host._ProcessStatus.STARTED
|
||||
#print(f'{self._get_name()} is started.')
|
||||
self._on_state_change_evl_call()
|
||||
|
||||
def _on_worker_stop(self, error : str = None, restart : bool = False):
|
||||
if error is not None:
|
||||
print(f'{self._get_name()} error: {error}')
|
||||
# Stop on error: reset state
|
||||
self._state = self._worker_state_cls()
|
||||
self.stop(force=True)
|
||||
|
||||
if self._reset_restart:
|
||||
self._reset_restart = False
|
||||
self._state = self._worker_state_cls()
|
||||
restart = True
|
||||
|
||||
if restart:
|
||||
self.start()
|
||||
|
||||
def _on_worker_state(self, state):
|
||||
self._state = state
|
||||
self._save_state()
|
||||
|
||||
def _on_worker_busy(self, is_busy):
|
||||
self._is_busy = is_busy
|
||||
self._on_state_change_evl_call()
|
||||
|
||||
def process_messages(self):
|
||||
self._pmpi.process_messages()
|
||||
|
||||
if self._process_status == Host._ProcessStatus.STARTED:
|
||||
if not self._process.is_alive():
|
||||
self.stop(force=True)
|
||||
|
||||
class Worker(Base):
|
||||
"""
|
||||
Base Worker class for CSW.
|
||||
"""
|
||||
|
||||
def __init__(self, sheet):
|
||||
super().__init__(sheet=sheet)
|
||||
self._started = False
|
||||
self._run = True
|
||||
self._req_restart = False
|
||||
self._req_save_state = False
|
||||
self._get_pmpi().call_on_msg('_stop', lambda: setattr(self, '_run', False))
|
||||
|
||||
def on_start(self, *args, **kwargs):
|
||||
"""overridable"""
|
||||
def on_tick(self):
|
||||
"""
|
||||
overridable
|
||||
do a sleep inside your implementation
|
||||
"""
|
||||
|
||||
def on_stop(self):
|
||||
"""overridable"""
|
||||
|
||||
def send_msg(self, name, *args, **kwargs): self._pmpi.send_msg(name, *args, **kwargs)
|
||||
def call_on_msg(self, name, func): self._pmpi.call_on_msg(name, func)
|
||||
def restart(self):
|
||||
"""request to restart Worker"""
|
||||
self._req_restart = True
|
||||
self._run = False
|
||||
|
||||
def get_state(self) -> WorkerState:
|
||||
"""
|
||||
get WorkerState object of Worker.
|
||||
Inner variables can be modified.
|
||||
Call save_state to save the WorkerState.
|
||||
"""
|
||||
return self._state
|
||||
|
||||
def save_state(self):
|
||||
"""Request to save current state"""
|
||||
self._req_save_state = True
|
||||
|
||||
def set_busy(self, is_busy : bool):
|
||||
"""
|
||||
indicate to host that worker is in busy mode now
|
||||
"""
|
||||
self.send_msg('_busy', is_busy)
|
||||
|
||||
def is_started(self) -> bool:
|
||||
"""
|
||||
returns True after on_start()
|
||||
"""
|
||||
return self._started
|
||||
|
||||
@staticmethod
|
||||
def _start_proc(cls_, sheet_cls, pipe, state, worker_start_args, worker_start_kwargs):
|
||||
self = cls_(sheet=sheet_cls())
|
||||
self._get_pmpi().set_pipe(pipe)
|
||||
self._state = state
|
||||
|
||||
error = None
|
||||
try:
|
||||
self.on_start(*worker_start_args, **worker_start_kwargs)
|
||||
self._started = True
|
||||
self.send_msg('_start')
|
||||
while True:
|
||||
if self._req_save_state:
|
||||
self._req_save_state = False
|
||||
self.send_msg('_state', self._state)
|
||||
|
||||
if not self._run:
|
||||
break
|
||||
|
||||
self._pmpi.process_messages()
|
||||
self.on_tick()
|
||||
|
||||
self.on_stop()
|
||||
|
||||
except Exception as e:
|
||||
error = f'{str(e)} {traceback.format_exc()}'
|
||||
|
||||
self.send_msg('_stop', error=error, restart=self._req_restart)
|
||||
time.sleep(1.0)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
172
xlib/mp/csw/DynamicSingleSwitch.py
Normal file
172
xlib/mp/csw/DynamicSingleSwitch.py
Normal file
|
@ -0,0 +1,172 @@
|
|||
from collections import Iterable
|
||||
from typing import List, Union
|
||||
|
||||
from xlib.python import EventListener
|
||||
|
||||
from .CSWBase import ControlClient, ControlHost
|
||||
|
||||
|
||||
class _DynamicSingleSwitchBase:
|
||||
def __init__(self):
|
||||
self._on_selected_evl = EventListener()
|
||||
self._on_choices_evl = EventListener()
|
||||
|
||||
self._call_on_msg('selected_idx', self._on_msg_selected)
|
||||
self._call_on_msg('choices', self._on_msg_choices)
|
||||
|
||||
self._selected_idx = None
|
||||
|
||||
self._choices = None
|
||||
self._choices_len = None
|
||||
self._choices_names = None
|
||||
self._none_choice_name = None
|
||||
|
||||
def _on_msg_selected(self, selected_idx):
|
||||
self._set_selected_idx(selected_idx)
|
||||
|
||||
def _send_selected_idx(self):
|
||||
self._send_msg('selected_idx', self.get_selected_idx() )
|
||||
|
||||
def _set_selected_idx(self, selected_idx):
|
||||
if self._selected_idx != selected_idx:
|
||||
self._selected_idx = selected_idx
|
||||
self._on_selected_evl.call(selected_idx, self.get_selected_choice() )
|
||||
return True
|
||||
return False
|
||||
|
||||
def _send_choices(self):
|
||||
self._send_msg('choices', self._choices, self._choices_names, self._none_choice_name)
|
||||
|
||||
def _set_choices(self, choices, choices_names : List[str], none_choice_name : Union[str,None]):
|
||||
self._choices = choices
|
||||
self._choices_len = len(choices)
|
||||
self._choices_names = choices_names
|
||||
self._none_choice_name = none_choice_name
|
||||
self._on_choices_evl.call(choices, choices_names, none_choice_name)
|
||||
|
||||
def _on_msg_choices(self, choices, choices_names, none_choice_name):
|
||||
self._set_choices(choices, choices_names, none_choice_name)
|
||||
|
||||
def _choice_to_index(self, idx_or_choice):
|
||||
choices = self._choices
|
||||
if idx_or_choice.__class__ != int:
|
||||
try:
|
||||
idx_or_choice = choices.index(idx_or_choice)
|
||||
except:
|
||||
# Choice not in list
|
||||
return None
|
||||
if idx_or_choice < 0 or idx_or_choice >= self._choices_len:
|
||||
# idx out of bounds
|
||||
return None
|
||||
return idx_or_choice
|
||||
|
||||
def call_on_choices(self, func_or_list):
|
||||
"""call when choices list is configured"""
|
||||
self._on_choices_evl.add(func_or_list)
|
||||
|
||||
def call_on_selected(self, func):
|
||||
"""
|
||||
called when selected
|
||||
func ( idx : int, choice : object)
|
||||
"""
|
||||
self._on_selected_evl.add(func)
|
||||
|
||||
def in_choices(self, choice) -> bool: return choice in self._choices
|
||||
|
||||
def get_choices(self): return self._choices
|
||||
def get_choices_names(self) -> List[str]: return self._choices_names
|
||||
|
||||
def get_selected_idx(self) -> Union[int, None]: return self._selected_idx
|
||||
def get_selected_choice(self):
|
||||
if self._selected_idx is None:
|
||||
return None
|
||||
return self._choices[self._selected_idx]
|
||||
|
||||
def select(self, idx_or_choice) -> bool:
|
||||
"""
|
||||
Select index or choice or None(unselect)
|
||||
|
||||
returns False if the value is not correct or already selected
|
||||
returns True if operation is success
|
||||
|
||||
func does not raise any exceptions
|
||||
"""
|
||||
if idx_or_choice is not None:
|
||||
idx_or_choice = self._choice_to_index (idx_or_choice)
|
||||
if idx_or_choice is None:
|
||||
return False
|
||||
|
||||
result = self._set_selected_idx(idx_or_choice)
|
||||
if result:
|
||||
self._send_selected_idx()
|
||||
return result
|
||||
|
||||
def unselect(self) -> bool:
|
||||
"""
|
||||
unselect
|
||||
returns True if operation is success
|
||||
"""
|
||||
return self.select(None)
|
||||
|
||||
|
||||
class DynamicSingleSwitch:
|
||||
"""
|
||||
DynamicSingleSwitch control dynamically loaded list of choices.
|
||||
Has None state as unselected.
|
||||
|
||||
|
||||
"""
|
||||
class Host(ControlHost, _DynamicSingleSwitchBase):
|
||||
def __init__(self):
|
||||
ControlHost.__init__(self)
|
||||
_DynamicSingleSwitchBase.__init__(self)
|
||||
|
||||
def _on_msg_selected(self, selected_idx):
|
||||
if self.is_enabled():
|
||||
_DynamicSingleSwitchBase._on_msg_selected(self, selected_idx)
|
||||
self._send_selected_idx()
|
||||
|
||||
def set_choices(self, choices, choices_names=None, none_choice_name=None):
|
||||
"""
|
||||
set choices, and optional choices_names.
|
||||
|
||||
choices_names list/dict/None if list, should match the len of choices
|
||||
if dict, should return a str by key of choice
|
||||
if None, choices will be stringfied
|
||||
|
||||
none_choice_name('') str/None if not None, shows None choice with name,
|
||||
by default empty string
|
||||
"""
|
||||
# Validate choices
|
||||
if choices is None:
|
||||
raise ValueError('Choices cannot be None.')
|
||||
if not isinstance(choices, Iterable):
|
||||
raise ValueError('Choices must be Iterable')
|
||||
|
||||
if choices_names is None:
|
||||
choices_names = tuple(str(c) for c in choices)
|
||||
elif isinstance(choices_names, (list,tuple)):
|
||||
if len(choices_names) != len(choices):
|
||||
raise ValueError('mismatch len of choices and choices names')
|
||||
elif isinstance(choices_names, dict):
|
||||
choices_names = [ choices_names[x] for x in choices ]
|
||||
else:
|
||||
raise ValueError('unsupported type of choices_names')
|
||||
|
||||
if not all( isinstance(x, str) for x in choices_names ):
|
||||
raise ValueError('all values in choices_names must be a str')
|
||||
|
||||
choices = tuple(choices)
|
||||
|
||||
self._set_choices(choices, choices_names, none_choice_name)
|
||||
self._send_choices()
|
||||
|
||||
class Client(ControlClient, _DynamicSingleSwitchBase):
|
||||
def __init__(self):
|
||||
ControlClient.__init__(self)
|
||||
_DynamicSingleSwitchBase.__init__(self)
|
||||
|
||||
def _on_reset(self):
|
||||
self._set_selected_idx(None)
|
||||
|
||||
|
53
xlib/mp/csw/Error.py
Normal file
53
xlib/mp/csw/Error.py
Normal file
|
@ -0,0 +1,53 @@
|
|||
from typing import Union
|
||||
|
||||
from xlib.python import EventListener
|
||||
|
||||
from .CSWBase import ControlClient, ControlHost
|
||||
|
||||
|
||||
class Error:
|
||||
"""
|
||||
One-way error control.
|
||||
|
||||
"""
|
||||
|
||||
class Client(ControlClient):
|
||||
def __init__(self):
|
||||
ControlClient.__init__(self)
|
||||
|
||||
self._on_error_evl = EventListener()
|
||||
self._call_on_msg('error', self._on_msg_error)
|
||||
|
||||
def _on_msg_error(self, text):
|
||||
self._on_error_evl.call(text)
|
||||
|
||||
def call_on_error(self, func_or_list):
|
||||
"""
|
||||
Call when the error message arrive
|
||||
|
||||
func(text : Union[str,None])
|
||||
"""
|
||||
self._on_error_evl.add(func_or_list)
|
||||
|
||||
def _on_reset(self):
|
||||
self._on_msg_error(None)
|
||||
|
||||
|
||||
class Host(ControlHost):
|
||||
def __init__(self):
|
||||
ControlHost.__init__(self)
|
||||
|
||||
|
||||
def set_error(self, text : Union[str, None]):
|
||||
"""
|
||||
set tex
|
||||
|
||||
text str or None
|
||||
"""
|
||||
if text is None:
|
||||
self.disable()
|
||||
else:
|
||||
self.enable()
|
||||
self._send_msg('error', text)
|
||||
|
||||
|
62
xlib/mp/csw/Flag.py
Normal file
62
xlib/mp/csw/Flag.py
Normal file
|
@ -0,0 +1,62 @@
|
|||
from xlib.python import EventListener
|
||||
|
||||
from .CSWBase import ControlClient, ControlHost
|
||||
|
||||
|
||||
class _FlagBase:
|
||||
def __init__(self):
|
||||
self._flag = None
|
||||
self._on_flag_evl = EventListener()
|
||||
|
||||
self._call_on_msg('flag', self._on_msg_flag)
|
||||
|
||||
def _on_msg_flag(self, flag):
|
||||
self._set_flag(flag)
|
||||
|
||||
def _send_flag(self):
|
||||
self._send_msg('flag', self._flag)
|
||||
|
||||
def _set_flag(self, flag : bool):
|
||||
if flag is not None and not isinstance(flag, bool):
|
||||
raise ValueError('flag must be a bool value or None')
|
||||
|
||||
if self._flag != flag:
|
||||
self._flag = flag
|
||||
self._on_flag_evl.call(flag if flag is not None else False)
|
||||
return True
|
||||
return False
|
||||
|
||||
def call_on_flag(self, func):
|
||||
"""Call when the flag is changed"""
|
||||
self._on_flag_evl.add(func)
|
||||
|
||||
def set_flag(self, flag : bool):
|
||||
if self._set_flag(flag):
|
||||
self._send_flag()
|
||||
|
||||
def get_flag(self): return self._flag
|
||||
|
||||
class Flag:
|
||||
"""
|
||||
Flag control.
|
||||
|
||||
Values: None : uninitialized/not set
|
||||
bool : value
|
||||
"""
|
||||
class Host(ControlHost, _FlagBase):
|
||||
def __init__(self):
|
||||
ControlHost.__init__(self)
|
||||
_FlagBase.__init__(self)
|
||||
|
||||
def _on_msg_flag(self, flag):
|
||||
if self.is_enabled():
|
||||
_FlagBase._on_msg_flag(self, flag)
|
||||
self._send_flag()
|
||||
|
||||
class Client(ControlClient, _FlagBase):
|
||||
def __init__(self):
|
||||
ControlClient.__init__(self)
|
||||
_FlagBase.__init__(self)
|
||||
|
||||
def _on_reset(self):
|
||||
self._set_flag(None)
|
48
xlib/mp/csw/InfoBlock.py
Normal file
48
xlib/mp/csw/InfoBlock.py
Normal file
|
@ -0,0 +1,48 @@
|
|||
from typing import Union, List
|
||||
|
||||
from xlib.python import EventListener
|
||||
|
||||
from .CSWBase import ControlClient, ControlHost
|
||||
|
||||
|
||||
class InfoBlock:
|
||||
"""
|
||||
|
||||
"""
|
||||
|
||||
class Client(ControlClient):
|
||||
def __init__(self):
|
||||
ControlClient.__init__(self)
|
||||
|
||||
self._on_info_evl = EventListener()
|
||||
self._call_on_msg('info', self._on_msg_info)
|
||||
|
||||
def _on_msg_info(self, lines):
|
||||
self._on_info_evl.call(lines)
|
||||
|
||||
def call_on_info(self, func_or_list):
|
||||
"""
|
||||
Call when the error message arrive
|
||||
|
||||
func( lines : Union[ List[str], None] )
|
||||
"""
|
||||
self._on_info_evl.add(func_or_list)
|
||||
|
||||
def _on_reset(self):
|
||||
self._on_msg_info(None)
|
||||
|
||||
|
||||
class Host(ControlHost):
|
||||
def __init__(self):
|
||||
ControlHost.__init__(self)
|
||||
|
||||
|
||||
def set_info(self, lines : Union[ List[str], None]):
|
||||
"""
|
||||
set info
|
||||
|
||||
lines List[str] | None
|
||||
"""
|
||||
self._send_msg('info', lines)
|
||||
|
||||
|
48
xlib/mp/csw/InfoLabel.py
Normal file
48
xlib/mp/csw/InfoLabel.py
Normal file
|
@ -0,0 +1,48 @@
|
|||
from typing import Union, List
|
||||
|
||||
from xlib.python import EventListener
|
||||
|
||||
from .CSWBase import ControlClient, ControlHost
|
||||
|
||||
|
||||
class InfoLabel:
|
||||
"""
|
||||
|
||||
"""
|
||||
class Config:
|
||||
def __init__(self, label : Union[str, None] = None,
|
||||
info_icon = False,
|
||||
info_lines : Union[ List[str], None] = None):
|
||||
self.label = label
|
||||
self.info_icon = info_icon
|
||||
self.info_lines = info_lines
|
||||
|
||||
class Client(ControlClient):
|
||||
def __init__(self):
|
||||
ControlClient.__init__(self)
|
||||
|
||||
self._on_config_evl = EventListener()
|
||||
self._call_on_msg('_cfg', self._on_msg_config)
|
||||
|
||||
def _on_msg_config(self, cfg):
|
||||
self._on_config_evl.call(cfg)
|
||||
|
||||
def call_on_config(self, func_or_list):
|
||||
"""
|
||||
"""
|
||||
self._on_config_evl.add(func_or_list)
|
||||
|
||||
def _on_reset(self):
|
||||
...
|
||||
# self._on_msg_config( InfoLabel.Config() )
|
||||
|
||||
|
||||
class Host(ControlHost):
|
||||
def __init__(self):
|
||||
ControlHost.__init__(self)
|
||||
|
||||
def set_config(self, cfg : 'InfoLabel.Config'):
|
||||
"""
|
||||
"""
|
||||
self._send_msg('_cfg', cfg)
|
||||
|
107
xlib/mp/csw/Number.py
Normal file
107
xlib/mp/csw/Number.py
Normal file
|
@ -0,0 +1,107 @@
|
|||
import numpy as np
|
||||
from xlib.python import EventListener
|
||||
|
||||
from .CSWBase import ControlClient, ControlHost
|
||||
|
||||
|
||||
class _NumberBase:
|
||||
def __init__(self):
|
||||
self._number = None
|
||||
self._on_number_evl = EventListener()
|
||||
self._call_on_msg('number', self._on_msg_number)
|
||||
|
||||
def _on_msg_number(self, number):
|
||||
self._set_number(number)
|
||||
|
||||
def _send_number(self):
|
||||
self._send_msg('number', self._number)
|
||||
|
||||
def _set_number(self, number, block_event=False):
|
||||
if number is not None:
|
||||
if isinstance(number, (int, np.int, np.int8, np.int16, np.int32, np.int64)):
|
||||
number = int(number)
|
||||
elif isinstance(number, (float, np.float, np.float16, np.float32, np.float64)):
|
||||
number = float(number)
|
||||
else:
|
||||
raise ValueError('number must be an instance of int/float')
|
||||
|
||||
if self._number != number:
|
||||
self._number = number
|
||||
if not block_event:
|
||||
self._on_number_evl.call(number if number is not None else 0)
|
||||
return True
|
||||
return False
|
||||
|
||||
def call_on_number(self, func_or_list):
|
||||
"""Call when the number is changed."""
|
||||
self._on_number_evl.add(func_or_list)
|
||||
|
||||
def set_number(self, number, block_event=False):
|
||||
"""
|
||||
|
||||
block_event(False) bool on_number event will not be called on this side
|
||||
"""
|
||||
if self._set_number(number, block_event=block_event):
|
||||
self._send_number()
|
||||
|
||||
def get_number(self): return self._number
|
||||
|
||||
class Number:
|
||||
"""
|
||||
Number control.
|
||||
|
||||
Values:
|
||||
None : uninitialized state
|
||||
int/float : value
|
||||
"""
|
||||
|
||||
class Config:
|
||||
"""
|
||||
allow_instant_update mean that the user widget can
|
||||
send the value immediatelly during change,
|
||||
for example - scrolling the spinbox
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, min=None, max=None, step=None, decimals=None, zero_is_auto : bool =False, allow_instant_update : bool =False, read_only : bool =False):
|
||||
self.min = min
|
||||
self.max = max
|
||||
self.step = step
|
||||
self.decimals = decimals
|
||||
self.zero_is_auto : bool = zero_is_auto
|
||||
self.allow_instant_update : bool = allow_instant_update
|
||||
self.read_only : bool = read_only
|
||||
|
||||
|
||||
class Host(ControlHost, _NumberBase):
|
||||
def __init__(self):
|
||||
ControlHost.__init__(self)
|
||||
_NumberBase.__init__(self)
|
||||
self._config = Number.Config()
|
||||
|
||||
def _on_msg_number(self, number):
|
||||
if self.is_enabled():
|
||||
_NumberBase._on_msg_number(self, number)
|
||||
self._send_number()
|
||||
|
||||
def get_config(self) -> 'Number.Config':
|
||||
return self._config
|
||||
|
||||
def set_config(self, config : 'Number.Config'):
|
||||
self._config = config
|
||||
self._send_msg('config', config)
|
||||
|
||||
class Client(ControlClient, _NumberBase):
|
||||
def __init__(self):
|
||||
ControlClient.__init__(self)
|
||||
_NumberBase.__init__(self)
|
||||
self._on_config_evl = EventListener()
|
||||
self._call_on_msg('config', self._on_msg_config)
|
||||
|
||||
def _on_reset(self):
|
||||
self._set_number(None)
|
||||
|
||||
def _on_msg_config(self, cfg : 'Number.Config'):
|
||||
self._on_config_evl.call(cfg)
|
||||
|
||||
def call_on_config(self, func): self._on_config_evl.add(func)
|
144
xlib/mp/csw/Paths.py
Normal file
144
xlib/mp/csw/Paths.py
Normal file
|
@ -0,0 +1,144 @@
|
|||
from collections import Iterable
|
||||
from enum import IntEnum
|
||||
from pathlib import Path
|
||||
from typing import List, Union
|
||||
|
||||
from xlib.python import EventListener
|
||||
|
||||
from .CSWBase import ControlClient, ControlHost
|
||||
|
||||
|
||||
class _PathBase:
|
||||
def __init__(self):
|
||||
self._paths = []
|
||||
self._on_paths_evl = EventListener()
|
||||
self._call_on_msg('paths', self._on_msg_paths)
|
||||
|
||||
def _on_msg_paths(self, path):
|
||||
self._set_paths(path)
|
||||
|
||||
def _send_paths(self):
|
||||
self._send_msg('paths', self._paths)
|
||||
|
||||
def _set_paths(self, path_or_list, block_event=False):
|
||||
if isinstance(path_or_list, Iterable) and \
|
||||
not isinstance(path_or_list, str):
|
||||
path_or_list = list(path_or_list)
|
||||
else:
|
||||
path_or_list = [path_or_list]
|
||||
|
||||
for i,path in enumerate(path_or_list):
|
||||
if isinstance(path, str):
|
||||
path_or_list[i] = Path(path)
|
||||
elif not isinstance(path, Path):
|
||||
raise ValueError(f'value {path} must be an instance of str or Path')
|
||||
|
||||
if self._paths != path_or_list:
|
||||
prev_paths = self._paths
|
||||
self._paths = path_or_list
|
||||
if not block_event:
|
||||
self._on_paths_evl.call(path_or_list, prev_paths)
|
||||
return True
|
||||
return False
|
||||
|
||||
def call_on_paths(self, func_or_list):
|
||||
"""
|
||||
Call when the path is changed
|
||||
|
||||
func(path_list, prev_path_list)
|
||||
"""
|
||||
self._on_paths_evl.add(func_or_list)
|
||||
|
||||
def set_paths(self, path_or_list, block_event=False):
|
||||
"""
|
||||
path_or_list Path/str or list of Paths/str or []
|
||||
or None which is same as []
|
||||
"""
|
||||
if path_or_list is None:
|
||||
path_or_list = []
|
||||
|
||||
if self._set_paths(path_or_list, block_event=block_event):
|
||||
self._send_paths()
|
||||
|
||||
def get_paths(self): return self._paths
|
||||
|
||||
class Paths:
|
||||
"""
|
||||
Paths control.
|
||||
|
||||
Values: [] not set
|
||||
list of [1+] Paths
|
||||
"""
|
||||
|
||||
|
||||
class Config:
|
||||
class Type(IntEnum):
|
||||
NONE = 0
|
||||
ANY_FILE = 1
|
||||
EXISTING_FILE = 2
|
||||
EXISTING_FILES = 3
|
||||
DIRECTORY = 4
|
||||
|
||||
def __init__(self, type = None, is_save = False, caption = None, suffixes = None, directory_path = None):
|
||||
if type is None:
|
||||
type = Paths.Config.Type.NONE
|
||||
self._type = type
|
||||
self._is_save = is_save
|
||||
self._caption = caption
|
||||
self._suffixes = suffixes
|
||||
self._directory_path = directory_path
|
||||
|
||||
def get_type(self) -> 'Paths.Config.Type': return self._type
|
||||
def is_save(self) -> bool: return self._is_save
|
||||
def get_caption(self) -> Union[str, None]: return self._caption
|
||||
def get_suffixes(self) -> Union[List[str], None]: return self._suffixes
|
||||
def get_directory_path(self) -> Union[Path, None]: return self._directory_path
|
||||
|
||||
@staticmethod
|
||||
def AnyFile(is_save=False, caption=None, suffixes=None):
|
||||
return Paths.Config(Paths.Config.Type.ANY_FILE, is_save, caption, suffixes)
|
||||
|
||||
@staticmethod
|
||||
def ExistingFile(is_save=False, caption=None, suffixes=None):
|
||||
return Paths.Config(Paths.Config.Type.EXISTING_FILE, is_save, caption, suffixes)
|
||||
|
||||
@staticmethod
|
||||
def ExistingFiles(caption=None, suffixes=None):
|
||||
return Paths.Config(Paths.Config.Type.EXISTING_FILES, False, caption, suffixes)
|
||||
|
||||
@staticmethod
|
||||
def Directory(caption=None, directory_path=None):
|
||||
return Paths.Config(Paths.Config.Type.DIRECTORY, False, caption, None, directory_path=directory_path)
|
||||
|
||||
|
||||
class Host(ControlHost, _PathBase):
|
||||
def __init__(self):
|
||||
ControlHost.__init__(self)
|
||||
_PathBase.__init__(self)
|
||||
self._config = Paths.Config()
|
||||
|
||||
def _on_msg_paths(self, path):
|
||||
if self.is_enabled():
|
||||
_PathBase._on_msg_paths(self, path)
|
||||
self._send_paths()
|
||||
|
||||
def set_config(self, config : 'Paths.Config'):
|
||||
self._config = config
|
||||
self._send_msg('config', config)
|
||||
|
||||
|
||||
|
||||
class Client(ControlClient, _PathBase):
|
||||
def __init__(self):
|
||||
ControlClient.__init__(self)
|
||||
_PathBase.__init__(self)
|
||||
self._on_config_evl = EventListener()
|
||||
self._call_on_msg('config', self._on_msg_config)
|
||||
|
||||
def _on_msg_config(self, config : 'Paths.Config'):
|
||||
self._on_config_evl.call(config)
|
||||
|
||||
def call_on_config(self, func): self._on_config_evl.add(func)
|
||||
|
||||
def _on_reset(self):
|
||||
self._set_paths([])
|
84
xlib/mp/csw/Progress.py
Normal file
84
xlib/mp/csw/Progress.py
Normal file
|
@ -0,0 +1,84 @@
|
|||
from typing import Union
|
||||
|
||||
from xlib.python import EventListener
|
||||
|
||||
from .CSWBase import ControlClient, ControlHost
|
||||
|
||||
|
||||
class _ProgressBase:
|
||||
def __init__(self):
|
||||
self._progress = None
|
||||
self._on_progress_evl = EventListener()
|
||||
self._call_on_msg('progress', self._on_msg_progress)
|
||||
|
||||
def _on_msg_progress(self, progress):
|
||||
self._set_progress(progress)
|
||||
|
||||
def _set_progress(self, progress, block_event=False):
|
||||
if progress is not None:
|
||||
progress = int(progress)
|
||||
|
||||
if self._progress != progress:
|
||||
self._progress = progress
|
||||
if not block_event:
|
||||
self._on_progress_evl.call(progress if progress is not None else 0)
|
||||
return True
|
||||
return False
|
||||
|
||||
def call_on_progress(self, func_or_list):
|
||||
"""Call when the progress is changed."""
|
||||
self._on_progress_evl.add(func_or_list)
|
||||
|
||||
def get_progress(self): return self._progress
|
||||
|
||||
class Progress:
|
||||
"""
|
||||
Progress control with 0..100 int value
|
||||
|
||||
Values:
|
||||
None : uninitialized state
|
||||
int/float : value
|
||||
"""
|
||||
|
||||
class Config:
|
||||
def __init__(self, title=None):
|
||||
self._title = title
|
||||
|
||||
def get_title(self) -> Union[str, None]:
|
||||
return self._title
|
||||
|
||||
class Host(ControlHost, _ProgressBase):
|
||||
def __init__(self):
|
||||
ControlHost.__init__(self)
|
||||
_ProgressBase.__init__(self)
|
||||
self._config = Progress.Config()
|
||||
|
||||
def _send_progress(self):
|
||||
self._send_msg('progress', self._progress)
|
||||
|
||||
def set_progress(self, progress, block_event=False):
|
||||
"""
|
||||
progress number 0..100
|
||||
block_event(False) on_progress event will not be called on this side
|
||||
"""
|
||||
if self._set_progress(progress, block_event=block_event):
|
||||
self._send_progress()
|
||||
|
||||
def set_config(self, config : 'Progress.Config'):
|
||||
self._send_msg('config', config)
|
||||
|
||||
class Client(ControlClient, _ProgressBase):
|
||||
def __init__(self):
|
||||
ControlClient.__init__(self)
|
||||
_ProgressBase.__init__(self)
|
||||
self._on_config_evl = EventListener()
|
||||
self._call_on_msg('config', self._on_msg_config)
|
||||
|
||||
def _on_reset(self):
|
||||
self._set_progress(None)
|
||||
|
||||
def _on_msg_config(self, config):
|
||||
self._on_config_evl.call(config)
|
||||
|
||||
def call_on_config(self, func):
|
||||
self._on_config_evl.add(func)
|
27
xlib/mp/csw/Signal.py
Normal file
27
xlib/mp/csw/Signal.py
Normal file
|
@ -0,0 +1,27 @@
|
|||
from xlib.python import EventListener
|
||||
|
||||
from .CSWBase import ControlClient, ControlHost
|
||||
|
||||
class Signal:
|
||||
class Host(ControlHost):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
self._signal_evl = EventListener()
|
||||
self._call_on_msg('signal', self._on_msg_signal)
|
||||
|
||||
def call_on_signal(self, func): self._signal_evl.add(func)
|
||||
|
||||
def signal(self):
|
||||
self._on_msg_signal()
|
||||
|
||||
def _on_msg_signal(self):
|
||||
if self.is_enabled():
|
||||
self._signal_evl.call()
|
||||
|
||||
class Client(ControlClient):
|
||||
def signal(self):
|
||||
self._send_msg('signal')
|
||||
|
||||
def _on_reset(self):
|
||||
...
|
67
xlib/mp/csw/Text.py
Normal file
67
xlib/mp/csw/Text.py
Normal file
|
@ -0,0 +1,67 @@
|
|||
from xlib.python import EventListener
|
||||
|
||||
from .CSWBase import ControlClient, ControlHost
|
||||
|
||||
|
||||
class _TextBase:
|
||||
def __init__(self):
|
||||
self._text = None
|
||||
self._on_text_evl = EventListener()
|
||||
|
||||
self._call_on_msg('text', self._on_msg_text)
|
||||
|
||||
def _on_msg_text(self, text):
|
||||
self._set_text(text)
|
||||
|
||||
def _send_text(self):
|
||||
self._send_msg('text', self._text)
|
||||
|
||||
def _set_text(self, text : str):
|
||||
if text is not None and not isinstance(text, str):
|
||||
raise ValueError('text must be str or None')
|
||||
|
||||
if self._text != text:
|
||||
self._text = text
|
||||
self._on_text_evl.call(text)
|
||||
return True
|
||||
return False
|
||||
|
||||
def call_on_text(self, func_or_list):
|
||||
"""
|
||||
Call when the text is changed
|
||||
|
||||
func(text : Union[str,None])
|
||||
"""
|
||||
self._on_text_evl.add(func_or_list)
|
||||
|
||||
def set_text(self, text : str):
|
||||
if self._set_text(text):
|
||||
self._send_text()
|
||||
|
||||
def get_text(self): return self._text
|
||||
|
||||
class Text:
|
||||
"""
|
||||
Text control.
|
||||
|
||||
Values:
|
||||
None : uninitialized state
|
||||
str : value
|
||||
"""
|
||||
class Host(ControlHost, _TextBase):
|
||||
def __init__(self):
|
||||
ControlHost.__init__(self)
|
||||
_TextBase.__init__(self)
|
||||
|
||||
def _on_msg_text(self, text):
|
||||
if self.is_enabled():
|
||||
_TextBase._on_msg_text(self, text)
|
||||
self._send_text()
|
||||
|
||||
class Client(ControlClient, _TextBase):
|
||||
def __init__(self):
|
||||
ControlClient.__init__(self)
|
||||
_TextBase.__init__(self)
|
||||
|
||||
def _on_reset(self):
|
||||
self._set_text(None)
|
17
xlib/mp/csw/__init__.py
Normal file
17
xlib/mp/csw/__init__.py
Normal file
|
@ -0,0 +1,17 @@
|
|||
"""
|
||||
Controllable Subprocess Worker
|
||||
"""
|
||||
|
||||
|
||||
from .CSWBase import (DB, Control, ControlClient, ControlHost, Host, Sheet,
|
||||
Worker, WorkerState)
|
||||
from .DynamicSingleSwitch import DynamicSingleSwitch
|
||||
from .Error import Error
|
||||
from .Flag import Flag
|
||||
from .InfoBlock import InfoBlock
|
||||
from .InfoLabel import InfoLabel
|
||||
from .Number import Number
|
||||
from .Paths import Paths
|
||||
from .Progress import Progress
|
||||
from .Signal import Signal
|
||||
from .Text import Text
|
Loading…
Add table
Add a link
Reference in a new issue