1005 lines
39 KiB
Python
1005 lines
39 KiB
Python
""" Base accelerator module
|
|
"""
|
|
import argparse
|
|
from collections import OrderedDict
|
|
import datetime
|
|
import inspect
|
|
import platform
|
|
import os
|
|
import sys
|
|
import time
|
|
|
|
# Third-party modules
|
|
from qtpy.QtCore import (PYQT_VERSION_STR, Signal, Slot, QFile, QFileInfo,
|
|
QIODevice, QMutex, QSettings, QSize, Qt, QTemporaryDir,
|
|
QThread, QTimer, qVersion)
|
|
from qtpy.QtCore import __version__ as QT_VERSION_STR
|
|
from qtpy.QtGui import (QColor, QKeySequence, QFont, QIcon, QPainter, QPalette,
|
|
QPixmap)
|
|
from qtpy.QtPrintSupport import QPrinter, QPrintDialog
|
|
from qtpy.QtWidgets import (QAbstractItemView, QAction, QActionGroup,
|
|
QApplication, QButtonGroup, QComboBox, QDialog,
|
|
QDockWidget, QDoubleSpinBox, QFileDialog, QFrame,
|
|
QGridLayout, QGroupBox, QHBoxLayout, QLabel,
|
|
QLayout, QLineEdit, QListWidget, QMainWindow, QMenu,
|
|
QMenuBar, QMessageBox, QPlainTextEdit, QProgressBar,
|
|
QPushButton, QScrollArea, QSizePolicy, QSlider,
|
|
QSpinBox, QSplashScreen, QStyle, QStyleOptionSlider,
|
|
QToolButton, QVBoxLayout, QWidget)
|
|
|
|
from matplotlib.backends.backend_qt5agg import (
|
|
FigureCanvasQTAgg, NavigationToolbar2QT as NavigationToolbar)
|
|
|
|
|
|
import pyqtacc.bdbase.pyrcc5.qrc_resources
|
|
from pyqtacc.bdbase.enumkind import Facility, MsgSeverity, UserMode
|
|
from pyqtacc.bdbase.helpbrowser import HelpBrowser
|
|
from pyqtacc.bdbase.readjson import ReadJSON
|
|
from pyqtacc.bdbase.savehdf import QSaveHDF
|
|
from pyqtacc.bdbase.hdf5filemenu import HDF5GroupBox
|
|
from pyqtacc.bdbase.sendelog import QSendToELOG
|
|
from pyqtacc.bdbase.screenshot import QScreenshot
|
|
from pyqtacc.bdbase.guiframe import GUIFrame
|
|
|
|
from pyqtacc.sf.enumkind import ElogSwissFEL
|
|
|
|
from caqtwidgets.pvwidgets import QHDFDockWidget, QNoDockWidget
|
|
|
|
import PyCafe
|
|
_pymodule = os.path.basename(__file__)
|
|
_appname, _appext = _pymodule.split(".")
|
|
_appversion = "1.0.0"
|
|
|
|
PROGRESS_BAR_THREAD_INIT = 0
|
|
PROGRESS_BAR_THREAD_START = 1
|
|
PROGRESS_BAR_THREAD_ABORTING = 2
|
|
PROGRESS_BAR_THREAD_ABORTED = 3
|
|
PROGRESS_BAR_THREAD_ERROR = 4
|
|
PROGRESS_BAR_THREAD_END = 100
|
|
|
|
CENTRAL_WIDGET_MINIMUM_HEIGHT = 840
|
|
CENTRAL_WIDGET_MINIMUM_WIDTH = 1240
|
|
|
|
def _line():
|
|
"""Macro to return the current line number.
|
|
|
|
The current line number within the file is used when
|
|
reporting messages to the message logging window.
|
|
|
|
Returns:
|
|
int: Current line number.
|
|
"""
|
|
return inspect.currentframe().f_back.f_lineno
|
|
|
|
|
|
class BaseWindow(QMainWindow):
|
|
""" BaseWindow
|
|
"""
|
|
####trigger_elog_entry = Signal(bool, str, str)
|
|
trigger_log_message = Signal(str, str, int, str, dict)
|
|
trigger_progressbar = Signal(int)
|
|
|
|
class LoadH5Thread(QThread):
|
|
"""Thread for hdf5 analysis
|
|
"""
|
|
trigger_thread_event = Signal(dict)
|
|
|
|
def __init__(self, parent, analysis_procedure):
|
|
QThread.__init__(self)
|
|
self.parent = parent
|
|
self.analysis_procedure = analysis_procedure
|
|
self.h5_filename = self.parent.h5_filename
|
|
|
|
def __del__(self):
|
|
self.wait()
|
|
|
|
def run(self):
|
|
"""Run hdf5 thread
|
|
"""
|
|
#Open hdf5file here and
|
|
|
|
|
|
mess = "HDF file {} analysis proceeding...".format(
|
|
self.h5_filename)
|
|
self.parent.trigger_log_message.emit(MsgSeverity.INFO.name,
|
|
_pymodule, _line(),
|
|
mess, {})
|
|
|
|
all_dict = self.analysis_procedure.reanalyze(self.input_parameters)
|
|
|
|
# Emit results
|
|
if all_dict is not None:
|
|
self.trigger_thread_event.emit(all_dict)
|
|
mess = "HDF file {} analysis succeeded".format(self.h5_filename)
|
|
self.parent.trigger_log_message.emit(
|
|
MsgSeverity.INFO.name, _pymodule, _line(), mess, {})
|
|
else:
|
|
mess = "HDF file {} has wrong content!!".format(
|
|
self.h5_filename)
|
|
self.parent.trigger_log_message.emit(
|
|
MsgSeverity.ERROR.name, _pymodule, _line(), mess, {})
|
|
|
|
|
|
class AnalysisThread(QThread):
|
|
"""Analysis thread
|
|
"""
|
|
trigger_thread_event = Signal(dict)
|
|
|
|
def __init__(self, parent, analysis_procedure, input_parameters):
|
|
QThread.__init__(self)
|
|
self.parent = parent
|
|
self.analysis_procedure = analysis_procedure
|
|
self.input_parameters = input_parameters
|
|
print (self.input_parameters)
|
|
|
|
def __del__(self):
|
|
self.wait()
|
|
|
|
def run(self):
|
|
"""Run thread
|
|
"""
|
|
all_dict = self.analysis_procedure.measure_and_analyze(
|
|
self.input_parameters)
|
|
|
|
# Emit results
|
|
if all_dict is not None:
|
|
self.trigger_thread_event.emit(all_dict)
|
|
mess = "Analysis completed"
|
|
self.parent.trigger_log_message.emit(
|
|
MsgSeverity.INFO.name, _pymodule, _line(), mess, {})
|
|
else:
|
|
mess = "No data returned from analysis procedure."
|
|
self.parent.trigger_log_message.emit(
|
|
MsgSeverity.WARN.name, _pymodule, _line(), mess, {})
|
|
|
|
|
|
def __init__(self, parent=None, pymodule=None, appversion=None, title="",
|
|
user_mode=UserMode.OPERATION, facility=Facility.SwissFEL,
|
|
extended=True):
|
|
super(BaseWindow, self).__init__(parent)
|
|
self.parent = parent
|
|
self.pymodule = pymodule if pymodule else _pymodule
|
|
self.appversion = appversion if appversion else _appversion
|
|
self.title = title
|
|
self.facility = facility
|
|
self.user_mode = user_mode
|
|
print(self.pymodule)
|
|
print(self.appversion)
|
|
self.appname, self.appext = self.pymodule.split(".")
|
|
self.cafe = PyCafe.CyCafe()
|
|
self.cyca = PyCafe.CyCa()
|
|
self.cafe_exception = PyCafe.CafeException
|
|
self.cafe.enableExceptions = False
|
|
self.caget = self.cafe.caget
|
|
self.caput = self.cafe.caput
|
|
|
|
self.application_recent_files = []
|
|
self.application_geometry = None
|
|
|
|
self.filename = None
|
|
|
|
self.screenshot_titles = []
|
|
self.screenshot_items = []
|
|
|
|
self.simulation = False
|
|
|
|
|
|
self.setObjectName("MainWindow")
|
|
self.setWindowTitle(self.appname)
|
|
|
|
self.statusbar_label = QLabel()
|
|
self.statusbar = self.statusBar()
|
|
self.progressbar = QProgressBar(self)
|
|
|
|
self.menu = self.menuBar()
|
|
|
|
temp_dir = QTemporaryDir()
|
|
if temp_dir.isValid():
|
|
temp_file = temp_dir.path() + "/cNodes.xml"
|
|
if QFile.copy(":/cNodes.xml", temp_file):
|
|
status = self.cafe.loadCollectionsFromXML(temp_file)
|
|
if status != self.cyca.ICAFE_NORMAL:
|
|
options = {}
|
|
options['statusCode'] = "{0} {1}".format(
|
|
str(status), self.cafe.getStatusCodeAsString(status))
|
|
options['statusInfo'] = self.cafe.getStatusInfo(status)
|
|
print(options['statusCode'], options['statusInfo'])
|
|
else:
|
|
print("QFile copy failed for file {0}".format(temp_file))
|
|
else:
|
|
print("Invalid temporary directory {0}".format(temp_dir))
|
|
|
|
self.settings = ReadJSON(self.appname)
|
|
self.elog_dest = self.settings.data["Elog"]["destination"]
|
|
self.screenshot_dest = self.settings.data["screenshot"]["destination"]
|
|
self.stdlog_dest = (self.settings.data["stdlog"]["destination"] +
|
|
self.appname + ".log")
|
|
|
|
self.input_parameters = {}
|
|
self.input_labels = {}
|
|
#self.input_value_dict = {}
|
|
self.expert_parameters = {}
|
|
self.expert_labels = {}
|
|
|
|
|
|
for key, dictval in self.settings.data["Parameters"].items():
|
|
#print (key, dictval)
|
|
|
|
if self.settings.data["Parameters"][key]["flag"]:
|
|
if "value" in dictval["data"]:
|
|
self.input_parameters[key] = dictval["data"]["value"]
|
|
else:
|
|
try:
|
|
link_list = []
|
|
if 'forwardLink' in dictval["data"]:
|
|
link_list = dictval["data"]["forwardLink"]
|
|
elif 'link' in dictval["data"]:
|
|
link_list = dictval["data"]["link"]
|
|
print("link_list", link_list)
|
|
if len(link_list) > 1:
|
|
val = self.settings.data[link_list[0]]
|
|
for link in link_list[1:]:
|
|
val = val[link]
|
|
print("lval", val)
|
|
else:
|
|
val = []
|
|
for inner_key in self.settings.data[link_list[0]].keys():
|
|
val.append(inner_key)
|
|
self.input_parameters[key] = val
|
|
print("val=",val)
|
|
|
|
except KeyError as e:
|
|
print("Key Error in base.py initialization:", e)
|
|
|
|
self.input_labels[key] = dictval["data"]["text"]
|
|
#print(key, self.settings.data["Parameters"][key]["flag"])
|
|
|
|
print(self.input_parameters)
|
|
#sys.exit(1)
|
|
for key, dictval in self.settings.data["Expert"].items():
|
|
#print (key, dictval)
|
|
if self.settings.data["Expert"][key]["flag"]:
|
|
self.expert_parameters[key] = dictval["data"]["value"]
|
|
self.expert_labels[key] = dictval["data"]["text"]
|
|
|
|
self.analysis_thread = None
|
|
self.analysis_procedure = None
|
|
try:
|
|
from src.analysis import AnalysisProcedure
|
|
self.analysis_procedure = AnalysisProcedure(self)
|
|
except ImportError as e:
|
|
print("Import Error:", e)
|
|
|
|
self.loadh5_thread = None
|
|
|
|
###self.trigger_elog_entry.connect(self.receive_elog_notification)
|
|
|
|
"""
|
|
self.alarm_severity_color = {
|
|
self.cyca.SEV_NO_ALARM: self.settings.data['StyleGuide'][
|
|
'fgAlarmNoAlarm'],
|
|
self.cyca.SEV_MINOR: self.settings.data['StyleGuide'][
|
|
'fgAlarmMinor'],
|
|
self.cyca.SEV_MAJOR: self.settings.data['StyleGuide'][
|
|
'fgAlarmMajor'],
|
|
self.cyca.SEV_INVALID: self.settings.data['StyleGuide'][
|
|
'fgAlarmInvalid'],
|
|
self.cyca.ICAFE_CA_OP_CONN_DOWN: self.settings.data['StyleGuide'][
|
|
'fgAlarmInvalid']
|
|
}
|
|
"""
|
|
|
|
self.msg_severity_qcolor = {
|
|
MsgSeverity.FATAL: QColor(
|
|
self.settings.data["MsgSeverity"]["fatal"]),
|
|
MsgSeverity.ERROR: QColor(
|
|
self.settings.data["MsgSeverity"]["error"]),
|
|
MsgSeverity.WARN: QColor(
|
|
self.settings.data["MsgSeverity"]["warn"]),
|
|
MsgSeverity.WARNING: QColor(
|
|
self.settings.data["MsgSeverity"]["warn"]),
|
|
MsgSeverity.INFO: QColor(
|
|
self.settings.data["MsgSeverity"]["info"]),
|
|
MsgSeverity.DEBUG: QColor(
|
|
self.settings.data["MsgSeverity"]["debug"]),
|
|
MsgSeverity.FATAL.name: QColor(
|
|
self.settings.data["MsgSeverity"]["fatal"]),
|
|
MsgSeverity.ERROR.name: QColor(
|
|
self.settings.data["MsgSeverity"]["error"]),
|
|
MsgSeverity.WARN.name: QColor(
|
|
self.settings.data["MsgSeverity"]["warn"]),
|
|
MsgSeverity.WARNING.name: QColor(
|
|
self.settings.data["MsgSeverity"]["warn"]),
|
|
MsgSeverity.INFO.name: QColor(
|
|
self.settings.data["MsgSeverity"]["info"]),
|
|
MsgSeverity.DEBUG.name: QColor(
|
|
self.settings.data["MsgSeverity"]["debug"])
|
|
}
|
|
|
|
#QSetttings
|
|
self.restore_application_settings()
|
|
|
|
self.init_toolbar()
|
|
self.init_statusbar_wgt()
|
|
self.init_progressbar_wgt()
|
|
|
|
self.trigger_log_message.connect(self.receive_log_message)
|
|
self.trigger_progressbar.connect(self.receive_progressbar)
|
|
self.statusbar.addPermanentWidget(self.progressbar, 0)
|
|
|
|
####
|
|
self.appname, appext = self.pymodule.split(".")
|
|
|
|
self.mainwindow = QWidget()
|
|
self.mainwindow_layout = QVBoxLayout()
|
|
#self.mainwindow_layout.addLayout(self.gui_header.top_layout)
|
|
if self.facility == Facility.SwissFEL:
|
|
from pyqtacc.sf.guiheader import GUIHeader
|
|
self.gui_header = GUIHeader(self, user_mode=self.user_mode,
|
|
extended=extended)
|
|
self.mainwindow_layout.addWidget(self.gui_header.header_wgt)
|
|
|
|
self.gui_frame = GUIFrame(self, self.appname)
|
|
self.show_log_message = self.gui_frame.show_log_message
|
|
|
|
self.hdf_dock_widget = QNoDockWidget(" HDF5", self)
|
|
self.init_hdf_analysis_wgt()
|
|
|
|
self.mainwindow_layout.addWidget(self.gui_frame.central_tab_widget)
|
|
self.mainwindow.setLayout(self.mainwindow_layout)
|
|
self.mainwindow.setMinimumHeight(CENTRAL_WIDGET_MINIMUM_HEIGHT)
|
|
self.mainwindow.setMinimumWidth(CENTRAL_WIDGET_MINIMUM_WIDTH)
|
|
|
|
self.setCentralWidget(self.mainwindow)
|
|
self.show_log_message(MsgSeverity.INFO, _pymodule, _line(),
|
|
"Application configured")
|
|
|
|
def init_toolbar(self):
|
|
""" Prepare toolbar
|
|
"""
|
|
|
|
#menu / toolbar
|
|
font = self.menu.font()
|
|
font.setPointSize(12)
|
|
self.menu.setFont(font)
|
|
|
|
menu_file = self.menu.addMenu("&Output")
|
|
menu_file.setFont(font)
|
|
menu_file_actions = []
|
|
menu_view = self.menu.addMenu("&View")
|
|
menu_view.setFont(font)
|
|
menu_view_actions = []
|
|
menu_help = self.menu.addMenu("&Help")
|
|
menu_help.setFont(font)
|
|
menu_help_actions = []
|
|
|
|
file_toolbar = self.addToolBar("Output")
|
|
file_toolbar.setObjectName("FileToolBar")
|
|
view_toolbar = self.addToolBar("View")
|
|
view_toolbar.setObjectName("ViewToolBar")
|
|
|
|
if False:
|
|
menu_daq = self.menu.addMenu("&DAQ")
|
|
menu_daq_actions = []
|
|
daq_toolbar = self.addToolBar("DAQ")
|
|
daq_toolbar.setObjectName("ViewToolBar")
|
|
|
|
help_toolbar = self.addToolBar("Help")
|
|
help_toolbar.setObjectName("HelpToolBar")
|
|
|
|
a = False
|
|
|
|
if a:
|
|
epics_action = self.create_action(
|
|
"&EPICS", slot=self.write_to_epics, shortcut="Ctrl+Shift+E",
|
|
icon="epics", tip="Write to EPICS PVs")
|
|
menu_file_actions.append(epics_action)
|
|
if a:
|
|
hdf_action = self.create_action(
|
|
"Save&HDF", slot=self.save_to_hdf, shortcut="Ctrl+H",
|
|
icon="hdf", tip="Save as HDF")
|
|
menu_file_actions.append(hdf_action)
|
|
|
|
elog_action = self.create_action(
|
|
"&Elog", slot=self.send_to_elog, shortcut="Alt+E", icon="elog",
|
|
tip="Send to ELOG")
|
|
menu_file_actions.append(elog_action)
|
|
menu_file_actions.append(QAction().setSeparator(True))
|
|
|
|
screenshot_action = self.create_action(
|
|
"&Screenshot", slot=self.take_screenshot, shortcut="Alt+S",
|
|
icon="screenshot", tip="Screenshot of selected items")
|
|
menu_file_actions.append(screenshot_action)
|
|
|
|
print_action = self.create_action(
|
|
"&Print", slot=self.send_to_printer, shortcut=QKeySequence.Print,
|
|
icon="fileprint", tip="Send to printer")
|
|
menu_file_actions.append(print_action)
|
|
|
|
stdout_action = self.create_action(
|
|
"stdout &Log", slot=self.open_stdout_log, shortcut="Alt+L",
|
|
icon="filestdlog", tip="Open stdout log file")
|
|
menu_view_actions.append(stdout_action)
|
|
|
|
clear_log_action = self.create_action(
|
|
"&Clear log window", slot=self.clear_log_window, shortcut="Alt+C",
|
|
icon="viewclearlog", tip="Clears message log window")
|
|
menu_view_actions.append(clear_log_action)
|
|
|
|
quit_action = self.create_action(
|
|
"&Quit", slot=self.close, shortcut="Ctrl+Q", icon="filequit",
|
|
tip="Exit application, closing all windows")
|
|
menu_file_actions.append(QAction().setSeparator(True))
|
|
menu_file_actions.append(quit_action)
|
|
|
|
help_action = self.create_action(
|
|
"Help", slot=self.show_help, shortcut=QKeySequence.HelpContents,
|
|
icon="home", tip="Help!")
|
|
menu_help_actions.append(help_action)
|
|
|
|
about_action = self.create_action(
|
|
"About", slot=self.show_about, shortcut=QKeySequence.WhatsThis,
|
|
icon="helpabout", tip="About this app")
|
|
menu_help_actions.append(about_action)
|
|
|
|
self.add_actions(menu_file, tuple(menu_file_actions))
|
|
self.add_actions(menu_view, tuple(menu_view_actions))
|
|
self.add_actions(menu_help, tuple(menu_help_actions))
|
|
self.add_actions(file_toolbar, tuple(menu_file_actions[:-1]))
|
|
self.add_actions(view_toolbar, tuple(menu_view_actions))
|
|
self.add_actions(help_toolbar, tuple(menu_help_actions))
|
|
|
|
#toolbar
|
|
# Place ExitWidget on the far right
|
|
exit_toolbar = self.addToolBar("EXIT")
|
|
exit_toolbar.setObjectName("ExitToolBar")
|
|
spacer_wgt = QWidget()
|
|
spacer_wgt.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)
|
|
spacer_wgt.setVisible(True)
|
|
exit_toolbar.addWidget(spacer_wgt)
|
|
self.add_actions(exit_toolbar, (quit_action,))
|
|
|
|
################# Actions
|
|
def add_actions(self, target, actions):
|
|
""" Add to toolbar
|
|
"""
|
|
for action in actions:
|
|
if action is None:
|
|
target.addSeparator()
|
|
else:
|
|
target.addAction(action)
|
|
|
|
def create_action(self, text, slot=None, shortcut=None, icon=None, tip=None,
|
|
checkable=False, signal="triggered()"):
|
|
"""
|
|
"""
|
|
action = QAction(text, self)
|
|
if icon is not None:
|
|
action.setIcon(QIcon(":/{0}.png".format(icon)))
|
|
if shortcut is not None:
|
|
action.setShortcut(shortcut)
|
|
if tip is not None:
|
|
action.setToolTip(tip)
|
|
if slot is not None:
|
|
action.triggered.connect(slot)
|
|
if checkable:
|
|
action.setCheckable(True)
|
|
return action
|
|
|
|
def closeEvent(self, event):
|
|
""" Overrides QMainWindow method
|
|
"""
|
|
if self.analysis_thread is not None:
|
|
if self.analysis_thread.isRunning():
|
|
qmbox = QMessageBox()
|
|
qmbox.setText(("Measurement in progress." +
|
|
"Please try again in a few seconds."))
|
|
qmbox.exec()
|
|
return event.ignore()
|
|
#Close all dock widgets
|
|
|
|
#self.removeDockWidget(self.hdf_dock_widget)
|
|
|
|
self.save_application_settings()
|
|
QApplication.processEvents()
|
|
self.cafe.monitorStopAll()
|
|
time.sleep(0.05)
|
|
self.cafe.terminate()
|
|
time.sleep(0.05)
|
|
QApplication.closeAllWindows()
|
|
event.accept()
|
|
|
|
@Slot()
|
|
def clear_log_window(self):
|
|
""" Requests user confirmation before wiping out log message
|
|
window
|
|
"""
|
|
reply = QMessageBox.question(
|
|
self, "Clear Message Log",
|
|
"Do you wish to wipe out all messages from the log window?",
|
|
QMessageBox.Yes | QMessageBox.No)
|
|
if reply == QMessageBox.No:
|
|
return False
|
|
elif reply == QMessageBox.Yes:
|
|
return self.gui_frame.log_wgt.clear()
|
|
|
|
@Slot()
|
|
def open_stdout_log(self):
|
|
""" Slot to stdout_action
|
|
"""
|
|
log_title = self.appname + ": stdout"
|
|
sys.stdout.flush()
|
|
|
|
file = None
|
|
if QFile.exists(self.stdlog_dest):
|
|
file = open(self.stdlog_dest)
|
|
else:
|
|
message = "Error Log File: '{0}' does not exist!".format(
|
|
self.stdlog_dest)
|
|
QMessageBox.warning(self, log_title, message)
|
|
return
|
|
|
|
text = file.read()
|
|
file.close()
|
|
|
|
text_edit = QPlainTextEdit()
|
|
text_edit.setPlainText(text)
|
|
text_edit.setReadOnly(True)
|
|
|
|
bgcolor = ("QPlainTextEdit{background-color:" +
|
|
self.settings.data["StyleGuide"]["bgErrorLogFile"] + "}")
|
|
text_edit.setStyleSheet(bgcolor)
|
|
text_edit.verticalScrollBar().setValue(
|
|
text_edit.verticalScrollBar().maximum())
|
|
text_edit.horizontalScrollBar().setValue(
|
|
text_edit.horizontalScrollBar().maximum())
|
|
|
|
scroll_area = QScrollArea()
|
|
scroll_area.setBackgroundRole(QPalette.Light)
|
|
scroll_area.setWidgetResizable(True)
|
|
scroll_area.setMinimumHeight(600)
|
|
scroll_area.setMinimumWidth(700)
|
|
scroll_area.setWindowTitle(log_title)
|
|
scroll_area.verticalScrollBar().setValue(
|
|
scroll_area.verticalScrollBar().maximum())
|
|
scroll_area.horizontalScrollBar().setValue(
|
|
scroll_area.horizontalScrollBar().maximum())
|
|
scroll_area.verticalScrollBar().setSliderPosition(
|
|
scroll_area.verticalScrollBar().maximum())
|
|
scroll_area.horizontalScrollBar().setSliderPosition(
|
|
scroll_area.horizontalScrollBar().maximum())
|
|
scroll_area.setWidget(text_edit)
|
|
text_edit.scrollArea = scroll_area
|
|
|
|
layout = QVBoxLayout()
|
|
layout.addWidget(scroll_area)
|
|
layout.setContentsMargins(0, 0, 0, 0)
|
|
qdialog = QDialog(self)
|
|
qdialog.setWindowTitle(log_title)
|
|
qdialog.setLayout(layout)
|
|
qdialog.show()
|
|
|
|
|
|
@Slot()
|
|
def save_to_hdf(self):
|
|
|
|
""" Abstract method to be overwritten by user
|
|
"""
|
|
QM = QMessageBox()
|
|
QM.setText(
|
|
str(NotImplementedError("save_to_hdf method has not been " +
|
|
"implemented in this application. \n" +
|
|
"If not required, consider removing the " +
|
|
"icon from the application/config file."))
|
|
)
|
|
QM.exec()
|
|
|
|
|
|
@Slot()
|
|
def save_to_hdf_dialog(self):
|
|
""" This uses the widget interface to allow the user to enter
|
|
additional meta-data
|
|
"""
|
|
|
|
class QSaveHDF5(QSaveHDF):
|
|
"""local instanstiation"""
|
|
def save(self):
|
|
"""overriding save method"""
|
|
user_dict = self.get_data()
|
|
h5_filename = user_dict['Destination']
|
|
h5_handle = h5py.File(h5_filename, 'w')
|
|
for key in user_dict:
|
|
if key not in ['Destination']:
|
|
lowkey = key.lower()
|
|
h5_handle.create_dataset(
|
|
'metadata/'+lowkey.replace(' ', '_'),
|
|
data=user_dict[key])
|
|
'''
|
|
for key in self.parent.results_dict:
|
|
if key in ['Data1','Data2']:
|
|
h5_handle.create_dataset(
|
|
'data/' + key,
|
|
data = self.parent.results_dict[key])
|
|
'''
|
|
h5_handle.close()
|
|
self.close()
|
|
|
|
input_options = OrderedDict()
|
|
|
|
_now = datetime.datetime.now()
|
|
_date = _now.strftime("%m/%d/%Y, %H:%M:%S")
|
|
input_options['Date'] = _date
|
|
#QCombobox if list
|
|
#input_options['QComboBox'] = ['one', 'two', 'three']
|
|
#input_options['Comment'] = 'Please enter a comment'
|
|
|
|
QSaveHDF5(self, input_options=input_options)
|
|
|
|
@Slot()
|
|
def send_to_elog(self):
|
|
""" Response to elog_action
|
|
"""
|
|
if self.analysis_thread is not None:
|
|
if self.analysis_thread.isRunning():
|
|
qmbox = QMessageBox()
|
|
qmbox.setText(("Measurement in progress. " +
|
|
"Please try again in a few seconds."))
|
|
qmbox.exec()
|
|
return
|
|
|
|
_elog_sf = ElogSwissFEL()
|
|
_logbook = None
|
|
_category_idx = _elog_sf.category.MEASUREMENT
|
|
_domain_idx = _elog_sf.system.NONE
|
|
_system_idx = _elog_sf.system.BEAMDYNAMICS
|
|
_attach_files = []
|
|
_message = ""
|
|
|
|
QSendToELOG(self, logbook=_logbook, categoryIdx=_category_idx,
|
|
domainIdx=_domain_idx, sectionIdx=0, title=_title,
|
|
message=_message, attachFile=_attach_files)
|
|
QApplication.processEvents()
|
|
|
|
|
|
@Slot()
|
|
def write_to_epics(self):
|
|
""" Abstract method to be overwritten by user
|
|
"""
|
|
QM = QMessageBox()
|
|
QM.setText(
|
|
str(NotImplementedError("write_to_epics method has not been " +
|
|
"implemented in this application. \n" +
|
|
"If not required, consider removing the " +
|
|
"icon from the application/config file."))
|
|
)
|
|
QM.exec()
|
|
|
|
@Slot()
|
|
def send_to_printer(self):
|
|
""" Response to printer_action
|
|
"""
|
|
self.printer = QPrinter(QPrinter.HighResolution)
|
|
self.printer.setPaperSize(QPrinter.A4)
|
|
self.printer.setOrientation(QPrinter.Landscape)
|
|
form = QPrintDialog(self.printer, self)
|
|
if form.exec_():
|
|
painter = QPainter(self.printer)
|
|
w = self.centralWidget()
|
|
xscale = self.printer.pageRect().width() / (w.width())
|
|
yscale = self.printer.pageRect().height() / (w.height())
|
|
scale = min(xscale, yscale)
|
|
painter.translate(self.printer.paperRect().x() +
|
|
self.printer.pageRect().width() / 2,
|
|
self.printer.paperRect().y() +
|
|
self.printer.pageRect().height() / 2)
|
|
painter.scale(scale, scale)
|
|
painter.translate(-w.width()/2, -w.height()/2)
|
|
w.render(painter)
|
|
|
|
@Slot()
|
|
def show_about(self):
|
|
""" To overide by application
|
|
"""
|
|
QApplication.processEvents()
|
|
QMessageBox.about(
|
|
self, "About",
|
|
"""<b>{0}</b> v {1}
|
|
<p>Copyright © Paul Scherrer Institut (PSI).
|
|
All rights reserved.</p>
|
|
<p>Author: J. Chrin </p>
|
|
<p>1st Responsible: J. Chrin, Tel. 2930,
|
|
+41 76 206 0988, jan.chrin@psi.ch </p>
|
|
<p>2nd Responsible: 2nd name if appropriate </p>
|
|
<p>A main-window style application with menus,
|
|
a status bar, a toolbar and log window </p>
|
|
<p>Python {2} - Qt {3} - PyQt {4} <br>
|
|
cafe {5} - epics {6} on {7}""".format(
|
|
_pymodule, _appversion, platform.python_version(),
|
|
QT_VERSION_STR, PYQT_VERSION_STR,
|
|
self.cafe.CAFE_version(), self.cafe.EPICS_version(),
|
|
platform.system()))
|
|
QApplication.processEvents()
|
|
|
|
@Slot()
|
|
def show_help(self):
|
|
""" Invoke help pages from qrc_resources
|
|
"""
|
|
index_html = self.appname + "/index.html"
|
|
help_base = ":/help/" + self.appname
|
|
help_page = HelpBrowser(help_base, index_html, self)
|
|
help_page.show()
|
|
|
|
@Slot()
|
|
def take_screenshot(self):
|
|
QScreenshot(self, window_title=self.appname,
|
|
screen_items=self.screenshot_items,
|
|
screen_titles=self.screenshot_titles)
|
|
|
|
def add_screenshot(self, title=None, item=None):
|
|
if title and item:
|
|
if title not in self.screenshot_titles and \
|
|
item not in self.screenshot_items:
|
|
self.screenshot_titles.append(title)
|
|
self.screenshot_items.append(item)
|
|
|
|
def clear_screenshot(self, title=None, item=None):
|
|
if title and item:
|
|
if title in self.screenshot_titles and \
|
|
item in self.screenshot_items:
|
|
self.screenshot_titles.remove(title)
|
|
self.screenshot_items.remove(item)
|
|
|
|
|
|
def clear_screenshot(self, title=None, item=None):
|
|
if title and item:
|
|
if title in self.screenshot_titles and \
|
|
item in self.screenshot_items:
|
|
self.screenshot_titles.remove(title)
|
|
self.screenshot_items.remove(item)
|
|
|
|
|
|
############################### Init
|
|
def init_statusbar_wgt(self):
|
|
""" Configure statusbar
|
|
"""
|
|
self.statusbar_label.setFrameStyle(QFrame.StyledPanel | QFrame.Sunken)
|
|
self.statusbar.setSizeGripEnabled(False)
|
|
self.statusbar.addPermanentWidget(self.statusbar_label)
|
|
self.statusbar.clearMessage()
|
|
self.statusbar.showMessage("Initialization complete")
|
|
|
|
def init_progressbar_wgt(self):
|
|
self.progressbar_simulation = "simulation"
|
|
self.progressbar_standard = "blue"
|
|
self.progressbar_abort = "abort"
|
|
self.progressbar_color = self.progressbar_standard
|
|
self.progressbar.setObjectName(self.progressbar_color)
|
|
self.progressbar.setRange(PROGRESS_BAR_THREAD_START,
|
|
PROGRESS_BAR_THREAD_END)
|
|
self.progressbar.setTextVisible(True)
|
|
self.progressbar.setAlignment(Qt.AlignCenter)
|
|
self.progressbar.setVisible(False)
|
|
|
|
def init_hdf_analysis_wgt(self):
|
|
h5_groupbox = HDF5GroupBox(self, title="HDF5 Analysis", layout="Hor")
|
|
self.hdf_dock_widget.setObjectName("hdfDockWidget")
|
|
self.hdf_dock_widget.setVisible(False)
|
|
#hdfDockWidget.setFloating(True)
|
|
self.hdf_dock_widget.setContentsMargins(9, 9, 9, 9)
|
|
h5_groupbox.analyze_h5_widget.clicked.connect(
|
|
self.start_h5_thread)
|
|
self.hdf_dock_widget.setWidget(h5_groupbox)
|
|
self.addDockWidget(Qt.TopDockWidgetArea, self.hdf_dock_widget)
|
|
|
|
def restore_application_settings(self):
|
|
""" Restore platform-independent application settings
|
|
"""
|
|
qsettings = QSettings()
|
|
if qsettings.value("RecentFiles"):
|
|
self.application_recent_files = str(
|
|
qsettings.value("RecentFiles")).split(',')
|
|
if qsettings.value("BaseWindow/Geometry"):
|
|
self.restoreGeometry(qsettings.value("BaseWindow/Geometry"))
|
|
if qsettings.value("BaseWindow/State"):
|
|
self.restoreState(qsettings.value("BaseWindow/State"))
|
|
self.application_geometry = self.geometry()
|
|
|
|
|
|
def save_application_settings(self):
|
|
""" Save platform-independent application settings
|
|
"""
|
|
settings = QSettings()
|
|
if self.filename:
|
|
settings.setValue("LastFile", str(self.filename))
|
|
if self.application_recent_files:
|
|
recent_files = ', '.join(self.application_recent_files)
|
|
settings.setValue("RecentFiles", (recent_files))
|
|
settings.setValue("BaseWindow/Geometry", (self.saveGeometry()))
|
|
settings.setValue("BaseWindow/State", (self.saveState()))
|
|
|
|
|
|
|
|
@Slot()
|
|
def start_h5_thread(self):
|
|
pass
|
|
|
|
@Slot()
|
|
def start_analysis_thread(self):
|
|
|
|
##self.analysis_input_parameters()
|
|
|
|
if not self.analysis_procedure:
|
|
mess = "Analysis thread not configured for this application"
|
|
self.show_log_message(MsgSeverity.ERROR, _pymodule, _line(), mess)
|
|
self.statusbar.showMessage(mess)
|
|
return
|
|
|
|
self.analysis_thread = self.AnalysisThread(
|
|
self, self.analysis_procedure, self.input_parameters)
|
|
self.analysis_thread.trigger_thread_event.connect(
|
|
self.receive_analysis_results)
|
|
self.analysis_thread.started.connect(self.analysis_thread_started)
|
|
self.analysis_thread.finished.connect(self.analysis_thread_finished)
|
|
|
|
self.analysis_thread.start()
|
|
QApplication.processEvents()
|
|
|
|
@Slot()
|
|
def analysis_thread_started(self):
|
|
""" Change state of widgets when measuring
|
|
"""
|
|
self.gui_frame.in_measurement_procedure()
|
|
QApplication.processEvents()
|
|
print("Thread Started")
|
|
|
|
@Slot()
|
|
def analysis_thread_finished(self):
|
|
""" Reset widgets to intial pre-measurement state
|
|
"""
|
|
self.gui_frame.reset_procedure()
|
|
#self.progressbar.setObjectName(self.progressbar_color)
|
|
#self.progressbar.setFormat("")
|
|
#self.progressbar.setValue(PROGRESS_BAR_THREAD_INIT)
|
|
#self.progressbar.style().polish(self.progressbar)
|
|
QApplication.processEvents()
|
|
print("Thread Finished")
|
|
|
|
@Slot(dict)
|
|
def receive_analysis_results(self, all_dict):
|
|
print("receive analysis results", all_dict)
|
|
self.gui_frame.canvas_update( all_dict['Figure data'])
|
|
self.gui_frame.central_tab_widget.setCurrentIndex(1)
|
|
self.gui_frame.results_tab_wgt.setCurrentIndex(0)
|
|
|
|
@Slot()
|
|
def receive_abort_analysis(self):
|
|
"""Instantiate action on abort
|
|
"""
|
|
print("Aborting")
|
|
if self.analysis_procedure.abort:
|
|
return
|
|
self.gui_frame.in_abort_procedure()
|
|
# Trigger abort signal to the analysis thread
|
|
self.analysis_procedure.trigger_abort.emit()
|
|
#self.trigger_progressbar.emit(PROGRESS_BAR_THREAD_ABORTING)
|
|
QApplication.processEvents()
|
|
|
|
@Slot(str, str, int, str, dict)
|
|
def receive_log_message(self, severity, module, line, message, options={}):
|
|
'''Receive message from thread for routing to log window'''
|
|
|
|
self.show_log_message(severity, module, line, message, options)
|
|
self.statusbar.showMessage(message)
|
|
|
|
@Slot(int)
|
|
def receive_progressbar(self, value):
|
|
'''Receives update of message'''
|
|
if value == PROGRESS_BAR_THREAD_INIT:
|
|
self.progressbar.setVisible(False)
|
|
self.progressbar.setFormat("")
|
|
self.progressbar.reset()
|
|
self.progressbar.setObjectName(self.progressbar_color)
|
|
elif value == PROGRESS_BAR_THREAD_START:
|
|
self.progressbar.setVisible(True)
|
|
self.progressbar.setFormat("Measurement started")
|
|
self.progressbar.setValue(value)
|
|
self.progressbar.setObjectName(self.progressbar_color)
|
|
elif value == PROGRESS_BAR_THREAD_ABORTING:
|
|
self.progressbar.setFormat(
|
|
"Aborting procedure at the next available break point")
|
|
self.progressbar.setObjectName(self.progressbar_abort)
|
|
mess = "Aborting measurement procedure"
|
|
self.show_log_message(
|
|
MsgSeverity.WARN.name, _pymodule, _line(), mess)
|
|
self.statusbar.showMessage(mess)
|
|
elif value == PROGRESS_BAR_THREAD_ABORTED:
|
|
self.progressbar.setFormat("Procedure aborted")
|
|
self.progressbar.setObjectName(self.progressbar_abort)
|
|
mess = "Measurement procedure aborted"
|
|
self.show_log_message(
|
|
MsgSeverity.WARN.name, _pymodule, _line(), mess)
|
|
self.statusbar.showMessage(mess)
|
|
QTimer.singleShot(5000, lambda: self.trigger_progressbar.emit(
|
|
PROGRESS_BAR_THREAD_INIT))
|
|
elif value == PROGRESS_BAR_THREAD_ERROR:
|
|
self.progressbar.setFormat("No data returned!")
|
|
self.progressbar.setObjectName(self.progressbar_abort)
|
|
|
|
elif value == PROGRESS_BAR_THREAD_END:
|
|
self.progressbar.setFormat("Measurement completed")
|
|
self.progressbar.setValue(value)
|
|
self.progressbar.setObjectName(self.progressbar_color)
|
|
self.daq_analysis_completed = True
|
|
QTimer.singleShot(5000, lambda: self.progressbar.setVisible(False))
|
|
else:
|
|
self.progressbar.setFormat("Measurement in progress...")
|
|
self.progressbar.setValue(value)
|
|
self.progressbar.setObjectName(self.progressbar_color)
|
|
|
|
self.progressbar.style().polish(self.progressbar)
|
|
QApplication.processEvents()
|
|
time.sleep(0.001)
|
|
|
|
|
|
def initialize_application(self, appname=_appname, delay=None,
|
|
facility="PSI"):
|
|
"""
|
|
Initialize application configuration for use by QSettings.
|
|
Loads application stylesheet from qrc_resources or input file
|
|
These are saved into $HOME/.config
|
|
e.g., $HOME/.config/<facility>/appname.conf
|
|
Provide QSplashScreen if delay is set.
|
|
"""
|
|
self.setStyle("motif")
|
|
self.setOrganizationName(facility)
|
|
self.setOrganizationDomain("psi.ch")
|
|
self.setApplicationName(appname)
|
|
self.setWindowIcon(QIcon(":/icon.png"))
|
|
|
|
parser = argparse.ArgumentParser(description="stylesheet")
|
|
parser.add_argument("-q", "--qss", action="store", dest="qss")
|
|
parser.add_argument("-c", "--config")
|
|
parser.add_argument("-f", "--facility")
|
|
parser.add_argument("-u", "--user")
|
|
#result, unknown = parser.parse_known_args(["--qss", ":/sfop.qss"])
|
|
#result, unknown = parser.parse_known_args(["--qss", ":/sfop.qss", "-c", "--facility", "--user"])
|
|
#print(result)
|
|
#print(unknown)
|
|
result=parser.parse_args()
|
|
qss_file = result.qss if result.qss else ":/acc.qss"
|
|
print(qss_file)
|
|
qss = QFile(qss_file)
|
|
if qss.open(QIODevice.ReadOnly):
|
|
fByteArray = qss.readAll()
|
|
self.setStyleSheet(str(fByteArray.data(), encoding='utf-8'))
|
|
qss.close()
|
|
else:
|
|
print("COULD NOT OPEN :/acc.qss stylesheet")
|
|
exit(1)
|
|
|
|
if delay is not None:
|
|
delay = max(delay, 5)
|
|
start = time.time()
|
|
splash_screen = QSplashScreen(QPixmap(":/loading.gif"))
|
|
splash_screen.setAttribute(Qt.WA_TranslucentBackground, False)
|
|
splash_screen.setWindowFlags(Qt.WindowStaysOnTopHint)
|
|
splash_screen.showMessage(
|
|
"""
|
|
<br><p style='color:black; font-weight:bold;
|
|
font-size:28px; margin-right:10px;'>
|
|
LOADING APPLICATION:
|
|
<font color=royalblue > {0} </font> </p>
|
|
<p style='color:black; font-weight:bold;
|
|
font-size:20px; margin-right:22px;'>
|
|
This message will self-destruct in {1} seconds<br></p>
|
|
""".format(appname, round(delay)), Qt.AlignCenter)
|
|
width = 860 + (len(appname)-10)*15
|
|
height = 220
|
|
splash_screen.resize(width, height)
|
|
splash_screen.show()
|
|
while time.time()-start < delay:
|
|
time.sleep(0.01)
|
|
self.processEvents()
|
|
return splash_screen
|
|
|