Newer
Older
Contains the scheduler-gui class for scheduling operations in an SFG.
from importlib.machinery import SourceFileLoader
import inspect
from qtpy.QtCore import QCoreApplication, Qt, Slot, Signal, QSettings, QStandardPaths
QApplication, QMainWindow, QMessageBox, QFileDialog, QInputDialog, QCheckBox, QAbstractButton,
QRect, QRectF, QPoint, QSize, QByteArray, QMarginsF, QObject)
from qtpy.QtGui import (
QPaintEvent, QPainter, QPainterPath, QColor, QBrush, QPen, QFont, QPolygon, QIcon, QPixmap,
QLinearGradient)
QGraphicsView, QGraphicsScene, QGraphicsWidget, QGraphicsScale,
QGraphicsLayout, QGraphicsLinearLayout, QGraphicsGridLayout, QGraphicsLayoutItem, QGraphicsAnchorLayout,
from b_asic.schedule import Schedule
from graphics_graph_item import GraphicsGraphItem
from graphics_axis_item import GraphicsAxisItem
from graphics_component_item import GraphicsComponentItem
# if sys.version_info >= (3, 9):
# List = list
# #Dict = dict
if __debug__:
log.setLevel('DEBUG')
# Print some system version information
QT_API = os.environ.get('QT_API')
log.debug('Qt version (runtime): {}'.format(QtCore.qVersion()))
log.debug('Qt version (compiletime): {}'.format(QtCore.__version__))
log.debug('QT_API: {}'.format(QT_API))
if QT_API.lower().startswith('pyside'):
import PySide2
log.debug('PySide version: {}'.format(PySide2.__version__))
if QT_API.lower().startswith('pyqt'):
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
uic.compileUiDir('.', map=(lambda dir,file: (dir, 'ui_' + file)))
except:
try: # PySide2, try manual compile
import subprocess
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
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'")
sys.path.insert(0, 'icons/') # Needed for the compiled '*_rc.py' files in 'ui_*.py' files
from ui_main_window import Ui_MainWindow # Only availible when the form (.ui) is compiled
# The folowing QCoreApplication values is used for QSettings among others
QCoreApplication.setOrganizationName('Linöping University')
QCoreApplication.setOrganizationDomain('liu.se')
QCoreApplication.setApplicationName('B-ASIC Scheduler')
#QCoreApplication.setApplicationVersion(__version__) # TODO: read from packet __version__
class MainWindow(QMainWindow, Ui_MainWindow):
log.debug('themeName: \'{}\''.format(QIcon.themeName()))
log.debug('themeSearchPaths: {}'.format(QIcon.themeSearchPaths()))
self.menu_load_from_file.triggered .connect(self._load_schedule_from_pyfile)
self.menu_save .triggered .connect(self.save)
self.menu_save_as .triggered .connect(self.save_as)
self.menu_quit .triggered .connect(self.close)
self.menu_node_info .triggered .connect(self.toggle_component_info)
self.menu_exit_dialog .triggered .connect(self.toggle_exit_dialog)
self.splitter .splitterMoved .connect(self._splitter_moved)
# Setup event member functions
self.closeEvent = self._close_event
self.info_table.setHorizontalHeaderLabels(['Property','Value'])
# test = '#b085b2'
# self.info_table.setStyleSheet('alternate-background-color: lightGray;background-color: white;')
self.info_table.setStyleSheet('alternate-background-color: #fadefb;background-color: #ebebeb;')
self.info_table.insertRow(i)
item = QTableWidgetItem('this is a very very very very long string that says abolutly nothing')
self.info_table.setItem(i,0, QTableWidgetItem('property {}: '.format(i)))
self.info_table.setItem(i,1,item)
self.splitter.setStretchFactor(0, 1)
self.splitter.setStretchFactor(1, 0)
self.splitter.setCollapsible(0, False)
self.splitter.setCollapsible(1, True)
self.view.setScene(self._scene)
self.view.scale(self._scale, self._scale)
GraphicsComponentItem._scale = self._scale
GraphicsAxisItem._scale = self._scale
self._scene.changed.connect(self.shrink_scene_to_min_size)
###############
#### Slots ####
###############
self._graph.schedule.plot_schedule()
print(f'filtersChildEvents(): {self._graph.filtersChildEvents()}')
def _load_schedule_from_pyfile(self) -> None:
settings = QSettings()
# open_dir = QStandardPaths.standardLocations(QStandardPaths.HomeLocation)[0] if not self._open_file_dialog_opened else ''
last_file = settings.value('mainwindow/last_opened_file', QStandardPaths.standardLocations(QStandardPaths.HomeLocation)[0], str)
if not os.path.exists(last_file): # if filename does not exist
last_file = os.path.dirname(last_file) + '/'
if not os.path.exists(last_file): # if path does not exist
last_file = QStandardPaths.standardLocations(QStandardPaths.HomeLocation)[0]
abs_path_filename, _ = QFileDialog.getOpenFileName(self,
if not abs_path_filename: # return if empty filename (QFileDialog was canceled)
return
log.debug('abs_path_filename = {}.'.format(abs_path_filename))
module_name = inspect.getmodulename(abs_path_filename)
if not module_name: # return if empty module name
log.error('Could not load module from file \'{}\'.'.format(abs_path_filename))
return
try:
module = SourceFileLoader(module_name, abs_path_filename).load_module()
except:
log.exception('Exception occurred. Could not load module from file \'{}\'.'.format(abs_path_filename))
return
schedule_obj_list = dict(inspect.getmembers(module, (lambda x: isinstance(x, Schedule))))
if not schedule_obj_list: # return if no Schedule objects in script
QMessageBox.warning(self,
self.tr('File not found'),
self.tr('Could not find any Schedule object in file \'{}\'.')
.format(os.path.basename(abs_path_filename)))
log.info('Could not find any Schedule object in file \'{}\'.'
.format(os.path.basename(abs_path_filename)))
return
ret_tuple = QInputDialog.getItem(self,
self.tr('Load object'),
self.tr('Found the following Schedule object(s) in file.)\n\n'
'Select an object to proceed:'),
schedule_obj_list.keys(),0,False)
if not ret_tuple[1]: # User canceled the operation
log.debug('Load schedule operation: user canceled')
settings.setValue("mainwindow/last_opened_file", abs_path_filename)
#@Slot()
def open(self, schedule: Schedule) -> None:
"""Takes in an Schedule and creates a GraphicsGraphItem object."""
self._graph = GraphicsGraphItem(schedule)
self._scene.addItem(self._graph)
# graph.prepareGeometryChange()
# graph.setPos(200, 20)
# self._scene.setSceneRect(self._scene.itemsBoundingRect()) # Forces the scene to it's minimum size
# # Debug rectangles
# # self._scene.setSceneRect(self._scene.itemsBoundingRect()) # Forces the scene to it's minimum size
# m = QMarginsF(1/self._scale, 1/self._scale, 0, 0)
# m2 = QMarginsF(1, 1, 1, 1)
# pen.setCosmetic(True)
# for component in graph.items:
# self._scene.addRect(component.mapRectToScene(component.boundingRect() - m), pen)
# pen.setColor(Qt.red)
# self._scene.addRect(axis.mapRectToScene(axis.boundingRect() - m), pen)
# # self._scene.addRect(self._scene.itemsBoundingRect() - m, pen)
# self._scene.setSceneRect(self._scene.itemsBoundingRect())
self.update_statusbar(self.tr('Schedule loaded successfully'))
def save(self) -> None:
"""This method save an schedule."""
#TODO: all
self.update_statusbar(self.tr('Schedule saved successfully'))
@Slot()
def save_as(self) -> None:
"""This method save as an schedule."""
#TODO: all
self.printButtonPressed('save_schedule()')
self.update_statusbar(self.tr('Schedule saved successfully'))
"""This method toggles the right hand side info window."""
# Note: splitter handler index 0 is a hidden splitter handle far most left, use index 1
settings = QSettings()
self.splitter.restoreState(settings.value("mainwindow/splitter/last_state"))
# self.splitter.restoreState(settings.value("splitterSizes"))
settings.setValue("mainwindow/splitter/last_state", self.splitter.saveState())
self.splitter.moveSplitter(range[1], 1)
s = QSettings()
s.setValue("mainwindow/hide_exit_dialog", checked)
"""Callback method used to check if the right widget (info window)
has collapsed. Update the checkbutton accordingly."""
# TODO: Custom move handler, save state on click-release?
if widths[1] == 0:
self.menu_node_info.setChecked(False)
else:
self.menu_node_info.setChecked(True)
def shrink_scene_to_min_size(self, region: List[QRectF]) -> None:
self._scene.setSceneRect(self._scene.itemsBoundingRect())
################
#### Events ####
################
"""Replaces QMainWindow default closeEvent(QCloseEvent) event"""
s = QSettings()
hide_dialog = s.value('mainwindow/hide_exit_dialog', False, bool)
ret = QMessageBox.StandardButton.Yes
if not hide_dialog:
box = QMessageBox(self)
box.setWindowTitle(self.tr('Confirm Exit'))
box.setText('<h3>' + self.tr('Confirm Exit') + '</h3><p><br>' +
self.tr('Are you sure you want to exit?') +
' <br></p>')
box.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
buttons: list[QAbstractButton] = box.buttons()
buttons[0].setText(self.tr('&Exit'))
buttons[1].setText(self.tr('&Cancel'))
if not hide_dialog:
s.setValue('mainwindow/hide_exit_dialog', checkbox.isChecked())
self._write_settings()
log.info('Exit: {}'.format(os.path.basename(__file__)))
#################################
#### Helper member functions ####
#################################
def printButtonPressed(self, func_name: str) -> None:
alert = QMessageBox(self)
alert.setText("Called from " + func_name + '!')
alert.exec_()
def update_statusbar(self, msg: str) -> None:
"""Write the given str to the statusbar with temporarily policy."""
def _write_settings(self) -> None:
"""Write settings from MainWindow to Settings."""
s = QSettings()
s.setValue('mainwindow/maximized', self.isMaximized()) # window: maximized, in X11 - alwas False
s.setValue('mainwindow/pos', self.pos()) # window: pos
s.setValue('mainwindow/size', self.size()) # window: size
s.setValue('mainwindow/state', self.saveState()) # toolbars, dockwidgets: pos, size
s.setValue('mainwindow/menu/node_info', self.menu_node_info.isChecked())
s.setValue('mainwindow/splitter/state', self.splitter.saveState())
if s.isWritable():
log.debug('Settings written to \'{}\'.'.format(s.fileName()))
else:
log.warning('Settings cant be saved to file, read-only.')
def _read_settings(self) -> None:
"""Read settings from Settings to MainWindow."""
s = QSettings()
if s.value('mainwindow/maximized', defaultValue=False, type=bool):
self.showMaximized()
else:
self.move( s.value('mainwindow/pos', self.pos()))
self.resize( s.value('mainwindow/size', self.size()))
self.restoreState( s.value('mainwindow/state', QByteArray()))
self.menu_node_info.setChecked( s.value('mainwindow/menu/node_info', True, bool))
self.splitter.restoreState( s.value('mainwindow/splitter/state', QByteArray()))
self.menu_exit_dialog.setChecked( s.value('mainwindow/hide_exit_dialog', False, bool))
log.debug('Settings read from \'{}\'.'.format(s.fileName()))
sys.exit(app.exec_())
if __name__ == "__main__":
start_gui()