Index: .gitignore =================================================================== diff -u --- .gitignore (revision 0) +++ .gitignore (revision d711fdaf5fdcd83ec84ec7a05965768429483c29) @@ -0,0 +1,13 @@ +__pycache__ +*.pyc +paths.ini* +tags +tags.* +*.whl +*.log +*.*~ +venv* +.idea/ +.vscode/ +build/ +dist/ Index: dialin =================================================================== diff -u --- dialin (revision 0) +++ dialin (revision d711fdaf5fdcd83ec84ec7a05965768429483c29) @@ -0,0 +1 @@ +../dialin/leahi_dialin \ No newline at end of file Index: engine/diality-logo.jpg =================================================================== diff -u Binary files differ Index: engine/dynamicloader.py =================================================================== diff -u --- engine/dynamicloader.py (revision 0) +++ engine/dynamicloader.py (revision d711fdaf5fdcd83ec84ec7a05965768429483c29) @@ -0,0 +1,203 @@ +""" + find and loads the ui objects +""" +import os +from PySide2.QtUiTools import QUiLoader +from PySide2.QtCore import QFile, QObject +from PySide2 import QtWidgets + + +class DynamicLoader(QObject): + """ + the parent class of all the run time loadable widgets + """ + + canMaximize = False + isVisible = False + menu_name = 'Dynamic Loader' + + loader: QUiLoader + window: QtWidgets.QWidget + + def __init__(self, location: str): + super().__init__(None) + self.location = location + self.loader = QUiLoader() + self.__load_ui() + self._init_loader() + self._init_widgets() + self._init_connections() + + def _init_loader(self): + """ + Virtual method to be implemented in the child class and should be called in order + Main purpose is to find/create the widgets objects in this method + :return: None + """ + raise NotImplementedError() + + def _init_widgets(self): + """ + Virtual method to be implemented in the child class and should be called in order + Main purpose is to setup/initialize widgets properties in this method + :return: None + """ + raise NotImplementedError() + + def _init_connections(self): + """ + Virtual method to be implemented in the child class and should be called in order + Main purpose is to initial the widgets signal/slots connections in this method + :return: None + """ + raise NotImplementedError() + + def __load_ui(self): + """ + loads the ui file of the GUI at run time + :return: none + """ + ui_file = QFile(self.location + "/interface.ui") + ui_file.open(QFile.ReadOnly) + self.window = self.loader.load(ui_file) + # assert if the ui file can't be loaded + error = self.loader.errorString() + assert len(error) == 0, error + ui_file.close() + + def show(self): + """ + shows the main container window + :return: none + """ + self.window.show() + + def find_widget(self, child_type, child_name: str) -> QtWidgets.QWidget: + """ + finds a child in the loaded ui and returns the reference to it if found + otherwise quits the application + :param child_type: (var) type of the child + :param child_name: (str) name of the child + :return: (QtWidgets.QWidget) reference to the child + """ + child: QtWidgets.QWidget = self.window.findChild(child_type, child_name) + assert child is not None, "child name '{}' with type '{}' can't be found.".format(child_name, child_type) + return child + + def find_action(self, name: str) -> QtWidgets.QAction: + """ + convenient method of find_widget for QAction + :param name: (str) name of the QLabel Object + :return: (QAction) reference to the QAction + """ + child = self.find_widget(QtWidgets.QAction, name) + return child + + def find_label(self, name: str) -> QtWidgets.QLabel: + """ + convenient method of find_widget for QLabel + :param name: (str) name of the QLabel Object + :return: (QLabel) reference to the QLabel + """ + child = self.find_widget(QtWidgets.QLabel, name) + return child + + def find_line_edit(self, name: str) -> QtWidgets.QLineEdit: + """ + convenient method of find_widget for QLineEdit + :param name: (str) name of the QLabel Object + :return: (QLabel) reference to the QLineEdit + """ + child = self.find_widget(QtWidgets.QLineEdit, name) + return child + + def find_button(self, name: str) -> QtWidgets.QPushButton: + """ + convenient method of find_widget for QPushButton + :param name: (str) name of the QPushButton Object + :return: (QPushButton) reference to the QPushButton + """ + child = self.find_widget(QtWidgets.QPushButton, name) + return child + + def find_combobox(self, name: str) -> QtWidgets.QComboBox: + """ + convenient method of find_widget for QComboBox + :param name: (str) name of the QComboBox Object + :return: (QComboBox) reference to the QComboBox + """ + child = self.find_widget(QtWidgets.QComboBox, name) + return child + + def find_checkbox(self, name: str) -> QtWidgets.QCheckBox: + """ + convenient method of find_widget for QCheckBox + :param name: (str) name of the QCheckBox Object + :return: (QCheckBox) reference to the QComboBox + """ + child = self.find_widget(QtWidgets.QCheckBox, name) + return child + + def find_spinbox(self, name: str) -> QtWidgets.QSpinBox: + """ + convenient method of find_widget for QSpinBox + :param name: (str) name of the QSpinBox Object + :return: (QSpinBox) reference to the QSpinBox + """ + child = self.find_widget(QtWidgets.QSpinBox, name) + return child + + def find_double_spinbox(self, name: str) -> QtWidgets.QDoubleSpinBox: + """ + convenient method of find_widget for QDoubleSpinBox + :param name: (str) name of the QDoubleSpinBox Object + :return: (QDoubleSpinBox) reference to the QDoubleSpinBox + """ + child = self.find_widget(QtWidgets.QDoubleSpinBox, name) + return child + + def find_radio_button(self, name: str) -> QtWidgets.QRadioButton: + """ + convenient method of find_widget for QRadioButton + :param name: (str) name of the QRadioButton Object + :return: (QRadioButton) reference to the QSpinBox + """ + child = self.find_widget(QtWidgets.QRadioButton, name) + return child + + def find_slider(self, name: str) -> QtWidgets.QSlider: + """ + convenient method of find_widget for QSlider + :param name: (str) name of the QSlider Object + :return: (QSlider) reference to the QSlider + """ + child = self.find_widget(QtWidgets.QSlider, name) + return child + + def find_table_widget(self, name: str) -> QtWidgets.QTableWidget: + """ + convenient method of find_widget for QTableWidget + :param name: (str) name of the QTableWidget Object + :return: (QTableWidget) reference to the QTableWidget + """ + child: QtWidgets.QTableWidget = self.find_widget(QtWidgets.QTableWidget, name) + return child + + def find_list_widget(self, name: str) -> QtWidgets.QListWidget: + """ + convenient method of find_widget for QListWidget + :param name: (str) name of the QListWidget Object + :return: (QListWidget) reference to the QListWidget + """ + child: QtWidgets.QListWidget = self.find_widget(QtWidgets.QListWidget, name) + return child + + def find_tool_button(self, name: str) -> QtWidgets.QToolButton: + """ + convenient method of find_widget for QToolButton + :param name: (str) name of the QToolButton Object + :return: (QToolButton) reference to the QToolButton + """ + child: QtWidgets.QToolButton = self.find_widget(QtWidgets.QToolButton, name) + return child + Index: engine/engine.py =================================================================== diff -u --- engine/engine.py (revision 0) +++ engine/engine.py (revision d711fdaf5fdcd83ec84ec7a05965768429483c29) @@ -0,0 +1,147 @@ +""" +the plugin loader class +""" +# PySide2 import +from PySide2 import QtWidgets +from PySide2.QtCore import Qt + +# simulator imports +from engine.dynamicloader import DynamicLoader + +# it loads all the classes in plugin folder dynamically +# so used * in here to load all +from plugins import * + + +class Engine(DynamicLoader): + """ + The simulator class which loads the ui file dynamically and initializes the objects + and can be eventually shown. + Note: this class is growing fast and seems like needs to be multiple classes + """ + mdiArea: QtWidgets.QMdiArea + menuBar: QtWidgets.QMenuBar + menuWindows: QtWidgets.QMenu + lblStatusCANBus: QtWidgets.QLabel + lblStatusMessages: QtWidgets.QLabel + + plugins = [] + + def __init__(self): + super().__init__(os.path.dirname(__file__)) + + self.__init_plugins() + self.__init_actions() + + def _init_loader(self): + """ + initializes the class by calling it's initializer methods to make objects ready + :return: none + """ + self.lblStatusCANBus = self.find_label('lblStatusCANBus') + self.lblStatusMessages = self.find_label('lblStatusMessages') + self.mdiArea = self.find_widget(QtWidgets.QMdiArea, 'mdiArea') + self.menuBar = self.find_widget(QtWidgets.QMenuBar, 'menuBar') + self.menuWindows = self.find_widget(QtWidgets.QMenu, 'menuWindows') + self.action_show_all = self.find_action('actionShowAll') + + def _init_widgets(self): + pass + + def _init_connections(self): + pass + + def __init_plugins(self): + """ + initializes the widgets' properties + :return: none + """ + # loading/registering plugins + # the loaded_plugins has been filled and exposed + # from within the __init__.py in the plugins folder + # folders with '__' and '.' have been ignored. + for plugin in available_plugins: + self.plugins.append(eval(plugin)()) + self.__register_plugin() + + def __init_actions(self): + """ + initializes the widgets connections + :return: none + """ + self.action_show_all.toggled.connect(self.set_all_windows_visible) + + def __create_actions(self, name: str, window: QtWidgets.QWidget): + """ + creates a menu action item with the name/title name and set to show/hide the plugin form object + :param name: the action menu title + :param window: the object to show/hide + :return: false if the object is None + """ + if window is None: + return False + + top_menu_name = "View" + sub_menu_name = name + names = name.split("/") + if len(names) > 1: + top_menu_name = names[0] + sub_menu_name = names[1] + + menu = None + if top_menu_name != "": + found = False + for item in self.menuBar.actions(): + if item.text().replace('&', '').strip().lower() == top_menu_name.replace('&', '').strip().lower(): + menu = item.menu() + found = True + break + + if not found: + menu = self.menuBar.addMenu(top_menu_name) + + action = menu.addAction(sub_menu_name) + action.triggered.connect(lambda: self.__action_triggered(window)) + self.action_show_all.toggled.connect(action.setChecked) + + def __action_triggered(self, window): + if window.isVisible(): + if self.mdiArea.activeSubWindow() == window: + window.setVisible(False) + else: + self.mdiArea.setActiveSubWindow(window) + else: + window.setVisible(True) + + def __register_plugin(self): + """ + creates the plugin object and adds it to the mdi and creates an action menu for it + :param obj: the plugin object + :return: False if the passed obj is None + """ + + titles = {} + for obj in self.plugins: + titles[obj.window.windowTitle()] = obj + + for title in sorted(titles): + wgt = titles[title].window + sub = self.mdiArea.addSubWindow(wgt) + + sub.setWindowFlags(Qt.WindowMinimizeButtonHint | Qt.WindowTitleHint) + sub.setWindowFlag(Qt.WindowMaximizeButtonHint, obj.canMaximize) + sub.setVisible(obj.isVisible) + + self.__create_actions(title, sub) + wgt.setWindowTitle(wgt.setWindowTitle(title.replace('&', ''))) + + def set_all_windows_visible(self, visible: bool): + """ + sets all the windows (in)visible + :param visible: if true the windows are set to visible + :return: None + """ + for sub in self.mdiArea.subWindowList(): + sub.setVisible(visible) + self.mdiArea.cascadeSubWindows() + Index: engine/interface.ui =================================================================== diff -u --- engine/interface.ui (revision 0) +++ engine/interface.ui (revision d711fdaf5fdcd83ec84ec7a05965768429483c29) @@ -0,0 +1,163 @@ + + + MainWindow + + + + 0 + 0 + 800 + 600 + + + + Firmware Simulator + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + 1 + + + + + + 0 + 0 + + + + QFrame::StyledPanel + + + + + + + + + + QFrame::StyledPanel + + + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + + + 20 + 49 + 76 + + + + + + + + + + + 0 + 0 + 800 + 22 + + + + + &Windows + + + + + + + + + true + + + Cascade + + + true + + + + + true + + + Show All + + + + + + + + + actionCascade + triggered() + mdiArea + cascadeSubWindows() + + + -1 + -1 + + + 600 + 409 + + + + + Index: engine/resources.qrc =================================================================== diff -u --- engine/resources.qrc (revision 0) +++ engine/resources.qrc (revision d711fdaf5fdcd83ec84ec7a05965768429483c29) @@ -0,0 +1,5 @@ + + + diality-logo.jpg + + Index: plugins =================================================================== diff -u --- plugins (revision 0) +++ plugins (revision d711fdaf5fdcd83ec84ec7a05965768429483c29) @@ -0,0 +1 @@ +../plugins \ No newline at end of file Index: run.sh =================================================================== diff -u --- run.sh (revision 0) +++ run.sh (revision d711fdaf5fdcd83ec84ec7a05965768429483c29) @@ -0,0 +1,17 @@ +#!/bin/sh +location() { + if [ -L "$0" ]; then + local filename="$0" + local linkinfo=$(readlink $filename) + local filepath=$(dirname $linkinfo) + echo $filepath + else + echo $(dirname "$0") + fi +} + +cd $(location) + +export QT_QPA_PLATFORM=xcb +export XDG_SESSION_TYPE=xcb +python3 ./simulator.py $1 $2 $3 $4 $5 $6 & Index: setup.py =================================================================== diff -u --- setup.py (revision 0) +++ setup.py (revision d711fdaf5fdcd83ec84ec7a05965768429483c29) @@ -0,0 +1,15 @@ +import setuptools +from xlrd.timemachine import python_version + +if __name__ == '__main__': + setuptools.setup( + name="simulator", + python_version="~3.8", + entry_points = { + 'console_scripts': ['clinical=clinical.simulator:main'], + }, + install_requires = [ + "PySide2", + "openpyxl" + ] + ) Index: simulator.py =================================================================== diff -u --- simulator.py (revision 0) +++ simulator.py (revision d711fdaf5fdcd83ec84ec7a05965768429483c29) @@ -0,0 +1,32 @@ +""" +the main function +to load the main simulator loader widget +""" +import sys +from PySide2 import QtCore, QtWidgets +from engine.engine import Engine + +# https://pyinstaller.org/en/stable/spec-files.html +# https://www.pythonguis.com/tutorials/packaging-pyside2-applications-windows-pyinstaller/ +# https://forum.qt.io/topic/148718/qt-qpa-plugin-could-not-load-the-qt-platform-plugin-xcb-in-even-though-it-was-found + +def main(): + """ + the main function which initializes the Simulator and starts it. + :return: none + """ + # create qt application + app = QtWidgets.QApplication(sys.argv) + + engine = Engine() + + engine.show() + + # start qt application main loop + sys.exit(app.exec_()) + + +if __name__ == "__main__": + QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_ShareOpenGLContexts) + + main() Index: simulator.spec =================================================================== diff -u --- simulator.spec (revision 0) +++ simulator.spec (revision d711fdaf5fdcd83ec84ec7a05965768429483c29) @@ -0,0 +1,44 @@ +# -*- mode: python ; coding: utf-8 -*- + + +a = Analysis( + ['simulator.py'], + pathex=[], + binaries=[], + datas=[('engine', 'engine'), ('plugins', 'plugins')], + hiddenimports=['openpyxl'], + hookspath=[], + hooksconfig={}, + runtime_hooks=[], + excludes=[], + noarchive=False, + optimize=0, +) +pyz = PYZ(a.pure) + +exe = EXE( + pyz, + a.scripts, + [], + exclude_binaries=True, + name='simulator', + debug=False, + bootloader_ignore_signals=False, + strip=False, + upx=True, + console=False, + disable_windowed_traceback=False, + argv_emulation=False, + target_arch=None, + codesign_identity=None, + entitlements_file=None, +) +coll = COLLECT( + exe, + a.binaries, + a.datas, + strip=False, + upx=True, + upx_exclude=[], + name='simulator', +)