diff --git a/b_asic/scheduler_gui/compile.py b/b_asic/scheduler_gui/compile.py new file mode 100644 index 0000000000000000000000000000000000000000..c3db4f7e2c80ae7b1eaf7d9a1204370317e8d242 --- /dev/null +++ b/b_asic/scheduler_gui/compile.py @@ -0,0 +1,216 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +"""B-ASIC Scheduler-gui Resource and Form Compiler Module. + +Compiles Qt5 resource and form files. Requires PySide2 or PyQt5 to be installed. +If no arguments is given, the compiler search for and compiles all form (.ui) +files. +""" + +from qtpy import uic, API +import sys +import os +import shutil +import subprocess +import argparse +import b_asic.scheduler_gui +import b_asic.scheduler_gui.logger as logger + +log = logger.getLogger() +sys.excepthook = logger.handle_exceptions + + +def _check_filenames(*filenames: str) -> None: + """Check if the filename(s) exist and if not, raise 'FileNotFoundError' + exception.""" + for filename in filenames: + if not os.path.exists(filename): + raise FileNotFoundError(filename) + + +def _check_qt_version() -> None: + """Checks if PySide2 or PyQt5 is installed, raises AssertionError otherwise.""" + assert uic.PYSIDE2 or uic.PYQT5, 'PySide2 or PyQt5 need to be installed' + + +def replace_qt_bindings(filename: str) -> None: + """Raplaces qt-binding api in 'filename' from PySide2/PyQt5 to qtpy.""" + with open(f'{filename}', 'r') as file: + filedata = file.read() + filedata = filedata.replace('from PyQt5', 'from qtpy') + filedata = filedata.replace('from PySide2', 'from qtpy') + with open(f'{filename}', 'w') as file: + file.write(filedata) + + +def compile_rc(*filenames: str) -> None: + """Compile resource file(s) given by 'filenames'. If no arguments are given, + the compiler will search for '*.qrc' files in 'icons\' folder and compile + accordingly.""" + _check_qt_version() + + def compile(filename: str = None) -> None: + outfile = f'{os.path.splitext(filename)[0]}_rc.py' + os_ = sys.platform + + rcc = shutil.which('pyside2-rcc') + args = f'-g python -o {outfile} {filename}' + if rcc is None: + rcc = shutil.which('rcc') + if rcc is None: + rcc = shutil.which('pyrcc5') + args = f'-o {outfile} {filename}' + assert rcc, "PySide2 compiler failed, can't find rcc" + + if os_.startswith("linux"): # Linux + cmd = f'{rcc} {args}' + subprocess.call(cmd.split()) + + elif os_.startswith("win32"): # Windows + # TODO: implement + log.error('Windows RC compiler not implemented') + raise NotImplementedError + + elif os_.startswith("darwin"): # macOS + # TODO: implement + log.error('macOS RC compiler not implemented') + raise NotImplementedError + + else: # other OS + log.error(f'{os_} RC compiler not supported') + raise NotImplementedError + + replace_qt_bindings(outfile) # replace qt-bindings with qtpy + + if not filenames: + rc_files = [os.path.join(root, name) + for root, dirs, files in os.walk('.') + for name in files + if name.endswith(('.qrc'))] + + for filename in rc_files: + compile(filename) + + else: + _check_filenames(*filenames) + for filename in filenames: + compile(filename) + + +def compile_ui(*filenames: str) -> None: + """Compile form file(s) given by 'filenames'. If no arguments are given, + the compiler will search for '*.ui' files and compile accordingly.""" + _check_qt_version() + + def compile(filename: str) -> None: + dir, file = os.path.split(filename) + file, _ = os.path.splitext(file) + dir = dir if dir else '.' + outfile = f'{dir}/ui_{file}.py' + + if uic.PYSIDE2: + os_ = sys.platform + + uic_ = shutil.which('pyside2-uic') + args = f'-g python -o {outfile} {filename}' + if uic_ is None: + uic_ = shutil.which('uic') + if uic_ is None: + uic_ = shutil.which('pyuic5') + args = f'-o {outfile} {filename}' + assert uic_, "PySide2 compiler failed, can't find uic" + + if os_.startswith("linux"): # Linux + cmd = f'{uic_} {args}' + subprocess.call(cmd.split()) + + elif os_.startswith("win32"): # Windows + # TODO: implement + log.error('Windows UI compiler not implemented') + raise NotImplementedError + + elif os_.startswith("darwin"): # macOS + # TODO: implement + log.error('macOS UI compiler not implemented') + raise NotImplementedError + + else: # other OS + log.error(f'{os_} UI compiler not supported') + raise NotImplementedError + + else: # uic.PYQT5 + from qtpy.uic import compileUi + with open(outfile, 'w') as ofile: + compileUi(filename, ofile) + + replace_qt_bindings(outfile) # replace qt-bindings with qtpy + + if not filenames: + ui_files = [os.path.join(root, name) + for root, dirs, files in os.walk('.') + for name in files + if name.endswith(('.ui'))] + for filename in ui_files: + compile(filename) + else: + _check_filenames(*filenames) + for filename in filenames: + compile(filename) + + +def compile_all(): + """The compiler will search for '*.qrc* resource files in 'icons\' folder + and for '*.ui' form files and compile accordingly.""" + compile_rc() + compile_ui() + + +if __name__ == '__main__': + ver = b_asic.scheduler_gui.__version__ + descr = __doc__ + + parser = argparse.ArgumentParser(description=f'{descr}', + formatter_class=argparse.RawTextHelpFormatter) + + parser.add_argument('-v', '--version', + action='version', + version=f'%(prog)s v{ver}') + + if sys.version_info >= (3, 8): + parser.add_argument('--ui', + metavar='<file>', + action='extend', + nargs='*', + help="compile form file(s) if <file> is given, otherwise search\n" + "for all form (*.ui) files and compile them all (default)") + parser.add_argument('--rc', + metavar='<file>', + action='extend', + nargs='*', + help="compile resource file(s) if <file> is given, otherwise\n" + "search for all resource (*.ui) files and compile them all") + else: + parser.add_argument('--ui', + metavar='<file>', + action='append', + help="compile form file") + parser.add_argument('--rc', + metavar='<file>', + action='append', + help="compile resource file") + + parser.add_argument('--all', + action='store_true', + help="search and compile all resource and form file(s)") + + if len(sys.argv) == 1: + compile_ui() + + args = parser.parse_args() + + if args.ui is not None: + compile_ui(*args.ui) + if args.rc is not None: + compile_rc(*args.rc) + if args.all: + compile_all() diff --git a/b_asic/scheduler_gui/main_window.py b/b_asic/scheduler_gui/main_window.py index e752a0eb4299a551d130c4872560c7f84fd696be..29ef7253beefd0dbe253084017392f3dd18779ad 100644 --- a/b_asic/scheduler_gui/main_window.py +++ b/b_asic/scheduler_gui/main_window.py @@ -47,10 +47,8 @@ from b_asic.schedule import Schedule from b_asic.scheduler_gui.graphics_axes_item import GraphicsAxesItem from b_asic.scheduler_gui.graphics_component_item import GraphicsComponentItem from b_asic.scheduler_gui.graphics_graph_item import GraphicsGraphItem - -# if sys.version_info >= (3, 9): -# List = list -# #Dict = dict +sys.path.insert(0, "icons/") # Needed for *.rc.py files in ui_main_window +from b_asic.scheduler_gui.ui_main_window import Ui_MainWindow log = logger.getLogger() sys.excepthook = logger.handle_exceptions @@ -78,43 +76,6 @@ if __debug__: log.debug("PyQt version: {}".format(PYQT_VERSION_STR)) log.debug("QtPy version: {}".format(qtpy.__version__)) - # Autocompile the .ui form to a python file. - try: # PyQt5, try autocompile - from qtpy.uic import compileUiDir - - compileUiDir(".", map=(lambda dir, file: (dir, "ui_" + file))) - except: - try: # PySide2, try manual compile - import subprocess - - os_ = sys.platform - if os_.startswith("linux"): - cmds = ["pyside2-uic -o ui_main_window.py main_window.ui"] - for cmd in cmds: - subprocess.call(cmd.split()) - else: - # TODO: Implement (startswith) 'win32', 'darwin' (MacOs) - raise SystemExit - except: # Compile failed, look for pre-compiled file - try: - from b_asic.scheduler_gui.ui_main_window import Ui_MainWindow - except: # Everything failed, exit - log.exception("Could not import 'Ui_MainWindow'.") - log.exception( - "Can't autocompile under", - QT_API, - "eviroment. Try to manual compile 'main_window.ui' to" - " 'ui/main_window_ui.py'", - ) - os._exit(1) - - -sys.path.insert( - 0, "icons/" -) # Needed for the compiled '*_rc.py' files in 'ui_*.py' files -from b_asic.scheduler_gui.ui_main_window import ( - Ui_MainWindow, -) # Only availible when the form (.ui) is compiled # The following QCoreApplication values is used for QSettings among others QCoreApplication.setOrganizationName("Linöping University")