Import LinuxCNC source tree
This commit is contained in:
567
lib/python/qtvcp/qt_makegui.py
Normal file
567
lib/python/qtvcp/qt_makegui.py
Normal file
@@ -0,0 +1,567 @@
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
import subprocess
|
||||
|
||||
from qtpy import QtGui, QtCore, QtWidgets, uic
|
||||
import traceback
|
||||
from qtvcp.widgets.widget_baseclass import _HalWidgetBase
|
||||
# Set up logging
|
||||
from . import logger
|
||||
|
||||
log = logger.getLogger(__name__)
|
||||
|
||||
# Force the log level for this module
|
||||
#log.setLevel(logger.DEBUG) # One of DEBUG, INFO, WARNING, ERROR, CRITICAL
|
||||
|
||||
# PyQt6 raises KeyError when a .ui file signal connection references an overloaded
|
||||
# signal type that no longer exists (e.g. 'bool' subscript on a non-overloaded signal).
|
||||
# Patch _handle_connections to skip bad connections rather than aborting the whole load.
|
||||
try:
|
||||
from PyQt6.uic import uiparser as _uiparser
|
||||
from PyQt6 import QtCore as _QtCore
|
||||
|
||||
def _tolerant_handle_connections(self, el):
|
||||
def name2object(obj):
|
||||
if obj == self.uiname:
|
||||
return self.toplevelWidget
|
||||
else:
|
||||
return getattr(self.toplevelWidget, obj)
|
||||
|
||||
for conn in el:
|
||||
signal = conn.findtext('signal')
|
||||
signal_name, signal_args = signal.split('(')
|
||||
signal_args = signal_args[:-1].replace(' ', '')
|
||||
sender = name2object(conn.findtext('sender'))
|
||||
bound_signal = getattr(sender, signal_name)
|
||||
slot = self.factory.getSlot(name2object(conn.findtext('receiver')),
|
||||
conn.findtext('slot').split('(')[0])
|
||||
try:
|
||||
if signal_args == '':
|
||||
bound_signal.connect(slot)
|
||||
else:
|
||||
signal_args_list = signal_args.split(',')
|
||||
if len(signal_args_list) == 1:
|
||||
bound_signal[signal_args_list[0]].connect(slot)
|
||||
else:
|
||||
bound_signal[tuple(signal_args_list)].connect(slot)
|
||||
except KeyError:
|
||||
log.warning('Skipping ui signal connection {}({}) - no matching PyQt6 overload'.format(
|
||||
signal_name, signal_args))
|
||||
|
||||
_QtCore.QMetaObject.connectSlotsByName(self.toplevelWidget)
|
||||
|
||||
_uiparser.UIParser._handle_connections = _tolerant_handle_connections
|
||||
except ImportError:
|
||||
pass # Not using PyQt6
|
||||
|
||||
class Trampoline(object):
|
||||
def __init__(self, methods):
|
||||
self.methods = methods
|
||||
|
||||
def __call__(self, *a, **kw):
|
||||
for m in self.methods:
|
||||
m(*a, **kw)
|
||||
|
||||
|
||||
class MyEventFilter(QtCore.QObject):
|
||||
def __init__(self, window):
|
||||
super(MyEventFilter, self).__init__()
|
||||
self.w = window
|
||||
self.has_key_p_handler = 'keypress_event__' in dir(self.w.handler_instance)
|
||||
self.has_key_r_handler = 'keyrelease_event__' in dir(self.w.handler_instance)
|
||||
self.has_process_key_handler = 'processed_key_event__' in dir(self.w.handler_instance)
|
||||
|
||||
def process_event(self, event, pressed):
|
||||
shift = ctrl = False
|
||||
if (event.modifiers() & QtCore.Qt.ShiftModifier):
|
||||
shift = True
|
||||
if (event.modifiers() & QtCore.Qt.ControlModifier):
|
||||
ctrl = True
|
||||
code = event.key()
|
||||
key = event.text()
|
||||
return (pressed, key, code, shift, ctrl)
|
||||
|
||||
def eventFilter(self, receiver, event):
|
||||
# in pyqt5 QWindow gets all events before the widgets inside it do
|
||||
# we need the widgets inside it to get their events first
|
||||
# this line line provides that.
|
||||
# (pyqt4 did not require this)
|
||||
if isinstance(receiver, QtGui.QWindow):
|
||||
return super(MyEventFilter, self).eventFilter(receiver, event)
|
||||
# Run in try statement so if we want to event to run through normal event routines,
|
||||
# we just raise an error and run the super class event handler.
|
||||
# This is necessary because if the widget (such as dialogs) are owned by c++ rather then python,
|
||||
# it causes an error and the keystrokes don't get to the widgets.
|
||||
try:
|
||||
if (event.type() == QtCore.QEvent.KeyPress):
|
||||
handled = False
|
||||
handled = self.w.keyPressTrap(event)
|
||||
if (self.has_key_p_handler):
|
||||
handled = self.w.handler_instance.keypress_event__(receiver, event)
|
||||
elif self.has_process_key_handler:
|
||||
p, k, c, s, ctrl = self.process_event(event, True)
|
||||
handled = self.w.handler_instance.processed_key_event__(receiver, event, p, k, c, s, ctrl)
|
||||
if handled: return True
|
||||
elif (event.type() == QtCore.QEvent.KeyRelease):
|
||||
handled = False
|
||||
handled = self.w.keyReleaseTrap(event)
|
||||
if (self.has_key_r_handler):
|
||||
handled = self.w.handler_instance.keyrelease_event__(event)
|
||||
elif self.has_process_key_handler:
|
||||
p, k, c, s, ctrl = self.process_event(event, False)
|
||||
handled = self.w.handler_instance.processed_key_event__(receiver, event, p, k, c, s, ctrl)
|
||||
if handled: return True
|
||||
elif event.type() in (QtCore.QEvent.FocusIn, QtCore.QEvent.FocusOut):
|
||||
self.w.handler_instance.processed_focus_event__(receiver, event)
|
||||
# Call Base Class Method to Continue Normal Event Processing
|
||||
return super(MyEventFilter, self).eventFilter(receiver, event)
|
||||
except:
|
||||
return super(MyEventFilter, self).eventFilter(receiver, event)
|
||||
|
||||
|
||||
class _VCPWindow(QtWidgets.QMainWindow):
|
||||
def __init__(self, halcomp=None, path=None):
|
||||
super(_VCPWindow, self).__init__()
|
||||
# only initialize once for all instances
|
||||
if self.__class__._instanceNum >= 1:
|
||||
return
|
||||
self.__class__._instanceNum += 1
|
||||
|
||||
self._idName = "TopObject" # initial name, will be replaced
|
||||
self.halcomp = halcomp
|
||||
self.has_closing_handler = False
|
||||
self.setFocus()
|
||||
self.PATHS = path
|
||||
self.PREFS_ = None
|
||||
self.originalCloseEvent_ = self.closeEvent
|
||||
self._halWidgetList = []
|
||||
self._VCPWindowList = []
|
||||
self._DialogList = []
|
||||
self.settings = QtCore.QSettings('QtVcp', path.BASENAME)
|
||||
log.info('Qsettings file path: yellow<{}>'.format(self.settings.fileName()))
|
||||
# make an instance with embedded variables so they
|
||||
# are available to all subclassed objects
|
||||
_HalWidgetBase(halcomp, path, self)
|
||||
|
||||
def registerHalWidget(self, widget):
|
||||
self._halWidgetList.append(widget)
|
||||
|
||||
def getRegisteredHalWidgetList(self):
|
||||
return self._halWidgetList
|
||||
|
||||
def registerDialog(self, widget):
|
||||
self._DialogList.append(widget)
|
||||
|
||||
def getRegisteredDialogList(self):
|
||||
return self._DialogList
|
||||
|
||||
# These catch events if using a plain VCP panel and there is no handler file
|
||||
def keyPressEvent(self, e):
|
||||
self.keyPressTrap(e)
|
||||
|
||||
def keyReleaseEvent(self, e):
|
||||
self.keyReleaseTrap(e)
|
||||
|
||||
# These can get class patched by xembed library to catch events
|
||||
def keyPressTrap(self, e):
|
||||
return False
|
||||
|
||||
def keyReleaseTrap(self, e):
|
||||
return False
|
||||
|
||||
# call closing function on main and embedded windows.
|
||||
def shutdown(self):
|
||||
for i in (self._VCPWindowList):
|
||||
if 'closing_cleanup__' in dir(i):
|
||||
log.debug('Calling handler file closing_cleanup__ function of {}.'.format(i._idName))
|
||||
i.closing_cleanup__()
|
||||
|
||||
def sync_qsettings(self):
|
||||
try:
|
||||
self.settings.sync()
|
||||
log.debug('Qsettings sync called:')
|
||||
except Exception as e:
|
||||
log.debug('Error with Qsettings sync function:\n {}'.format(e))
|
||||
|
||||
# resource files are compiled from the qrs file
|
||||
# with an installed version of linuxcnc we can't save the resource
|
||||
# file in the installed directory without being root,
|
||||
# so we always make a directory in the config folder to put it in
|
||||
def load_resources(self):
|
||||
def qrccompile(qrcname, qrcpy):
|
||||
log.info('Compiling qrc: {} to \n {}'.format(qrcname, qrcpy))
|
||||
try:
|
||||
subprocess.call(["pyrcc5", "-o", "{}".format(qrcpy), "{}".format(qrcname)])
|
||||
return
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
try:
|
||||
rcc = shutil.which("pyside6-rcc")
|
||||
if not rcc:
|
||||
rcc = shutil.which("rcc", path="{}{}{}".format(os.getenv("PATH", ""), os.pathsep, "/usr/lib/qt6/libexec")) or "rcc"
|
||||
subprocess.call([rcc, "-g", "python", "-o", "{}".format(qrcpy), "{}".format(qrcname)])
|
||||
# rcc generates PySide6 imports; rewrite to use qtpy
|
||||
with open(qrcpy, 'r') as f:
|
||||
content = f.read()
|
||||
content = content.replace('from PySide6 import QtCore', 'from qtpy import QtCore')
|
||||
with open(qrcpy, 'w') as f:
|
||||
f.write(content)
|
||||
except OSError as e:
|
||||
log.error(
|
||||
'{}, pyrcc5/rcc error. try in terminal: sudo apt install pyqt5-dev-tools or qt6-base-dev-tools to install dev tools'.format(
|
||||
e))
|
||||
msg = QtWidgets.QMessageBox()
|
||||
msg.setIcon(QtWidgets.QMessageBox.Critical)
|
||||
msg.setText("QTvcp qrc compiling ERROR! ")
|
||||
msg.setInformativeText(
|
||||
'Qrc Compile error, try: "sudo apt install pyqt5-dev-tools" or "qt6-base-dev-tools" to install dev tools')
|
||||
msg.setWindowTitle("Error")
|
||||
msg.setDetailedText('You can continue but some images may be missing')
|
||||
msg.setStandardButtons(QtWidgets.QMessageBox.Retry | QtWidgets.QMessageBox.Abort)
|
||||
msg.show()
|
||||
retval = msg.exec()
|
||||
if retval == QtWidgets.QMessageBox.Abort: # cancel button
|
||||
log.critical("Canceled from qrc compiling Error Dialog.\n")
|
||||
raise SystemError('pyrcc5/rcc compiling error: try: "sudo apt install pyqt5-dev-tools" or "qt6-base-dev-tools"')
|
||||
|
||||
qrcname = self.PATHS.QRC
|
||||
qrcpy = self.PATHS.QRCPY
|
||||
|
||||
# Is there a qrc file in directory?
|
||||
if qrcname is not None:
|
||||
qrcTime = os.stat(qrcname).st_mtime
|
||||
if qrcpy is not None and os.path.isfile(qrcpy):
|
||||
pyTime = os.stat(qrcpy).st_mtime
|
||||
# is py older then qrc file?
|
||||
if pyTime < qrcTime:
|
||||
qrccompile(qrcname, qrcpy)
|
||||
# there is a qrc file but no resources.py file...compile it
|
||||
else:
|
||||
if not os.path.isfile(qrcpy):
|
||||
# make the missing directory
|
||||
try:
|
||||
os.makedirs(os.path.split(qrcpy)[0])
|
||||
except Exception as e:
|
||||
log.warning('Could not make directory {} resource file: {}'.format(os.path.split(qrcpy)[0], e))
|
||||
qrccompile(qrcname, qrcpy)
|
||||
|
||||
# is there a resource.py in the directory?
|
||||
# if so add a path to it so we can import it.
|
||||
if qrcpy is not None and os.path.isfile(qrcpy):
|
||||
try:
|
||||
sys.path.insert(0, os.path.split(qrcpy)[0])
|
||||
import importlib
|
||||
importlib.import_module('resources', os.path.split(qrcpy)[0])
|
||||
log.info('Imported resources.py file: yellow<{}>'.format(qrcpy))
|
||||
except Exception as e:
|
||||
log.warning('Could not load {} resource file: yellow<{}>'.format(qrcpy, e))
|
||||
else:
|
||||
log.info('No resource file to load: yellow<{}>'.format(qrcpy))
|
||||
|
||||
def instance(self, filename):
|
||||
self.load_resources()
|
||||
try:
|
||||
instance = uic.loadUi(filename, self)
|
||||
except AttributeError as e:
|
||||
formatted_lines = traceback.format_exc().splitlines()
|
||||
if 'slotname' in formatted_lines[-2]:
|
||||
log.critical('Missing slot name in handler file: {}'.format(e))
|
||||
message = '''A widget in the ui file, was assigned a signal \
|
||||
call to a missing function name in the handler file?\n
|
||||
There may be more. Some functions might not work.\n
|
||||
Python Error:\n {}'''.format(str(e))
|
||||
rtn = QtWidgets.QMessageBox.critical(None, "QTVCP Error", message)
|
||||
else:
|
||||
log.critical(e)
|
||||
raise
|
||||
|
||||
def apply_styles(self, fname=None):
|
||||
if self.PATHS.IS_SCREEN:
|
||||
DIR = self.PATHS.SCREENDIR
|
||||
BNAME = self.PATHS.BASENAME
|
||||
else:
|
||||
DIR = self.PATHS.PANELDIR
|
||||
BNAME = self.PATHS.BASENAME
|
||||
# apply one word system theme
|
||||
if fname in (list(QtWidgets.QStyleFactory.keys())):
|
||||
QtWidgets.QApplication.instance().setStyle(fname)
|
||||
log.info('Applied System Style name: yellow<{}>'.format(fname))
|
||||
return
|
||||
|
||||
# Check for Preference file specified qss
|
||||
if fname is None:
|
||||
if not self.PREFS_ is None:
|
||||
path = self.PREFS_.getpref('style_QSS_Path', 'DEFAULT', str, 'BOOK_KEEPING')
|
||||
if path.lower() == 'none':
|
||||
return
|
||||
if not path.lower() == 'default':
|
||||
fname = path
|
||||
|
||||
# check for default base named qss file
|
||||
if fname is None:
|
||||
if self.PATHS.QSS is not None:
|
||||
fname = self.PATHS.QSS
|
||||
|
||||
# if qss file is not a file, try expanding/adding a leading path
|
||||
if fname is not None:
|
||||
if not os.path.isfile(fname):
|
||||
temp = os.path.join(os.path.expanduser(fname))
|
||||
qssname = os.path.join(DIR, BNAME, fname + '.qss')
|
||||
if not os.path.isfile(fname):
|
||||
qssname = temp
|
||||
else:
|
||||
qssname = fname
|
||||
else:
|
||||
return
|
||||
try:
|
||||
qss_file = open(qssname).read()
|
||||
# qss files aren't friendly about changing image paths
|
||||
qss_file = qss_file.replace('url(:/newPrefix/images', 'url({}'.format(self.PATHS.IMAGEDIR))
|
||||
self.setStyleSheet(qss_file)
|
||||
log.info('Applied Style Sheet path: yellow<{}>'.format(qssname))
|
||||
return
|
||||
except:
|
||||
if fname:
|
||||
themes = ''
|
||||
log.error('QSS Filepath Error: {}'.format(qssname))
|
||||
log.error("{} theme not available.".format(fname))
|
||||
current_theme = str(QtWidgets.QApplication.instance().style().objectName())
|
||||
for i in (list(QtWidgets.QStyleFactory.keys())):
|
||||
themes += (', {}'.format(i))
|
||||
log.error('QTvcp Available system themes: green<{}> {}'.format(current_theme, themes))
|
||||
|
||||
def load_extension(self, handlerpath, obj = ''):
|
||||
methods, self[obj].handler_module, self[obj].handler_instance = self._load_handlers([handlerpath], self.halcomp, self[obj])
|
||||
for i in methods:
|
||||
self[obj][i] = methods[i]
|
||||
# See if the handler file has a closing function to call first
|
||||
self[obj].has_closing_handler = 'closing_cleanup__' in dir(self[obj].handler_instance)
|
||||
|
||||
def _load_handlers(self, usermod, halcomp, widgets):
|
||||
hdl_func = 'get_handlers'
|
||||
mod = None
|
||||
object = None
|
||||
|
||||
def add_handler(method, f):
|
||||
if method in handlers:
|
||||
handlers[method].append(f)
|
||||
else:
|
||||
handlers[method] = [f]
|
||||
|
||||
handlers = {}
|
||||
for u in usermod:
|
||||
(directory, filename) = os.path.split(u)
|
||||
(basename, extension) = os.path.splitext(filename)
|
||||
if directory == '':
|
||||
directory = '.'
|
||||
if directory not in sys.path:
|
||||
sys.path.insert(0, directory)
|
||||
log.debug('Adding import dir: yellow<{}>'.format(directory))
|
||||
|
||||
try:
|
||||
mod = __import__(basename)
|
||||
# inject/class patch function to read an override file
|
||||
# this will be called from qtvcp.py later
|
||||
mod.HandlerClass.call_user_command_ = self.call_user_command_
|
||||
except ImportError as e:
|
||||
log.critical("Module '{}' skipped - import error: ".format(basename), exc_info=e)
|
||||
raise
|
||||
continue
|
||||
log.debug("Module '{}' imported green<OK>".format(mod.__name__))
|
||||
|
||||
try:
|
||||
# look for 'get_handlers' function
|
||||
h = getattr(mod, hdl_func, None)
|
||||
if h and callable(h):
|
||||
log.debug("Module '{}' : '{}' function found.".format(mod.__name__, hdl_func))
|
||||
objlist = h(halcomp, widgets, self.PATHS) # this sets the handler class signature
|
||||
else:
|
||||
# the module has no get_handlers() callable.
|
||||
# in this case we permit any callable except class Objects in the module to register as handler
|
||||
log.debug("Module '{}': no '{}' function - registering only functions as callbacks."
|
||||
.format(mod.__name__, hdl_func))
|
||||
objlist = [mod]
|
||||
# extract callback candidates
|
||||
for object in objlist:
|
||||
log.debug("Registering handlers in module {} object {}".format(mod.__name__, object))
|
||||
if isinstance(object, dict):
|
||||
methods = list(dict.items())
|
||||
else:
|
||||
methods = [(n, getattr(object, n, None)) for n in dir(object)]
|
||||
for method, f in methods:
|
||||
if method.startswith('_'):
|
||||
continue
|
||||
if callable(f):
|
||||
log.verbose("Register callback '{}'".format(method))
|
||||
add_handler(method, f)
|
||||
|
||||
except Exception as e:
|
||||
log.exception("Trouble looking for handlers in '{}':".format(basename), exc_info=e)
|
||||
# we require a working handler file!
|
||||
raise
|
||||
|
||||
# Wrap lists in Trampoline, unwrap single functions
|
||||
for n, v in list(handlers.items()):
|
||||
if len(v) == 1:
|
||||
handlers[n] = v[0]
|
||||
else:
|
||||
handlers[n] = Trampoline(v)
|
||||
|
||||
return handlers, mod, object
|
||||
|
||||
# this is the function that is injected into the handler file to read an override file
|
||||
# this will be called from qtvcp.py later
|
||||
def call_user_command_(self, klass, rcfile = "~/.qtvcprc"):
|
||||
# substitute for any keywords
|
||||
rcfile.replace('CONFIGFOLDER',self.PATHS.CONFIGPATH)
|
||||
rcfile.replace('WORKINGFOLDER',self.PATHS.WORKINGDIR)
|
||||
rcfile = os.path.expanduser(rcfile)
|
||||
|
||||
if os.path.exists(rcfile):
|
||||
log.info('Handler Override file found at: yellow<{}>'.format(rcfile))
|
||||
try:
|
||||
local = {'self': klass, 'rcfile': rcfile}
|
||||
exec(compile(open(rcfile, "rb").read(), rcfile, 'exec'),local)
|
||||
except Exception as e:
|
||||
log.exception(e)
|
||||
else:
|
||||
log.info('No Handler Override file at: yellow<{}>'.format(rcfile))
|
||||
|
||||
# facilitate the main screen to create another window panel
|
||||
def makeMainPage(self, name):
|
||||
return MainPage(main=self,name=name )
|
||||
|
||||
# This is used to build a window that a qtvcp panel can be built from.
|
||||
# The idea is to embed the panel into the main screen
|
||||
# This allow the completely separate panels to be incorporated in
|
||||
# as the user sees fit.
|
||||
# the big deal is to still allow the panel to have a functioning handler file
|
||||
class MainPage(QtWidgets.QMainWindow):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__()
|
||||
for key, value in kwargs.items():
|
||||
if key =='main':
|
||||
self.halcomp = value.halcomp
|
||||
self.PATHS = value.PATHS
|
||||
self.MAIN = value
|
||||
elif key == 'name':
|
||||
self._idName = value
|
||||
|
||||
def instance(self, filename):
|
||||
try:
|
||||
instance = uic.loadUi(filename, self)
|
||||
except AttributeError as e:
|
||||
formatted_lines = traceback.format_exc().splitlines()
|
||||
if 'slotname' in formatted_lines[-2]:
|
||||
log.critical('{}: Missing slot name in handler file: {}'.format(self._idName, e))
|
||||
message = '''A widget in the ui file, {} was assigned a signal \
|
||||
call to a missing function name in the handler file?\n
|
||||
There may be more. Some functions might not work.\n
|
||||
Python Error:\n {}'''.format(self._idName, str(e))
|
||||
rtn = QtWidgets.QMessageBox.critical(None, "QTVCP Error", message)
|
||||
else:
|
||||
log.critical(e)
|
||||
raise
|
||||
|
||||
def load_extension(self, handlerpath):
|
||||
methods, self.handler_module, self.handler_instance = self._load_handlers([handlerpath], self.halcomp, self)
|
||||
for i in methods:
|
||||
self[i] = methods[i]
|
||||
# See if the handler file has a closing function to call first
|
||||
self.has_closing_handler = 'closing_cleanup__' in dir(self.handler_instance)
|
||||
|
||||
def _load_handlers(self, usermod, halcomp, widgets):
|
||||
hdl_func = 'get_handlers'
|
||||
mod = None
|
||||
object = None
|
||||
|
||||
def add_handler(method, f):
|
||||
if method in handlers:
|
||||
handlers[method].append(f)
|
||||
else:
|
||||
handlers[method] = [f]
|
||||
|
||||
handlers = {}
|
||||
for u in usermod:
|
||||
(directory, filename) = os.path.split(u)
|
||||
(basename, extension) = os.path.splitext(filename)
|
||||
if directory == '':
|
||||
directory = '.'
|
||||
if directory not in sys.path:
|
||||
sys.path.insert(0, directory)
|
||||
log.debug('{}: adding import dir: yellow<{}>'.format(self._idName, directory))
|
||||
|
||||
try:
|
||||
mod = __import__(basename)
|
||||
except ImportError as e:
|
||||
log.critical("{}: module '{}' skipped - import error: "
|
||||
.format(self._idName, basename), exc_info=e)
|
||||
raise
|
||||
continue
|
||||
log.debug("{}: module '{}' imported green<OK>".format(self._idName, mod.__name__))
|
||||
|
||||
try:
|
||||
# look for 'get_handlers' function
|
||||
h = getattr(mod, hdl_func, None)
|
||||
if h and callable(h):
|
||||
log.debug("{}: module '{}' : '{}' function found".format(self._idName, mod.__name__, hdl_func))
|
||||
objlist = h(halcomp, widgets, self.PATHS) # this sets the handler class signature
|
||||
else:
|
||||
# the module has no get_handlers() callable.
|
||||
# in this case we permit any callable except class Objects in the module to register as handler
|
||||
log.debug("{}: module '{}': no '{}' function - registering only functions as callbacks"
|
||||
.format(self._idName, mod.__name__, hdl_func))
|
||||
objlist = [mod]
|
||||
# extract callback candidates
|
||||
for object in objlist:
|
||||
log.debug("{}: Registering handlers in module {} object {}"
|
||||
.format(self._idName, mod.__name__, object))
|
||||
if isinstance(object, dict):
|
||||
methods = list(dict.items())
|
||||
else:
|
||||
methods = [(n, getattr(object, n, None)) for n in dir(object)]
|
||||
for method, f in methods:
|
||||
if method.startswith('_'):
|
||||
continue
|
||||
if callable(f):
|
||||
log.debug("{}: Register callback '{}'".format(self._idName, method))
|
||||
add_handler(method, f)
|
||||
|
||||
except Exception as e:
|
||||
log.exception("{}: Trouble looking for handlers in '{}':"
|
||||
.format(self._idName, basename), exc_info=e)
|
||||
# we require a working handler file!
|
||||
raise
|
||||
|
||||
# Wrap lists in Trampoline, unwrap single functions
|
||||
for n, v in list(handlers.items()):
|
||||
if len(v) == 1:
|
||||
handlers[n] = v[0]
|
||||
else:
|
||||
handlers[n] = Trampoline(v)
|
||||
|
||||
return handlers, mod, object
|
||||
|
||||
def __getitem__(self, item):
|
||||
return getattr(self, item)
|
||||
|
||||
def __setitem__(self, item, value):
|
||||
return setattr(self, item, value)
|
||||
|
||||
class VCPWindow(_VCPWindow):
|
||||
_instance = [None]
|
||||
_instanceNum = 0
|
||||
|
||||
def __new__(cls, *args, **kwargs):
|
||||
if not cls._instance[0]:
|
||||
cls._instance[0] = _VCPWindow.__new__(cls, *args, **kwargs)
|
||||
return cls._instance[0]
|
||||
|
||||
def __getitem__(self, item):
|
||||
if item == '':
|
||||
return self
|
||||
return getattr(self, item)
|
||||
|
||||
def __setitem__(self, item, value):
|
||||
return setattr(self, item, value)
|
||||
Reference in New Issue
Block a user