from PyQt4 import QtGui, QtCore import os import logging # --------[ Backend ]--------- import backendinterface as bif # --------[ Essentials ]--------- import storage from settings import Settings # --------[ Necessary Widgets ]------ import kcgwidget as kcgw from controlwidget import ControlWidget from multiWidget import MultiWidget from groupedelements import MenuItems, Elements #, create_new_group_object, change_group_obect_to_id from backend.board import available_boards from backend import board from multipage import MultiPage import bitsTable as bt import log # ---------[ Widgets IMPORTANT!!! ]------------------ # this enables widgets. If this is not imported (even though it is not directly used) no widgets will be available from ..widgets import * # from widgets import * # copy in case the above line gets removed by ide # ---------[ IMPORTANT ]--------------------- tr = kcgw.tr from .. import config import time import getpass # config.curr_id = create_new_group_object() # Elements.setFlags({'autoremove': True, 'warn': True, 'exception_on_deleted': False}) def readconfig(parent): """ Reads the config and evalues certain variables Also: Validates config to check if all necessary values are there :param parent: parent for popup windows :return: - """ nec_conf = ['acquireSettingsIcon', 'bunches_per_turn', 'default_log_entries', 'default_save_location', 'default_subdirectory_name', 'epics_base_path', 'epics_log_entry_pvs', 'epics_test_pv', 'force_ask', 'guiIcon', 'language', 'logCommentIcon', 'logIcon', 'newPlotDataIcon', 'newPlotLiveIcon', 'save_header', 'show_advanced_control', 'singleReadIcon', 'startIcon', 'stopIcon', 'style', 'tRev', 'timingIcon'] missing_conf = [] for c in nec_conf: if c not in dir(config): missing_conf.append(c) if missing_conf: class ConfigError(Exception): pass raise ConfigError('The Following variables are missing in config.py: "' + '", "'.join(missing_conf)+'"') if config.language != "en_GB": kcgw.translator.load(config.install_path+'lang/'+ config.language) else: global tr kcgw.tr = lambda _, x: x tr = lambda _, x: x dateG = "{d}.{m}.{y}" dateGd = "{d}_{m}_{y}" dateA = "{m}-{d}-{y}" times = "{H}_{M}" timel = "{H}_{M}_{S}" session = "" if "{ask}" in config.default_subdirectory_name: status = False while not status: text, status = QtGui.QInputDialog.getText(parent, tr("Heading", "Subdirectory"), tr("Dialog", "Enter a name for the Subdirectory\n" "in which data will be saved:\n" "NOTE: You are being asked because it " "was set this way in the config file.")) if not status and not config.force_ask: config.default_subdirectory_name = "{user}_{dateGd}-{timel}" break else: config.subdirectory_name = text.replace(" ", "_") return if "{sessionname}" in config.default_subdirectory_name: status = False while not status: text, status = QtGui.QInputDialog.getText(parent, tr("Heading", "Sessionname"), tr("Dialog", "Enter Sessionname\n" "NOTE: You are being asked because it " "was set this way in the config file.:")) if not status and not config.force_ask: config.default_subdirectory_name = "{user}_{dateGd}-{timel}" break else: session = text.replace(" ", "_") config.default_subdirectory_name = config.default_subdirectory_name.format( dateG=dateG, dateGd=dateGd, dateA=dateA, times=times, timel=timel, d=time.strftime("%d"), m=time.strftime("%m"), y=time.strftime("%y"), H=time.strftime("%H"), M=time.strftime("%M"), S=time.strftime("%S"), timestamp=time.localtime(), user=getpass.getuser(), sessionname=session ) config.subdirectory_name = config.default_subdirectory_name.format( d=time.strftime("%d"), m=time.strftime("%m"), y=time.strftime("%y"), H=time.strftime("%H"), M=time.strftime("%M"), S=time.strftime("%S"), timestamp=time.localtime(), user=getpass.getuser() ) if config.default_save_location == "pwd": import os config.save_location = os.getcwd() else: config.save_location = config.default_save_location _MultiView_Name_ = "MultiView" class CentralWidget(kcgw.KCGWidgets): """ Central Widget for the KCG gui main window """ def __init__(self, parent=None): super(CentralWidget, self).__init__(parent=parent) # -------[ Create empty Groups to avoid warnings ]--------- MenuItems.createEmptyGroup('Setup/Control') MenuItems.createEmptyGroup('Bits Table') # -------[ END ]--------------- self.layout = QtGui.QHBoxLayout() self.setLayout(self.layout) self.pagesWidget = MultiPage(self) self.layout.addWidget(self.pagesWidget) self.mainControlWidget = ControlWidget() self.pagesWidget.addPage(self.mainControlWidget, "Setup/Control") self.mainMultiWidget = MultiWidget() self.pagesWidget.addPage(self.mainMultiWidget, "MultiView") # self.tableWidget = bt.AdvancedBoardInterface(parent=self) self.tableWidget = bt.AdvanceControlView() self.tableWidget.hide() class Gui(QtGui.QMainWindow): """ Main Window of the KCG gui """ def __init__(self): super(Gui, self).__init__() self.createEmptyGroups() # -------[ Check for boards and create corresponding objects ]------ for board_id in available_boards: board.create_new_board_config(board_id) # board.get_board_config(board_id).observe(None, lambda x: bif.update_header(board_id), 'header') # Set update_header as function to call when header config is changed for board_id in available_boards: bif.initStatus(board.get_board_status(board_id)) readconfig(self) # ----------[ Set Variables and create objects ]----------------- # self.storage = storage.Storage() self.storage = storage.storage # storage.storage = self.storage self.settings = None # Only create Window when used self.statusbar = self.statusBar() kcgw.statusbar = self.statusbar # set status bar to kcgw to easily access from other sources self.pageIndicator = QtGui.QLabel() self.statusbar.addPermanentWidget(self.pageIndicator) self.cw = CentralWidget(self) self.doMenu() self.setCentralWidget(self.cw) self.initUI() self.finalizeInit() self.after_start_status_handler() self.setContentsMargins(0, -10, 0, 0) def initUI(self): """ Initialize ui :return: - """ self.setWindowTitle("KCG - Kapture Control Gui") self.setWindowIcon(QtGui.QIcon(config.install_path + config.guiIcon)) # self.statusbar.showMessage(board.status.status_text) # TODO: imporant status message for which board? all? # QtGui.QApplication.setStyle("Oxygen") # Make it look less blown up in Gnome for example def createEmptyGroups(self): """ This creates empty groups with the GroupedObjects class in groupedelements module. This has to be done to avoid warnings when groups are enabled or disabled before creation. :return: - """ for board_id in available_boards: Elements.createEmptyGroup("acquire_{}".format(board_id)) Elements.createEmptyGroup("timing_{}".format(board_id)) Elements.createEmptyGroup("no_board_{}".format(board_id)) Elements.createEmptyGroup("continuous_read_{}".format(board_id)) def finalizeInit(self): """ Final things done at initialisation :return: - """ self.populate_storage() with open(config.install_path+"style/style.css") as f: styleSheet = f.read() if config.style == 'blue': with open(config.install_path+'style/blue.css') as f: styleSheet += f.read() # with open('/tmp/darkorange.stylesheet') as f: # styleSheet = f.read() self.setStyleSheet(styleSheet) # evaluate config file regarding advanced_control self.showAdvancedControl(config.show_advanced_control) self.storage.advanced_control = config.show_advanced_control if not os.path.isdir(storage.storage.save_location + '/' + storage.storage.subdirname): os.makedirs(storage.storage.save_location + '/' + storage.storage.subdirname) self.measurementLogger = log.MeasurementLogger() # storage.storage.save_location + '/' + storage.storage.subdirname # + "/Measurement.log") log.logger = self.measurementLogger logStrings = [] functionAndParameter = [] for par in self.measurementLogger.predefined_parameters: # get strings and functions in seperate lists logStrings.append(par[0]) functionAndParameter.append(par[1]) for e in config.default_log_entries: # for every entry: if e in logStrings: self.measurementLogger.register_parameter(e, functionAndParameter[logStrings.index(e)][0], functionAndParameter[logStrings.index(e)][1]) # self.measurementLogger.register_dumper(board.config.dump) # TODO: register dumper for all boards if log.no_epics and log.epics_reachable: logging.info("Epics installation not found. Logfiles will not contain information that is to be " "obtained via epics.") if not log.epics_reachable: logging.info("Epics PVs could not be accessed. Check Internet connection and Epics PV provider. Logfiles will not contain" "information that is to be obtained via epics.") def doMenu(self): """ Create and show the menu and it's entries :return: - """ self.menu = self.menuBar() self.fileMenu = self.menu.addMenu("&"+tr("Button", "File")) self.saveConfigAction = self.fileMenu.addAction(tr("Button", "Save Board Configuration"), self.saveConfig) self.saveConfigAction = self.fileMenu.addAction(tr("Button", "Load Board Configuration"), self.loadConfig) self.settingsAction = self.fileMenu.addAction(tr("Button", "Settings"), self.showSettings, "Ctrl+P") self.quitAction = self.fileMenu.addAction(QtGui.QIcon(config.install_path + "icons/exit.png"), tr("Button", "Quit"), self.close, "Ctrl+Q") self.menu.setCornerWidget(self.cw.pagesWidget.leftright) # ----------[ Page specific Menu Entries ]------------- self.multiMenu = self.menu.addMenu("&"+tr("Button", "Windows")) MenuItems.addMenuItem(_MultiView_Name_, self.multiMenu) self.plotAction = self.multiMenu.addAction(QtGui.QIcon(config.install_path + config.newPlotLiveIcon), tr("Button", "New Plot"), self.cw.mainMultiWidget.leftBar.add_plot) self.addWindowMenuEntries() if not available_boards.multi_board: self.acquireMenu = self.menu.addMenu("&"+tr("Button", "Acquire")) MenuItems.addMenuItem(_MultiView_Name_, self.acquireMenu) self.startAcquisitionAction = self.acquireMenu.addAction(QtGui.QIcon(config.install_path + config.startIcon), tr("Button", "Start Acquisition"), lambda: bif.bk_acquire(available_boards[0])) self.startAcquisitionAction.setObjectName("start_acquisition_action") MenuItems.addMenuItem("continuous_read_{}".format(available_boards[0]), self.startAcquisitionAction) MenuItems.addMenuItem("acquireTrigger_{}".format(available_boards[0]), self.startAcquisitionAction) # -----[ disable Menu Items for MultiView as it is not the startup page ]------------- # this could be avoided if menu is created before the multipage widget MenuItems.setEnabled(_MultiView_Name_, False) # self.changeBoard = self.menu.addMenu("&"+tr("Button", "Change Board")) # self.changeBoard.addAction(tr("Button", "Board 1"), lambda: self.change_board(0)) # self.changeBoard.addAction(tr("Button", "Board 2"), lambda: self.change_board(1)) self.help = self.menu.addMenu("&"+tr("Button", "Help")) import webbrowser self.help.addAction(tr("Button", "Open Manual"), lambda: webbrowser.open(config.install_path + "Documentation/build/html/index.html")) self.help.addAction(tr("Button", "About"), self.showAbout) # def change_board(self, id): # board.change_board_config_to_id(id) # board.config.board_identifier = "first" ### SET IDENTIFIER HIER # change_group_obect_to_id(id) def saveConfig(self, board_id): filenameDialog = QtGui.QFileDialog(self, tr("Heading", "Save Configuration"), '', 'KAPTURE Configuration File (*.kcf)') filenameDialog.setDefaultSuffix("kcf") filenameDialog.setAcceptMode(filenameDialog.AcceptSave) filenameDialog.exec_() filename = filenameDialog.selectedFiles() if filename[0]: if not board.get_board_config(board_id).save_config(filename[0]): QtGui.QMessageBox.critical(self, tr("Heading", "Error Saving Config"), tr("Dialog", "There was an error saving to a config file.")) else: QtGui.QMessageBox.critical(self, tr("Heading", "Error Saving Config"), tr("Dialog", "There was an error saving to a config file.")) def loadConfig(self, board_id): filename = QtGui.QFileDialog.getOpenFileName(self, 'Open Configuration', '', 'KAPTURE Configuration File (*.kcf)') if not filename: return if board.get_board_config(board_id).load_config(filename): bif.bk_write_values(board_id, defaults=False) else: QtGui.QMessageBox.critical(self, tr("Heading", "Error Loading Config"), tr("Dialog", "There was an error loading the config file, make sure it is valid and try again.")) def showAbout(self): """ Show the about window. :return: - """ version = open(config.install_path+"VERSION").read() about = QtGui.QDialog(self) # TODO: read about text externally? read version externally? about.setWindowTitle("KCG - About") about_label = QtGui.QLabel(tr("About", "KAPTURE Control Gui\n" "KCG is a graphical control interface to the KAPTURE board\n\n" "Author: Patrick Schreiber\n\n" "Version:\n")+version) about_label.setAlignment(QtCore.Qt.AlignCenter) header_label = QtGui.QLabel(tr("About", "KCG")) header_label.setStyleSheet("font-size: 25pt; text-align: center;") header_label.setAlignment(QtCore.Qt.AlignCenter) footer_label = QtGui.QLabel(tr("About", "\nKAPTURE - Karlsruhe Pulse-Taking and Ultrafast Readout Electronics")) footer_label.setStyleSheet("font-size: 7pt;") footer_label.setAlignment(QtCore.Qt.AlignRight) about_layout = QtGui.QHBoxLayout() about_text_layout = QtGui.QVBoxLayout() about.setLayout(about_layout) # pxm = QtGui.QPixmap(config.guiIcon) # icon_layout = QtGui.QVBoxLayout() # icon_label = QtGui.QLabel("") # icon_label.setPixmap(pxm.scaled(QtCore.QSize(128, 128), QtCore.Qt.KeepAspectRatio)) # icon_label.setFixedSize(130, 130) # icon_layout.addWidget(icon_label) # icon_layout.addStretch(1) # about_layout.addLayout(icon_layout) about_layout.addLayout(about_text_layout) about_text_layout.addWidget(header_label) about_text_layout.addWidget(about_label) about_text_layout.addWidget(footer_label) about.setFixedSize(400, 230) about.setStyleSheet("background-color: darkgrey;") about.exec_() def addWindowMenuEntries(self): """ Adds Window Menu entries for custom widgets :return: - """ for f in kcgw.get_registered_widgets(): self.multiMenu.addAction(*f[:3]) # TODO: icon - ??? def showSettings(self): """ Create and show settings window :return: - """ if self.settings: # use preopened window self.settings.show() self.settings.raise_() self.settings.activateWindow() else: self.settings = Settings(self.storage) self.settings.changed.connect(self.updateSettings) def updateSettings(self, changedsettings): """ Update settings in storage if settings were changed in the settings window. :param changedsettings: list of settings that have been changed :return: - """ for setting in changedsettings: if setting == 'language': lang = getattr(self.storage, setting) self.update_configuration_file({'language':'"'+str(lang)+'"'}) QtGui.QMessageBox.information(self, "Change Language", "Language change takes effect after Gui restart", 1) if setting == 'advanced_control': self.showAdvancedControl(getattr(self.storage, setting)) if bif.bk_get_config(setting) != None: bif.bk_update_config(setting, getattr(self.storage, setting)) def showAdvancedControl(self, value): """ Enable or disable advanced table control view (Tables for registers) :param value: (bool) True to show and False to hide advanced view :return: - """ if value: if self.cw.tableWidget.isHidden(): self.cw.pagesWidget.addPage(self.cw.tableWidget, 'Bits Table', set_to_first=False) self.cw.tableWidget.show() else: if not self.cw.tableWidget.isHidden(): self.cw.pagesWidget.removePage(self.cw.tableWidget) self.cw.tableWidget.hide() def after_start_status_handler(self): bif.bk_status_readout() def populate_storage(self): """ Initially fills storage with predefined settings and configuration values :return: - """ self.storage.header = config.save_header self.storage.subdirname = config.subdirectory_name self.storage.save_location = config.save_location self.storage.language = config.language self.storage.advanced_control = False def update_header(val): self.storage.header = val if self.settings: self.settings.headerTick.setChecked(val) board.get_board_config(available_boards[0]).observe(self.storage.header, update_header, 'header') # TODO: header at one place for all boards? (here it uses the first board) def update_configuration_file(self, new_conf): """ Update variablevalues in config file NOTE: this doesn't use standard ConfigParser as that would delete comments :param new_conf: Dictionary with variable, value pair :return: """ import re # filename = "config.py" filename = os.path.expanduser("~")+"/.kcg/config.cfg" RE = '(('+'|'.join(new_conf.keys())+')\s*=)[^\r\n]*?(\r?\n|\r)' pat = re.compile(RE) def jojo(mat,dic = new_conf ): return dic[mat.group(2)].join(mat.group(1,3)) with open(filename,'rb') as f: content = f.read() with open(filename,'wb') as f: f.write(pat.sub(jojo,content)) def closeEvent(self, ev): """ Handles closing of the GUI - this function is called by pyqt upon a close event. Asks if user really wants to close the gui :param ev: event :return: - """ extra = "" for b in available_boards: if board.get_board_status(b).wait: extra += '\n'+tr('Dialog', 'Waiting on external trigger is still enabled.') if board.get_board_status(b).continuous_read: extra += '\n'+tr('Dialog', 'Continuous read is still enabled.') if extra: break cl = None if extra: cl = QtGui.QMessageBox.critical(self, tr("Heading", "Close KCG"), tr("Dialog", "Close KCG?")+extra, QtGui.QMessageBox.No | QtGui.QMessageBox.Yes, QtGui.QMessageBox.No) if not cl or cl == QtGui.QMessageBox.Yes: cl = QtGui.QMessageBox.question(self, tr("Heading", "Close KCG"), tr("Dialog", "Close KCG?\nYou will loose the state of open plots etc."), QtGui.QMessageBox.No | QtGui.QMessageBox.Yes, QtGui.QMessageBox.No) if cl == QtGui.QMessageBox.Yes: if self.settings: self.settings.close() # board.config.save_config("backup.kcf") ev.accept() else: ev.ignore()