from PyQt4 import QtGui, QtCore import os import logging # --------[ Backend ]--------- from . import backendinterface as bif # --------[ Essentials ]--------- from . import storage from .settings import Settings # --------[ Necessary Widgets ]------ from . import kcgwidget as kcgw from .ControlWidget import ControlWidget from .MultiWidget import MultiWidget from .groupedelements import MenuItems, Elements from .backend.board import available_boards from .backend import board from .backend.CalibrationHandle import theCalibration from .MultiPage import MultiPage from .globals import glob as global_objects from . import bitsTable as bt from . import log from ..widgets.ConfigSetup import ConfigSetup # ---------[ 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 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', 'force_ask', 'guiIcon', 'language', 'logCommentIcon', 'working_channels', '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(os.path.join(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() # .d8888b. 888 8888888888 # d88P Y88b888 888 888 # 888 888888 888 888 # 888 888 888 888 # 888 88888888 888 888 # 888 888888 888 888 # Y88b d88PY88b. .d88P 888 # "Y8888P88 "Y88888P" 8888888 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)) # fill status storage with correct variables readconfig(self) # ----------[ Set Variables and create objects ]----------------- # self.storage = storage.Storage() self.storage = storage.storage # storage.storage = self.storage self.settings = None # (this holds the settings window) Only create Window when used self.statusbar = self.statusBar() # kcgw.statusbar = self.statusbar # set status bar to kcgw to easily access from other sources global_objects.set_global('statusbar', self.statusbar) 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.icon_path(config.guiIcon))) # 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.style_path("style.css")) as f: styleSheet = f.read() if config.style == 'blue': with open(config.style_path('blue.css')) 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 bif._bif_update_working_dir() self.measurementLogger = log.MeasurementLogger() 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]) # activate Epics if defined if config.use_epics: from ..widgets import EpicsWidget EpicsWidget.epicsConfig = EpicsWidget.EpicsConfig() # open default CalibrationFile theCalibration.openFile(config.config_path("calibration.hdf")) 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.configAction = self.fileMenu.addAction(tr("Button", "Rerun Configuration Wizard"), self.rerunConfig) self.quitAction = self.fileMenu.addAction(QtGui.QIcon(config.icon_path("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.icon_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.icon_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.help = self.menu.addMenu("&"+tr("Button", "Help")) import webbrowser self.help.addAction(tr("Button", "Open Manual"), lambda: webbrowser.open(os.path.join(config.install_path, "Documentation","build","html","index.html"))) self.help.addAction(tr("Button", "About"), self.showAbout) def _show_board_chooser(self): selected_boards = [] if len(available_boards.board_ids) == 1: return [available_boards.board_ids[0]] chooser = QtGui.QDialog(self) chooser.setWindowTitle("KCG - Choose Boards") chooser_layout = QtGui.QVBoxLayout() chooser.setLayout(chooser_layout) chooser_layout.addWidget(QtGui.QLabel("Choose Boards")) boards = {} for bid in available_boards.board_ids: boards[bid] = QtGui.QCheckBox(str(bid), chooser) chooser_layout.addWidget(boards[bid]) button = QtGui.QPushButton("OK", chooser) def ok(): for bi, box in list(boards.items()): if box.isChecked(): selected_boards.append(bi) chooser.close() button.clicked.connect(ok) chooser_layout.addWidget(button) chooser.exec_() return selected_boards def saveConfig(self, board_id=None): """ Save the current configuration to a configuration file :param board_id: the board to save the configuration for """ 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 not filename: return if board_id is None: board_id = self._show_board_chooser() elif not isinstance(board_id, list): board_id = [board_id] fname = filename[0].split(".") for bid in board_id: if len(board_id) == 1: fname_board = filename[0] else: fname_board = ".".join(map(str, fname[:-1]))+"_"+str(bid)+"."+fname[-1] if not board.get_board_config(bid).save_config(fname_board): 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=None): """ Load the configuration for the given board from a file :param board_id: the board to read the configuration for """ filename = QtGui.QFileDialog.getOpenFileName(self, 'Open Configuration', '', 'KAPTURE Configuration File (*.kcf)') if not filename: return if board_id is None: board_id = self._show_board_chooser() elif not isinstance(board_id, list): board_id = [board_id] for bid in board_id: if board.get_board_config(bid).load_config(filename): bif.bk_write_values(bid, 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 rerunConfig(self): """ Rerun the initial configuration wizard """ self.setupConfig = ConfigSetup(restart=True) self.setupConfig.setWindowModality(QtCore.Qt.ApplicationModal) def restart(): import subprocess import sys import os try: subprocess.Popen(['kcg']) except OSError as exception: try: path = config.install_path[:-4]+'kcg' subprocess.Popen([sys.executable, path]) except: print('ERROR: could not restart aplication:') print((' %s' % str(exception))) else: QtGui.qApp.quit() else: QtGui.qApp.quit() self.setupConfig.success_signal.connect(restart) self.setupConfig.show() def showAbout(self): """ Show the about window. :return: - """ version = open(os.path.join(config.install_path,"VERSION")).read() about = QtGui.QDialog(self) 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)) for bid in available_boards.board_ids: try: if bif.bk_get_config(bid, setting) != None: bif.bk_update_config(bid, setting, getattr(self.storage, setting)) except board.NoSuchKeyError: pass 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): """ Method to check for boards and perform a status_readout after the gui is fully started :return: """ for bid in available_boards.board_ids: # there is always at least a dummy board bif.bk_check_for_board(bid) 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): '''Update header''' 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 = config.config_path("config.cfg") RE = '(('+'|'.join(list(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_on_trigger: 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() ev.accept() else: ev.ignore()