Пример #1
0
class MainApp(QWidget):
    def __init__(self, parent=None):
        logging.debug("MainApp:init() instantiated")
        super().__init__()
        self.baseWidgets = {}
        self.vmWidgets = {}
        self.materialWidgets = {}
        self.cf = SystemConfigIO()
        self.ec = ExperimentConfigIO.getInstance()
        self.statusBar = QStatusBar()

        self.setMinimumSize(670, 565)
        quit = QAction("Quit", self)
        quit.triggered.connect(self.closeEvent)
        self.setWindowTitle("ARL South RES v0.1")

        self.tabWidget = QtWidgets.QTabWidget()
        self.tabWidget.setGeometry(QtCore.QRect(0, 15, 668, 565))
        self.tabWidget.setObjectName("tabWidget")

        # Configuration Window (windowBox) contains:
        ## windowBoxHLayout contains:
        ###experimentTree (Left)
        ###basedataStackedWidget (Right)
        self.windowWidget = QtWidgets.QWidget()
        self.windowWidget.setObjectName("windowWidget")
        self.windowBoxHLayout = QtWidgets.QHBoxLayout()
        #self.windowBoxHLayout.setContentsMargins(0, 0, 0, 0)
        self.windowBoxHLayout.setObjectName("windowBoxHLayout")
        self.windowWidget.setLayout(self.windowBoxHLayout)

        self.experimentTree = QtWidgets.QTreeWidget()
        self.experimentTree.itemSelectionChanged.connect(self.onItemSelected)
        self.experimentTree.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
        self.experimentTree.customContextMenuRequested.connect(
            self.showContextMenu)
        self.experimentTree.setEnabled(True)
        self.experimentTree.setMinimumSize(200, 521)
        self.experimentTree.setMaximumWidth(350)
        self.experimentTree.setObjectName("experimentTree")
        self.experimentTree.headerItem().setText(0, "Experiments")
        self.experimentTree.setSortingEnabled(False)
        self.windowBoxHLayout.addWidget(self.experimentTree)

        self.basedataStackedWidget = QStackedWidget()
        self.basedataStackedWidget.setObjectName("basedataStackedWidget")
        self.basedataStackedWidget.setEnabled(False)
        self.windowBoxHLayout.addWidget(self.basedataStackedWidget)
        self.tabWidget.addTab(self.windowWidget, "Configuration")

        # VBox Actions Tab
        self.experimentActionsWidget = ExperimentActionsWidget(
            statusBar=self.statusBar)
        self.experimentActionsWidget.setObjectName("experimentActionsWidget")
        self.tabWidget.addTab(self.experimentActionsWidget,
                              "Experiment Actions")

        # Remote Connections Tab
        self.connectionWidget = ConnectionWidget(statusBar=self.statusBar)
        self.connectionWidget.setObjectName("connectionsWidget")
        self.tabWidget.addTab(self.connectionWidget, "Remote Connections")

        #Create the bottom layout so that we can access the status bar
        self.bottomLayout = QHBoxLayout()
        self.statusBar.showMessage("Loading GUI...")
        self.bottomLayout.addWidget(self.statusBar)
        self.saveButton = QtWidgets.QPushButton("Save Current")
        self.saveButton.clicked.connect(self.saveExperimentButton)
        self.saveButton.setEnabled(False)
        self.bottomLayout.addWidget(self.saveButton)

        self.populateUi()
        self.setupContextMenus()

        self.initMenu()
        self.mainLayout = QVBoxLayout()
        self.mainLayout.addWidget(self.mainMenu)
        self.mainLayout.addWidget(self.tabWidget)
        self.mainLayout.addLayout(self.bottomLayout)

        self.setLayout(self.mainLayout)
        #self.setCentralWidget(self.outerBox)
        self.tabWidget.setCurrentIndex(0)

        #self.statusBar.showMessage("Finished Loading GUI Components")

        # Plugin Section
        self.tabWidget.addTab(CTFi2GUI(), "CTFi2")

    def readSystemConfig(self):
        logging.debug("MainApp:readSystemConfig() instantiated")
        self.vboxPath = self.cf.getConfig()['VBOX']['VMANAGE_PATH']
        self.experimentPath = self.cf.getConfig(
        )['EXPERIMENTS']['EXPERIMENTS_PATH']
        self.statusBar.showMessage("Finished reading system config")

    def setupContextMenus(self):
        logging.debug("MainApp:setupContextMenus() instantiated")
        # Context menu for blank space
        self.blankTreeContextMenu = QtWidgets.QMenu()
        self.addExperiment = self.blankTreeContextMenu.addAction(
            "New Experiment")
        self.addExperiment.triggered.connect(self.addExperimentActionEvent)
        self.importExperiment = self.blankTreeContextMenu.addAction(
            "Import Experiment from RES archive")
        self.importExperiment.triggered.connect(self.importActionEvent)

        # Experiment context menu
        self.experimentContextMenu = QtWidgets.QMenu()
        self.addVMContextSubMenu = QtWidgets.QMenu()
        self.experimentContextMenu.addMenu(self.addVMContextSubMenu)
        self.addVMContextSubMenu.setTitle("Add")
        self.addVM = self.addVMContextSubMenu.addAction("Virtual Machines")
        self.addVM.triggered.connect(self.addVMActionEvent)
        self.addMaterial = self.addVMContextSubMenu.addAction("Material Files")
        self.addMaterial.triggered.connect(self.addMaterialActionEvent)

        # Add line separator here
        self.removeExperiment = self.experimentContextMenu.addAction(
            "Remove Experiment")
        self.removeExperiment.triggered.connect(
            self.removeExperimentItemActionEvent)
        self.exportExperiment = self.experimentContextMenu.addAction(
            "Export Experiment")
        self.exportExperiment.triggered.connect(self.exportActionEvent)

        # VM/Material context menu
        self.itemContextMenu = QtWidgets.QMenu()
        self.removeItem = self.itemContextMenu.addAction(
            "Remove Experiment Item")
        self.removeItem.triggered.connect(self.removeExperimentItemActionEvent)

    def populateUi(self):
        logging.debug("MainApp:populateUi() instantiated")
        self.statusBar.showMessage("Populating UI")
        self.readSystemConfig()
        #####Create the following based on the config file
        result = self.ec.getExperimentXMLFilenames()
        if result == None:
            return
        [xmlExperimentFilenames, xmlExperimentNames] = result
        if xmlExperimentFilenames == [] or xmlExperimentNames == []:
            self.statusBar.showMessage("No configs found")
            return

        #For all experiment files found
        for configname in xmlExperimentNames:
            ####Read Experiment Config Data and Populate Tree
            self.loadConfigname(configname)
        self.statusBar.showMessage(
            "Completed populating the User Interface from " +
            str(len(xmlExperimentNames)) + " config files read", 6000)

    ###############################

    def loadConfigname(self, configname):
        logging.debug("MainApp(): loadConfigname instantiated")
        logging.info("Reading XML data for " + str(configname))
        jsondata = self.ec.getExperimentXMLFileData(configname)
        self.statusBar.showMessage("Finished reading experiment config")

        ##########testbed-setup data######
        if jsondata == None:
            jsondata = {}
        if "xml" not in jsondata or jsondata["xml"] == None or str(
                jsondata["xml"]).strip() == "":
            jsondata["xml"] = {}
        if "testbed-setup" not in jsondata["xml"]:
            jsondata["xml"]["testbed-setup"] = {}
        if "vm-set" not in jsondata["xml"]["testbed-setup"]:
            jsondata["xml"]["testbed-setup"]["vm-set"] = {}
        #Temporary fix for older xml/json files.
        if "users-filename" not in jsondata["xml"]["testbed-setup"]["vm-set"]:
            jsondata["xml"]["testbed-setup"]["vm-set"]["users-filename"] = ""
        if "rdp-broker-ip" not in jsondata["xml"]["testbed-setup"]["vm-set"]:
            jsondata["xml"]["testbed-setup"]["vm-set"]["rdp-broker-ip"] = ""
        if "chat-server-ip" not in jsondata["xml"]["testbed-setup"]["vm-set"]:
            jsondata["xml"]["testbed-setup"]["vm-set"]["chat-server-ip"] = ""

        configTreeWidgetItem = QtWidgets.QTreeWidgetItem(self.experimentTree)
        configTreeWidgetItem.setText(0, configname)
        self.experimentActionsWidget.addExperimentItem(
            configname, config_jsondata=jsondata)
        self.connectionWidget.addExperimentItem(configname,
                                                config_jsondata=jsondata)
        basejsondata = jsondata["xml"]
        # Base Config Widget
        self.baseWidget = BaseWidget(self, configname, configname,
                                     basejsondata)
        self.baseWidgets[configname] = {
            "BaseWidget": {},
            "VMWidgets": {},
            "MaterialWidgets": {}
        }
        self.baseWidgets[configname]["BaseWidget"] = self.baseWidget
        self.basedataStackedWidget.addWidget(self.baseWidget)

        ##########vm data######
        if "vm" in jsondata["xml"]["testbed-setup"]["vm-set"]:
            vmsjsondata = jsondata["xml"]["testbed-setup"]["vm-set"]["vm"]
            if isinstance(vmsjsondata, list):
                for vm in vmsjsondata:
                    vm_item = QtWidgets.QTreeWidgetItem(configTreeWidgetItem)
                    vmlabel = "V: " + vm["name"]
                    vm_item.setText(0, vmlabel)
                    # VM Config Widget
                    vmWidget = VMWidget(None, configname, vm["name"], vm)
                    self.baseWidgets[configname]["VMWidgets"][
                        vmlabel] = vmWidget
                    self.basedataStackedWidget.addWidget(vmWidget)
            else:
                vm_item = QtWidgets.QTreeWidgetItem(configTreeWidgetItem)
                vmlabel = "V: " + vmsjsondata["name"]
                vm_item.setText(0, vmlabel)
                # VM Config Widget
                vmWidget = VMWidget(None, configname, vmsjsondata["name"],
                                    vmsjsondata)
                self.baseWidgets[configname]["VMWidgets"][vmlabel] = vmWidget
                self.basedataStackedWidget.addWidget(vmWidget)

    ##########material data######
        if "material" in jsondata["xml"]["testbed-setup"]["vm-set"]:
            materialsjsondata = jsondata["xml"]["testbed-setup"]["vm-set"][
                "material"]
            if isinstance(materialsjsondata, list):
                for material in materialsjsondata:
                    material_item = QtWidgets.QTreeWidgetItem(
                        configTreeWidgetItem)
                    materiallabel = "M: " + material["name"]
                    material_item.setText(0, materiallabel)
                    # Material Config Widget
                    materialWidget = MaterialWidget(None, configname,
                                                    material["name"], material)
                    self.baseWidgets[configname]["MaterialWidgets"][
                        materiallabel] = materialWidget
                    self.basedataStackedWidget.addWidget(materialWidget)
            else:
                material_item = QtWidgets.QTreeWidgetItem(configTreeWidgetItem)
                materiallabel = "M: " + materialsjsondata["name"]
                material_item.setText(0, materiallabel)
                # Material Config Widget
                materialWidget = MaterialWidget(None, configname,
                                                materialsjsondata["name"],
                                                materialsjsondata)
                self.baseWidgets[configname]["MaterialWidgets"][
                    materiallabel] = materialWidget
                self.basedataStackedWidget.addWidget(materialWidget)
        logging.debug("MainApp(): Finished loading configname: " +
                      str(configname))

    def onItemSelected(self):
        logging.debug("MainApp:onItemSelected instantiated")
        # Get the selected item
        selectedItem = self.experimentTree.currentItem()
        if selectedItem == None:
            logging.debug("MainApp:onItemSelected no configurations left")
            self.statusBar.showMessage(
                "No configuration items selected or available.")
            return
        self.basedataStackedWidget.setEnabled(True)
        # Now enable the save button
        self.saveButton.setEnabled(True)
        self.saveExperimentMenuButton.setEnabled(True)
        #Check if it's the case that an experiment name was selected
        parentSelectedItem = selectedItem.parent()
        if (parentSelectedItem == None):
            #A base widget was selected
            self.basedataStackedWidget.setCurrentWidget(
                self.baseWidgets[selectedItem.text(0)]["BaseWidget"])
        else:
            #Check if it's the case that a VM Name was selected
            if (selectedItem.text(0)[0] == "V"):
                logging.debug("Setting right widget: " +
                              str(self.baseWidgets[parentSelectedItem.text(0)]
                                  ["VMWidgets"][selectedItem.text(0)]))
                self.basedataStackedWidget.setCurrentWidget(
                    self.baseWidgets[parentSelectedItem.text(0)]["VMWidgets"][
                        selectedItem.text(0)])
            #Check if it's the case that a Material Name was selected
            elif (selectedItem.text(0)[0] == "M"):
                logging.debug("Setting right widget: " +
                              str(self.baseWidgets[parentSelectedItem.text(0)]
                                  ["MaterialWidgets"][selectedItem.text(0)]))
                self.basedataStackedWidget.setCurrentWidget(
                    self.baseWidgets[parentSelectedItem.text(
                        0)]["MaterialWidgets"][selectedItem.text(0)])

    def showContextMenu(self, position):
        logging.debug("MainApp:showContextMenu() instantiated: " +
                      str(position))
        if (self.experimentTree.itemAt(position) == None):
            self.blankTreeContextMenu.popup(
                self.experimentTree.mapToGlobal(position))
        elif (self.experimentTree.itemAt(position).parent() == None):
            self.experimentContextMenu.popup(
                self.experimentTree.mapToGlobal(position))
        else:
            self.itemContextMenu.popup(
                self.experimentTree.mapToGlobal(position))

    def addExperimentActionEvent(self):
        logging.debug("MainApp:addExperimentActionEvent() instantiated")
        configname = ExperimentAddDialog().experimentAddDialog(
            self, self.baseWidgets.keys())

        if configname != None:
            logging.debug(
                "configureVM(): OK pressed and valid configname entered: " +
                str(configname))
        else:
            logging.debug("configureVM(): Cancel pressed or no VM selected")
            return

        ##Now add the item to the tree widget and create the baseWidget
        configTreeWidgetItem = QtWidgets.QTreeWidgetItem(self.experimentTree)
        configTreeWidgetItem.setText(0, configname)
        self.experimentActionsWidget.addExperimentItem(configname)
        self.connectionWidget.addExperimentItem(configname)
        # Base Config Widget
        self.baseWidget = BaseWidget(self, configname, configname)
        self.baseWidgets[configname] = {
            "BaseWidget": {},
            "VMWidgets": {},
            "MaterialWidgets": {}
        }
        self.baseWidgets[configname]["BaseWidget"] = self.baseWidget
        self.basedataStackedWidget.addWidget(self.baseWidget)
        self.statusBar.showMessage("Added new experiment: " + str(configname))

    def importActionEvent(self):
        logging.debug("MainApp:importActionEvent() instantiated")
        #Check if it's the case that an experiment name was selected
        confignamesChosen = PackageImportDialog().packageImportDialog(
            self, self.baseWidgets.keys())
        if confignamesChosen == []:
            logging.debug(
                "importActionEvent(): Canceled or a file could not be imported. make sure file exists."
            )
            return
        #for fileChosen in filesChosen:
        logging.debug(
            "MainApp: importActionEvent(): Files choosen (getting only first): "
            + confignamesChosen)
        firstConfignameChosen = confignamesChosen
        self.loadConfigname(firstConfignameChosen)
        #Add the items to the tree
        self.statusBar.showMessage("Imported " + firstConfignameChosen)

    def addVMActionEvent(self):
        logging.debug("MainApp:addVMActionEvent() instantiated")
        selectedItem = self.experimentTree.currentItem()
        if selectedItem == None:
            logging.debug("MainApp:addVMActionEvent no configurations left")
            self.statusBar.showMessage(
                "Could not add VM. No configuration items selected or available."
            )
            return
        selectedItemName = selectedItem.text(0)
        #Now allow the user to choose the VM:
        (response, vmsChosen) = VMRetrieveDialog(self).exec_()

        if response == QMessageBox.Ok and vmsChosen != None:
            logging.debug("configureVM(): OK pressed and VMs selected " +
                          str(vmsChosen))
        else:
            logging.debug("configureVM(): Cancel pressed or no VM selected")
            return

        if vmsChosen == []:
            logging.debug(
                "configureVM(): Canceled or a file could not be added. Try again later or check permissions"
            )
            return

        for vmChosen in vmsChosen:
            logging.debug("MainApp: addVMActionEvent(): File choosen: " +
                          str(vmChosen))
            #Add the item to the tree
            vmItem = QtWidgets.QTreeWidgetItem(selectedItem)
            vmlabel = "V: " + vmChosen
            vmItem.setText(0, vmlabel)
            # VM Config Widget
            #Now add the item to the stack and list of baseWidgets
            vmjsondata = {"name": vmChosen}
            vmWidget = VMWidget(self, selectedItemName, vmChosen, vmjsondata)
            self.baseWidgets[selectedItemName]["VMWidgets"][vmlabel] = vmWidget
            self.basedataStackedWidget.addWidget(vmWidget)
        #Now add data to the experimentActionWidget associated with the current config
        #Check if it's the case that an experiment name was selected
        parentSelectedItem = selectedItem.parent()
        if parentSelectedItem != None:
            selectedItem = parentSelectedItem
        configname = selectedItem.text(0)
        config_jsondata = self.getWritableData(configname)
        self.experimentActionsWidget.resetExperiment(
            configname, config_jsondata=config_jsondata)
        self.connectionWidget.resetExperiment(configname,
                                              config_jsondata=config_jsondata)
        self.statusBar.showMessage("Added " + str(len(vmsChosen)) +
                                   " VM files to experiment: " +
                                   str(selectedItemName))

    def startHypervisorActionEvent(self):
        logging.debug("MainApp:startHypervisorActionEvent() instantiated")
        logging.debug(
            "MainApp:startHypervisorActionEvent no configurations left")

        # Try to open the hypervisor and check if it worked or not
        result = HypervisorOpenDialog().hypervisorOpenDialog(self)
        if result != "success":
            logging.debug(
                "startHypervisorActionEvent(): Could not start the hypervisor")
            self.statusBar.showMessage("Hypervisor could not be started.")
            return

        self.statusBar.showMessage("Started hypervisor.")

    def addMaterialActionEvent(self):
        logging.debug("MainApp:addMaterialActionEvent() instantiated")
        selectedItem = self.experimentTree.currentItem()
        if selectedItem == None:
            logging.debug(
                "MainApp:addMaterialActionEvent no configurations left")
            self.statusBar.showMessage(
                "Could not add item. No configuration items selected or available."
            )
            return

        selectedItemName = selectedItem.text(0)
        #Check if it's the case that an experiment name was selected
        filesChosen = MaterialAddFileDialog().materialAddFileDialog(
            selectedItemName)
        if filesChosen == []:
            logging.debug(
                "addMaterialActionEvent(): Canceled or a file could not be added. Try again later or check permissions"
            )
            return
        for fileChosen in filesChosen:
            fileChosen = os.path.basename(fileChosen)
            logging.debug("MainApp: addMaterialActionEvent(): File choosen: " +
                          fileChosen)
            #Add the item to the tree
            material_item = QtWidgets.QTreeWidgetItem(selectedItem)
            materiallabel = "M: " + fileChosen
            material_item.setText(0, materiallabel)
            # Material Config Widget
            #Now add the item to the stack and list of baseWidgets
            materialsjsondata = {"name": fileChosen}
            materialWidget = MaterialWidget(self, selectedItemName, fileChosen,
                                            materialsjsondata)
            self.baseWidgets[selectedItem.text(
                0)]["MaterialWidgets"][materiallabel] = materialWidget
            self.basedataStackedWidget.addWidget(materialWidget)
        self.statusBar.showMessage("Added " + str(len(filesChosen)) +
                                   " material files to experiment: " +
                                   str(selectedItemName))

    def removeExperimentItemActionEvent(self):
        logging.debug("MainApp:removeExperimentItemActionEvent() instantiated")
        selectedItem = self.experimentTree.currentItem()
        if selectedItem == None:
            logging.debug("MainApp:onItemSelected no configurations left")
            self.statusBar.showMessage(
                "Could not remove. No configuration items selected or available."
            )
            return

        selectedItemName = selectedItem.text(0)
        #Check if it's the case that an experiment name was selected
        parentSelectedItem = selectedItem.parent()
        if (parentSelectedItem == None):
            #A base widget was selected
            successfilenames = ExperimentRemoveFileDialog(
            ).experimentRemoveFileDialog(selectedItemName)
            if successfilenames == [] or successfilenames == "":
                logging.debug(
                    "removeExperimentItemActionEvent(): Canceled or a file could not be removed. Try again later or check permissions"
                )
                return

            self.experimentTree.invisibleRootItem().removeChild(selectedItem)
            self.basedataStackedWidget.removeWidget(
                self.baseWidgets[selectedItemName]["BaseWidget"])
            del self.baseWidgets[selectedItemName]
            self.experimentActionsWidget.removeExperimentItem(selectedItemName)
            self.experimentActionsWidget.removeExperimentItem(selectedItemName)
            self.statusBar.showMessage("Removed experiment: " +
                                       str(selectedItemName))
        else:
            #Check if it's the case that a VM Name was selected
            if (selectedItem.text(0)[0] == "V"):
                parentSelectedItem.removeChild(selectedItem)
                configname = parentSelectedItem.text(0)
                self.basedataStackedWidget.removeWidget(
                    self.baseWidgets[configname]["VMWidgets"][
                        selectedItem.text(0)])
                del self.baseWidgets[configname]["VMWidgets"][
                    selectedItem.text(0)]
                self.statusBar.showMessage("Removed VM: " +
                                           str(selectedItemName) +
                                           " from experiment: " +
                                           str(parentSelectedItem.text(0)))
                #Also remove from the experiment action widget:
                config_jsondata = self.getWritableData(configname)
                self.experimentActionsWidget.resetExperiment(
                    configname, config_jsondata=config_jsondata)
                self.connectionWidget.resetExperiment(
                    configname, config_jsondata=config_jsondata)

            #Check if it's the case that a Material Name was selected
            elif (selectedItem.text(0)[0] == "M"):
                materialName = selectedItemName.split("M: ")[1]
                successfilenames = MaterialRemoveFileDialog(
                ).materialRemoveFileDialog(parentSelectedItem.text(0),
                                           materialName)
                if successfilenames == []:
                    logging.debug(
                        "Canceled or a file could not be removed. Try again later or check permissions"
                    )
                    return
                parentSelectedItem.removeChild(selectedItem)
                self.basedataStackedWidget.removeWidget(
                    self.baseWidgets[parentSelectedItem.text(
                        0)]["MaterialWidgets"][selectedItem.text(0)])
                del self.baseWidgets[parentSelectedItem.text(
                    0)]["MaterialWidgets"][selectedItem.text(0)]
                self.statusBar.showMessage("Removed Material: " +
                                           str(materialName) +
                                           " from experiment: " +
                                           str(parentSelectedItem.text(0)))

    def exportActionEvent(self):
        logging.debug("MainApp:exportActionEvent() instantiated")
        #Check if it's the case that an experiment name was selected
        selectedItem = self.experimentTree.currentItem()
        if selectedItem == None:
            logging.debug("MainApp:exportActionEvent no configurations left")
            self.statusBar.showMessage(
                "Could not export experiment. No configuration items selected or available."
            )
            return
        selectedItemName = selectedItem.text(0)

        folderChosen = PackageExportDialog().packageExportDialog(
            self, selectedItemName)
        if folderChosen == []:
            logging.debug(
                "exportActionEvent(): Canceled or the experiment could not be exported. Check folder permissions."
            )
            return
        folderChosen = os.path.basename(folderChosen[0])

        logging.debug("MainApp: exportActionEvent(): File choosen: " +
                      folderChosen)
        #Add the items to the tree

        self.statusBar.showMessage("Exported to " + folderChosen)

    def editPathActionEvent(self):
        logging.debug("MainApp:editPathActionEvent() instantiated")
        result = ConfigurationDialog(self).exec_()

    def closeEvent(self, event):
        logging.debug("MainApp:closeEvent(): instantiated")
        logging.debug("closeEvent(): returning accept")
        event.accept()
        qApp.quit()
        return

    def initMenu(self):

        self.mainMenu = QMenuBar()
        self.fileMenu = self.mainMenu.addMenu("File")
        self.editMenu = self.mainMenu.addMenu("Edit")
        self.hypervisorMenu = self.mainMenu.addMenu("Hypervisor")

        self.newExperimentMenuButton = QAction(QIcon(), "New Experiment", self)
        self.newExperimentMenuButton.setShortcut("Ctrl+N")
        self.newExperimentMenuButton.setStatusTip("Create New Experiment")
        self.newExperimentMenuButton.triggered.connect(
            self.addExperimentActionEvent)
        self.fileMenu.addAction(self.newExperimentMenuButton)

        self.importExperimentMenuButton = QAction(QIcon(), "Import Experiment",
                                                  self)
        self.importExperimentMenuButton.setShortcut("Ctrl+I")
        self.importExperimentMenuButton.setStatusTip(
            "Import Experiment from RES File")
        self.importExperimentMenuButton.triggered.connect(
            self.importActionEvent)
        self.fileMenu.addAction(self.importExperimentMenuButton)

        self.saveExperimentMenuButton = QAction(QIcon(), "Save Experiment",
                                                self)
        self.saveExperimentMenuButton.setShortcut("Ctrl+I")
        self.saveExperimentMenuButton.setStatusTip(
            "Save currently selected experiment")
        self.saveExperimentMenuButton.triggered.connect(
            self.saveExperimentButton)
        self.saveExperimentMenuButton.setEnabled(False)
        self.fileMenu.addAction(self.saveExperimentMenuButton)

        self.exitMenuButton = QAction(QIcon("exit24.png"), "Exit", self)
        self.exitMenuButton.setShortcut("Ctrl+Q")
        self.exitMenuButton.setStatusTip("Exit application")
        self.exitMenuButton.triggered.connect(self.close)
        self.fileMenu.addAction(self.exitMenuButton)

        self.pathMenuButton = QAction(QIcon(), "Edit Paths", self)
        self.pathMenuButton.setShortcut("Ctrl+E")
        self.pathMenuButton.setStatusTip("Edit Paths")
        self.pathMenuButton.triggered.connect(self.editPathActionEvent)
        self.editMenu.addAction(self.pathMenuButton)

        self.startHypervisorMenuButton = QAction(QIcon(),
                                                 "Instantiate Hypervisor",
                                                 self)
        self.startHypervisorMenuButton.setShortcut("Ctrl+O")
        self.startHypervisorMenuButton.setStatusTip(
            "Start the hypervisor that is currently configured")
        self.startHypervisorMenuButton.triggered.connect(
            self.startHypervisorActionEvent)
        self.hypervisorMenu.addAction(self.startHypervisorMenuButton)

    def getWritableData(self, configname):
        logging.debug("MainApp: getWritableData() instantiated")
        jsondata = {}
        jsondata["xml"] = {}
        #get baseWidget data
        baseWidget = self.baseWidgets[configname]["BaseWidget"]
        ###TODO: make this work for multiple experiments (current testing assumes only one)
        if isinstance(baseWidget, BaseWidget):
            jsondata["xml"] = baseWidget.getWritableData()
        ###Setup the dictionary
        if "testbed-setup" not in jsondata["xml"]:
            jsondata["xml"]["testbed-setup"] = {}
        if "vm-set" not in jsondata["xml"]["testbed-setup"]:
            jsondata["xml"]["testbed-setup"]["vm-set"] = {}
        if "vm" not in jsondata["xml"]["testbed-setup"]["vm-set"]:
            jsondata["xml"]["testbed-setup"]["vm-set"]["vm"] = []
        if "material" not in jsondata["xml"]["testbed-setup"]["vm-set"]:
            jsondata["xml"]["testbed-setup"]["vm-set"]["material"] = []

        for vmData in self.baseWidgets[configname]["VMWidgets"].values():
            jsondata["xml"]["testbed-setup"]["vm-set"]["vm"].append(
                vmData.getWritableData())
        for materialData in self.baseWidgets[configname][
                "MaterialWidgets"].values():
            jsondata["xml"]["testbed-setup"]["vm-set"]["material"].append(
                materialData.getWritableData())
        return jsondata

    def saveExperimentButton(self):
        logging.debug("MainApp: saveExperiment() instantiated")
        self.saveExperiment()

    def saveExperiment(self, configname=None):
        logging.debug("MainApp: saveExperiment() instantiated")
        selectedItem = self.experimentTree.currentItem()
        if selectedItem == None:
            logging.debug("MainApp:onItemSelected no configurations left")
            self.statusBar.showMessage(
                "Could not save. No configuration items selected or available."
            )
            return
        #Check if it's the case that an experiment name was selected
        parentSelectedItem = selectedItem.parent()
        if parentSelectedItem != None:
            selectedItem = parentSelectedItem
        configname = selectedItem.text(0)
        jsondata = self.getWritableData(configname)

        self.ec.writeExperimentXMLFileData(jsondata, configname)
        self.ec.writeExperimentJSONFileData(jsondata, configname)
        self.ec.getExperimentVMRolledOut(configname,
                                         jsondata,
                                         force_refresh=True)
        res = self.ec.getExperimentServerInfo(configname)
        #Now reset the experimentActions view
        self.experimentActionsWidget.resetExperiment(configname, jsondata)
        self.connectionWidget.resetExperiment(configname, jsondata)
        self.statusBar.showMessage(
            "Succesfully saved experiment file for " + str(configname), 2000)
Пример #2
0
class ExperimentActionsWidget(QtWidgets.QWidget):
    def __init__(self, parent=None, statusBar=None):
        logging.debug("ExperimentActionsWidget instantiated")
        QtWidgets.QWidget.__init__(self, parent=None)
        self.statusBar = statusBar
        self.experimentItemNames = {}
        self.experimentActionsBaseWidgets = {}
        self.eco = ExperimentConfigIO.getInstance()
        self.rolledoutjson = None

        self.setObjectName("ExperimentActionsWidget")

        self.windowWidget = QtWidgets.QWidget()
        self.windowWidget.setObjectName("windowWidget")
        self.windowBoxHLayout = QtWidgets.QHBoxLayout()
        #self.windowBoxHLayout.setContentsMargins(0, 0, 0, 0)
        self.windowBoxHLayout.setObjectName("windowBoxHLayout")
        self.windowWidget.setLayout(self.windowBoxHLayout)

        self.experimentTree = QtWidgets.QTreeWidget(parent)
        self.experimentTree.setObjectName("experimentTree")
        self.experimentTree.header().resizeSection(0, 150)
        self.experimentTree.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
        self.experimentTree.customContextMenuRequested.connect(
            self.showContextMenu)
        self.experimentTree.itemSelectionChanged.connect(self.onItemSelected)
        self.experimentTree.setEnabled(True)
        self.experimentTree.setMinimumSize(200, 521)
        self.experimentTree.setMaximumWidth(350)
        self.experimentTree.setObjectName("experimentTree")
        self.experimentTree.headerItem().setText(0, "Experiments New")
        self.experimentTree.setSortingEnabled(False)
        self.windowBoxHLayout.addWidget(self.experimentTree)

        self.windowBoxVLayout = QtWidgets.QVBoxLayout()
        #self.windowBoxHLayout.setContentsMargins(0, 0, 0, 0)
        self.windowBoxVLayout.setObjectName("windowBoxVLayout")

        self.basedataStackedWidget = QStackedWidget()
        self.basedataStackedWidget.setObjectName("basedataStackedWidget")
        self.basedataStackedWidget.setEnabled(False)
        self.windowBoxVLayout.addWidget(self.basedataStackedWidget)

        self.refreshVMsButton = QtWidgets.QPushButton("Refresh Status")
        self.refreshVMsButton.clicked.connect(self.refreshVMStatus)
        self.refreshVMsButton.setEnabled(False)
        self.windowBoxVLayout.addWidget(self.refreshVMsButton)

        self.windowBoxHLayout.addLayout(self.windowBoxVLayout)

        # Context menus
        self.experimentMenu = QtWidgets.QMenu()
        self.startupContextMenu = QtWidgets.QMenu("Startup")
        self.shutdownContextMenu = QtWidgets.QMenu("Shutdown")
        self.stateContextMenu = QtWidgets.QMenu("State")
        self.experimentMenu.addMenu(self.startupContextMenu)
        self.experimentMenu.addMenu(self.shutdownContextMenu)
        self.experimentMenu.addMenu(self.stateContextMenu)

        self.cloneExperiment = self.startupContextMenu.addAction(
            "Signal - Create Clones")
        self.cloneExperiment.triggered.connect(self.menuItemSelected)

        self.startVMs = self.startupContextMenu.addAction(
            "Signal - Start VMs (headless)")
        self.startVMs.triggered.connect(self.menuItemSelected)

        self.restoreSnapshots = self.startupContextMenu.addAction(
            "Signal - Restore Snapshots")
        self.restoreSnapshots.triggered.connect(self.menuItemSelected)

        self.pauseVMs = self.shutdownContextMenu.addAction(
            "Signal - Pause VMs")
        self.pauseVMs.triggered.connect(self.menuItemSelected)

        self.suspendVMs = self.shutdownContextMenu.addAction(
            "Signal - Suspend & Save States")
        self.suspendVMs.triggered.connect(self.menuItemSelected)

        self.poweroffVMs = self.shutdownContextMenu.addAction(
            "Signal - Power Off VMs")
        self.poweroffVMs.triggered.connect(self.menuItemSelected)

        self.deleteClones = self.shutdownContextMenu.addAction(
            "Signal - Delete Clones")
        self.deleteClones.triggered.connect(self.menuItemSelected)
        self.shutdownContextMenu.addAction(self.deleteClones)

        self.snapshotVMs = self.stateContextMenu.addAction(
            "Signal - Snapshot VMs")
        self.snapshotVMs.triggered.connect(self.menuItemSelected)

        self.setLayout(self.windowBoxHLayout)
        self.retranslateUi()

    def retranslateUi(self):
        logging.debug("ExperimentActionsWidget: retranslateUi(): instantiated")
        self.setWindowTitle("ExperimentActionsWidget")
        self.experimentTree.headerItem().setText(0, "Experiments")
        self.experimentTree.setSortingEnabled(False)

    def onItemSelected(self):
        logging.debug("MainApp:onItemSelected instantiated")
        self.basedataStackedWidget.setEnabled(True)
        self.refreshVMsButton.setEnabled(True)
        # Get the selected item
        selectedItem = self.experimentTree.currentItem()
        if selectedItem == None:
            logging.debug("MainApp:onItemSelected no configurations left")
            self.statusBar.showMessage(
                "No configuration items selected or available.")
            return

        #Check if it's the case that an experiment name was selected
        parentparentSelectedItem = None
        parentSelectedItem = selectedItem.parent()
        if parentSelectedItem != None:
            parentparentSelectedItem = selectedItem.parent().parent()

        if parentSelectedItem == None:
            #A base widget was selected
            self.basedataStackedWidget.setCurrentWidget(
                self.experimentActionsBaseWidgets[selectedItem.text(
                    0)]["ExperimentActionsBaseWidget"])
            self.experimentTree.resizeColumnToContents(0)
        elif parentparentSelectedItem == None:
            #A base widget was selected
            self.basedataStackedWidget.setCurrentWidget(
                self.experimentActionsBaseWidgets[parentSelectedItem.text(
                    0)]["ExperimentActionsBaseWidget"])
            self.experimentTree.resizeColumnToContents(0)
        else:
            #Check if it's the case that a VM Name was selected
            if (selectedItem.text(0)[0] == "V"):
                logging.debug(
                    "Setting right widget: " +
                    str(self.experimentActionsBaseWidgets[
                        parentparentSelectedItem.text(0)]
                        ["ExperimentActionsVMWidgets"][selectedItem.text(0)]))
                self.basedataStackedWidget.setCurrentWidget(
                    self.experimentActionsBaseWidgets[
                        parentparentSelectedItem.text(0)]
                    ["ExperimentActionsVMWidgets"][selectedItem.text(0)])
            if (selectedItem.text(0)[0] == "S"):
                logging.debug(
                    "Setting right widget: " +
                    str(self.experimentActionsBaseWidgets[
                        parentparentSelectedItem.text(0)]
                        ["ExperimentActionsSetWidgets"][selectedItem.text(0)]))
                self.basedataStackedWidget.setCurrentWidget(
                    self.experimentActionsBaseWidgets[
                        parentparentSelectedItem.text(0)]
                    ["ExperimentActionsSetWidgets"][selectedItem.text(0)])
            if (selectedItem.text(0)[0] == "T"):
                logging.debug("Setting right widget: " +
                              str(self.experimentActionsBaseWidgets[
                                  parentparentSelectedItem.text(0)]
                                  ["ExperimentActionsTemplateWidgets"][
                                      selectedItem.text(0)]))
                self.basedataStackedWidget.setCurrentWidget(
                    self.experimentActionsBaseWidgets[
                        parentparentSelectedItem.text(0)]
                    ["ExperimentActionsTemplateWidgets"][selectedItem.text(0)])
            if (selectedItem.text(0)[0] == "U"):
                logging.debug("Setting right widget: " + str(
                    self.experimentActionsBaseWidgets[
                        parentparentSelectedItem.text(0)]
                    ["ExperimentActionsUserWidgets"][selectedItem.text(0)]))
                self.basedataStackedWidget.setCurrentWidget(
                    self.experimentActionsBaseWidgets[
                        parentparentSelectedItem.text(0)]
                    ["ExperimentActionsUserWidgets"][selectedItem.text(0)])

    def getExperimentVMRolledOut(self, configname, config_json):
        logging.debug(
            "ExperimentActionsWidget(): getExperimentVMRolledOut(): retranslateUi(): instantiated"
        )
        self.rolledoutjson = self.eco.getExperimentVMRolledOut(
            configname, config_json)

    def addExperimentItem(self, configname, config_jsondata=None):
        logging.debug("addExperimentItem(): retranslateUi(): instantiated")
        if configname in self.experimentItemNames:
            logging.error(
                "addExperimentItem(): Item already exists in tree: " +
                str(configname))
            return
        userpool = UserPool()
        ##Now add the item to the tree widget and create the baseWidget
        experimentTreeWidgetItem = QtWidgets.QTreeWidgetItem(
            self.experimentTree)
        experimentTreeWidgetItem.setText(0, configname)

        experimentSetTreeItem = QtWidgets.QTreeWidgetItem(
            experimentTreeWidgetItem)
        experimentSetTreeItem.setText(0, "Sets")

        experimentCloneTreeItem = QtWidgets.QTreeWidgetItem(
            experimentTreeWidgetItem)
        experimentCloneTreeItem.setText(0, "Templates")

        experimentVMTreeItem = QtWidgets.QTreeWidgetItem(
            experimentTreeWidgetItem)
        experimentVMTreeItem.setText(0, "VMs")

        experimentUserTreeItem = QtWidgets.QTreeWidgetItem(
            experimentTreeWidgetItem)
        experimentUserTreeItem.setText(0, "Users")

        self.experimentItemNames[configname] = experimentTreeWidgetItem
        #get all rolled out and then get them by VM
        funcs = []
        funcs.append(
            (self.getExperimentVMRolledOut, configname, config_jsondata))
        GUIFunctionExecutingDialog(None,
                                   "Processing VMs for " + str(configname),
                                   funcs).exec_()
        rolledoutjson = self.rolledoutjson
        if rolledoutjson != None:
            # if file was specified, but it doesn't exist, prepend usernames
            invalid_userfile = False
            users_filename = config_jsondata["xml"]["testbed-setup"]["vm-set"][
                "users-filename"]
            if users_filename != None and users_filename.strip() != "":
                if os.path.exists(users_filename) == False:
                    invalid_userfile = True
            #get the usersConn associations first:
            usersConns = userpool.generateUsersConns(
                configname,
                creds_file=users_filename,
                rolledout_json=rolledoutjson)
            vmuser_mapping = {}
            for (username, password) in usersConns:
                for conn in usersConns[(username, password)]:
                    cloneVMName = conn[0]
                    if invalid_userfile == False:
                        vmuser_mapping[cloneVMName] = (username, password)
                    else:
                        vmuser_mapping[cloneVMName] = "userfile_not_found"

            #create the status widgets (tables)
            self.experimentActionsBaseWidget = ExperimentActionsVMStatusWidget(
                self,
                configname,
                rolledoutjson=rolledoutjson,
                interest_vmnames=[],
                vmuser_mapping=vmuser_mapping,
                status_bar=self.statusBar)
            self.experimentActionsBaseWidgets[configname] = {
                "ExperimentActionsBaseWidget": {},
                "ExperimentActionsSetWidgets": {},
                "ExperimentActionsTemplateWidgets": {},
                "ExperimentActionsVMWidgets": {},
                "ExperimentActionsUserWidgets": {}
            }
            self.experimentActionsBaseWidgets[configname][
                "ExperimentActionsBaseWidget"] = self.experimentActionsBaseWidget
            self.basedataStackedWidget.addWidget(
                self.experimentActionsBaseWidget)
            #Set-based view
            (template_vms, num_clones) = rolledoutjson
            #First create the sets from the rolled out data
            sets = self.eco.getExperimentSetDictFromRolledOut(
                configname, rolledoutjson)
            for set in sets:
                set_item = QtWidgets.QTreeWidgetItem(experimentSetTreeItem)
                setlabel = "S: Set " + set
                set_item.setText(0, setlabel)
                # Set Widget
                experimentActionsSetStatusWidget = ExperimentActionsVMStatusWidget(
                    self,
                    configname,
                    rolledoutjson=rolledoutjson,
                    interest_vmnames=sets[set],
                    vmuser_mapping=vmuser_mapping,
                    status_bar=self.statusBar)
                self.experimentActionsBaseWidgets[configname][
                    "ExperimentActionsSetWidgets"][
                        setlabel] = experimentActionsSetStatusWidget
                self.basedataStackedWidget.addWidget(
                    experimentActionsSetStatusWidget)

            templates = self.eco.getExperimentVMNamesFromTemplateFromRolledOut(
                configname, rolledoutjson)
            for templatename in templates:
                template_item = QtWidgets.QTreeWidgetItem(
                    experimentCloneTreeItem)
                templatelabel = "T: " + templatename
                template_item.setText(0, templatelabel)
                # Set Widget
                experimentActionsTemplateStatusWidget = ExperimentActionsVMStatusWidget(
                    self,
                    configname,
                    rolledoutjson=rolledoutjson,
                    interest_vmnames=templates[templatename],
                    vmuser_mapping=vmuser_mapping,
                    status_bar=self.statusBar)
                self.experimentActionsBaseWidgets[configname][
                    "ExperimentActionsTemplateWidgets"][
                        templatelabel] = experimentActionsTemplateStatusWidget
                self.basedataStackedWidget.addWidget(
                    experimentActionsTemplateStatusWidget)

            #Individual VM-based view
            vms_list = self.eco.getExperimentVMListsFromRolledOut(
                configname, rolledoutjson)
            for vm in vms_list:
                vmname = vm["name"]
                vm_item = QtWidgets.QTreeWidgetItem(experimentVMTreeItem)
                vmlabel = "V: " + vmname
                vm_item.setText(0, vmlabel)
                # VM Config Widget
                experimentActionsVMStatusWidget = ExperimentActionsVMStatusWidget(
                    self,
                    configname,
                    rolledoutjson=rolledoutjson,
                    interest_vmnames=[vmname],
                    vmuser_mapping=vmuser_mapping,
                    status_bar=self.statusBar)
                self.experimentActionsBaseWidgets[configname][
                    "ExperimentActionsVMWidgets"][
                        vmlabel] = experimentActionsVMStatusWidget
                self.basedataStackedWidget.addWidget(
                    experimentActionsVMStatusWidget)

            #Individual Users-based view
            num = 1
            for (username, password) in usersConns:
                vmnames = [
                    tuple[0] for tuple in usersConns[(username, password)]
                ]
                user_item = QtWidgets.QTreeWidgetItem(experimentUserTreeItem)
                user_label = "U: " + username + " (Set " + str(num) + ")"
                num += 1
                user_item.setText(0, user_label)
                # VM Config Widget
                experimentActionsUserStatusWidget = ExperimentActionsVMStatusWidget(
                    self,
                    configname,
                    rolledoutjson=rolledoutjson,
                    interest_vmnames=vmnames,
                    vmuser_mapping=vmuser_mapping,
                    status_bar=self.statusBar)
                self.experimentActionsBaseWidgets[configname][
                    "ExperimentActionsUserWidgets"][
                        user_label] = experimentActionsUserStatusWidget
                self.basedataStackedWidget.addWidget(
                    experimentActionsUserStatusWidget)

        self.statusBar.showMessage("Added new experiment: " + str(configname))
        logging.debug("addExperimentItem(): retranslateUi(): Completed")

    def resetExperiment(self, configname, config_jsondata):
        logging.debug("updateExperimentItem(): retranslateUi(): instantiated")
        if configname not in self.experimentItemNames:
            logging.error(
                "removeExperimentItem(): Item does not exist in tree: " +
                str(configname))
            return
        self.removeExperimentItem(configname)
        self.addExperimentItem(configname, config_jsondata)

    def removeExperimentItem(self, configname):
        logging.debug("removeExperimentItem(): retranslateUi(): instantiated")
        if configname not in self.experimentItemNames:
            logging.error(
                "removeExperimentItem(): Item does not exist in tree: " +
                str(configname))
            return
        experimentTreeWidgetItem = self.experimentItemNames[configname]
        self.experimentTree.invisibleRootItem().removeChild(
            experimentTreeWidgetItem)
        del self.experimentItemNames[configname]
        logging.debug("removeExperimentItem(): Completed")

    def showContextMenu(self, position):
        logging.debug(
            "ExperimentActionsWidget(): showContextMenu(): instantiated")
        self.experimentMenu.popup(self.experimentTree.mapToGlobal(position))

    def getTypeNameFromSelection(self):
        configname = ""
        itype = ""
        name = ""
        #configname selected
        if self.experimentTree.currentItem().parent() == None:
            configname = self.experimentTree.currentItem().text(0)
            itype = "set"
            name = "all"
        #sets, clones, or VMs label selected
        elif self.experimentTree.currentItem().parent().parent() == None:
            configname = self.experimentTree.currentItem().parent().text(0)
            itype = "set"
            name = "all"
        #specific item selected
        elif self.experimentTree.currentItem().parent().parent().parent(
        ) == None:
            configname = self.experimentTree.currentItem().parent().parent(
            ).text(0)
            currItemText = self.experimentTree.currentItem().text(0)
            if currItemText.startswith("S: Set "):
                itype = "set"
                name = currItemText.split("S: Set ")[1:]
                name = " ".join(name)
            elif currItemText.startswith("V: "):
                itype = "vm"
                name = currItemText.split("V: ")[1:]
                name = "\"" + " ".join(name) + "\""
            elif currItemText.startswith("T: "):
                itype = "template"
                name = currItemText.split("T: ")[1:]
                name = "\"" + " ".join(name) + "\""
            elif currItemText.startswith("U: "):
                itype = "set"
                name = currItemText.split("(Set ")[1].split(")")[0:-1]
                name = " ".join(name)
        return configname, itype, name

    def menuItemSelected(self):
        logging.debug("menuItemSelected(): instantiated")
        actionlabelname = self.sender().text()
        configname, itype, name = self.getTypeNameFromSelection()
        ExperimentActions().experimentActionEvent(configname, actionlabelname,
                                                  itype, name)
        self.statusBar.showMessage("Executed " + str(actionlabelname) +
                                   " on " + configname)

    def refreshVMStatus(self):
        logging.debug("refreshVMStatus(): instantiated")

        #Get the configname based on selected item:
        selectedItem = self.experimentTree.currentItem()
        #Check if an experiment name is selected
        if selectedItem == None:
            logging.error("No experiment label was selected.")
            return

        #If so, get the configname associated with it
        while selectedItem.parent() != None:
            selectedItem = selectedItem.parent()
        configname = selectedItem.text(0)
        s = VMRetrievingDialog(self).exec_()
        self.vms = s["vmstatus"]

        #Update all vm status in the subtrees
        #First the "all" view
        for widget in self.experimentActionsBaseWidgets[configname].values():
            if isinstance(widget, ExperimentActionsVMStatusWidget):
                widget.updateVMStatus(self.vms)
        #The Sets:
        for widget in self.experimentActionsBaseWidgets[configname][
                "ExperimentActionsSetWidgets"].values():
            if isinstance(widget, ExperimentActionsVMStatusWidget):
                widget.updateVMStatus(self.vms)
        #The Templates:
        for widget in self.experimentActionsBaseWidgets[configname][
                "ExperimentActionsTemplateWidgets"].values():
            if isinstance(widget, ExperimentActionsVMStatusWidget):
                widget.updateVMStatus(self.vms)
        #The VMs
        for widget in self.experimentActionsBaseWidgets[configname][
                "ExperimentActionsVMWidgets"].values():
            if isinstance(widget, ExperimentActionsVMStatusWidget):
                widget.updateVMStatus(self.vms)
        #The Users
        for widget in self.experimentActionsBaseWidgets[configname][
                "ExperimentActionsUserWidgets"].values():
            if isinstance(widget, ExperimentActionsVMStatusWidget):
                widget.updateVMStatus(self.vms)
class QKeithleyBiasWidget:
    def __init__(self, _app, _name):

        # Cache a reference to the calling application
        self._app = _app
        self._name = _name

        # Set thread variables
        self.thread, self.thread_running = None, False

        # Generate widgets
        self.gen_ctrl_widget()
        self.gen_plot_widget()
        self.gen_output_widget()

        # Reset the keithley
        if self._name != "__none__":

            self.keithley().rst()
            self.keithley().set_voltage(self.voltage_bias.value())
            self.keithley().current_cmp(self.voltage_cmpl.value())

    def keithley(self):
        return self._app.get_device_by_name(self._name)

    #####################################
    # APPLICATION HELPER METHODS
    #

    def get_output_widget(self):
        return self.output_widget[0]

    def get_ctrl_widget(self):
        return self.ctl_widget

    def get_plot_widget(self):
        return self.plot_stack

    # Create a QStateMachine and output button for each connected insturment
    def gen_output_widget(self):

        if self._name != "__none__":

            # Each output is a tuple [QPushButton, QStateMachine]
            self.output_widget = (QPushButton(), QStateMachine())
            self.output_widget[0].setStyleSheet(
                "background-color: #dddddd; border-style: solid; border-width: 1px; border-color: #aaaaaa; padding: 7px;"
            )

            # Create output states
            output_off = QState()
            output_on = QState()

            # Attach states to output button and define state transitions
            output_off.assignProperty(self.output_widget[0], 'text',
                                      "%s Output On" % self._name.split()[1])
            output_off.addTransition(self.output_widget[0].clicked, output_on)
            output_off.entered.connect(self.exec_output_off)

            output_on.assignProperty(self.output_widget[0], 'text',
                                     '%s Output Off' % self._name.split()[1])
            output_on.addTransition(self.output_widget[0].clicked, output_off)
            output_on.entered.connect(self.exec_output_on)

            # Add states, set initial state, and start machine
            self.output_widget[1].addState(output_off)
            self.output_widget[1].addState(output_on)
            self.output_widget[1].setInitialState(output_off)
            self.output_widget[1].start()

        else:

            self.output_widget = (QPushButton(), False)
            self.output_widget[0].setStyleSheet(
                "background-color: #dddddd; border-style: solid; border-width: 1px; border-color: #aaaaaa; padding: 7px;"
            )
            self.output_widget[0].setEnabled(False)
            self.output_widget[0].setText("Keithley not Initialized")

    # Generate bias control widget
    def gen_ctrl_widget(self):

        # Control layout
        self.ctl_widget = QWidget()
        self.ctl_layout = QVBoxLayout()

        # Main mode selctor
        self.src_select_label = QLabel("Bias Mode")
        self.src_select = QComboBox()
        self.src_select.setFixedWidth(200)
        self.src_select.addItems(["Voltage", "Current"])
        self.src_select.currentTextChanged.connect(self.update_bias_ctrl)

        # Generate voltage and current source widgets
        self.gen_voltage_src()  # self.voltage_src
        self.gen_current_src()  # self.current_src

        # Add to stacked widget
        self.src_pages = QStackedWidget()
        self.src_pages.addWidget(self.voltage_src)
        self.src_pages.addWidget(self.current_src)
        self.src_pages.setCurrentIndex(0)

        # Disable controls if "__none__ passed as name"
        if self._name == "__none__":
            self.src_select_label.setEnabled(False)
            self.src_select.setEnabled(False)
            self.src_pages.setEnabled(False)

        #####################################
        #  ADD CONTROLS
        #

        # Main output and controls
        self.ctl_layout.addWidget(
            self._app._gen_hbox_widget(
                [self.src_select, self.src_select_label]))
        self.ctl_layout.addWidget(self.src_pages)
        self.ctl_layout.setContentsMargins(0, 0, 0, 0)

        # Set layouth
        self.ctl_widget.setLayout(self.ctl_layout)

    # Generate voltage and current sources
    def gen_voltage_src(self):

        # New QWidget
        self.voltage_src = QWidget()
        self.voltage_layout = QVBoxLayout()

        # Configuration for bias level unit box
        self.voltage_bias_config = {
            "unit": "V",
            "min": "u",
            "max": "",
            "label": "Bias Level",
            "limit": 20.0,
            "signed": True,
            "default": [0.0, ""]
        }
        self.voltage_bias = QVisaUnitSelector.QVisaUnitSelector(
            self.voltage_bias_config)
        self.voltage_bias.unit_value.valueChanged.connect(self.update_bias)
        self.voltage_bias.unit_select.currentTextChanged.connect(
            self.update_bias)

        # Compliance Spinbox
        self.voltage_cmpl_config = {
            "unit": "A",
            "min": "u",
            "max": "",
            "label": "Compliance",
            "limit": 1.0,
            "signed": False,
            "default": [100, "m"]
        }
        self.voltage_cmpl = QVisaUnitSelector.QVisaUnitSelector(
            self.voltage_cmpl_config)
        self.voltage_cmpl.unit_value.valueChanged.connect(self.update_cmpl)
        self.voltage_cmpl.unit_select.currentTextChanged.connect(
            self.update_cmpl)

        # Measurement Delay
        self.voltage_delay_config = {
            "unit": "__DOUBLE__",
            "label": "Measurement Interval (s)",
            "limit": 60.0,
            "signed": False,
            "default": [0.1]
        }
        self.voltage_delay = QVisaUnitSelector.QVisaUnitSelector(
            self.voltage_delay_config)

        # Pack selectors into layout
        self.voltage_layout.addWidget(self.voltage_bias)
        self.voltage_layout.addWidget(self.voltage_cmpl)
        self.voltage_layout.addWidget(self.voltage_delay)
        self.voltage_layout.setContentsMargins(0, 0, 0, 0)

        # Set layout
        self.voltage_src.setLayout(self.voltage_layout)

    def gen_current_src(self):

        # New QWidget
        self.current_src = QWidget()
        self.current_layout = QVBoxLayout()

        # Configuration for bias level unit box
        self.current_bias_config = {
            "unit": "A",
            "min": "u",
            "max": "",
            "label": "Bias Level",
            "limit": 1.0,
            "signed": True,
            "default": [1.0, "m"]
        }
        self.current_bias = QVisaUnitSelector.QVisaUnitSelector(
            self.current_bias_config)
        self.current_bias.unit_value.valueChanged.connect(self.update_bias)
        self.current_bias.unit_select.currentTextChanged.connect(
            self.update_bias)

        # Compliance Spinbox
        self.current_cmpl_config = {
            "unit": "V",
            "min": "u",
            "max": "",
            "label": "Compliance",
            "limit": 1.0,
            "signed": False,
            "default": [1, ""]
        }
        self.current_cmpl = QVisaUnitSelector.QVisaUnitSelector(
            self.current_cmpl_config)
        self.current_cmpl.unit_value.valueChanged.connect(self.update_cmpl)
        self.current_cmpl.unit_select.currentTextChanged.connect(
            self.update_cmpl)

        # Measurement Delay
        self.current_delay_config = {
            "unit": "__DOUBLE__",
            "label": "Measurement Interval (s)",
            "limit": 60.0,
            "signed": False,
            "default": [0.1]
        }
        self.current_delay = QVisaUnitSelector.QVisaUnitSelector(
            self.current_delay_config)

        # Pack selectors into layout
        self.current_layout.addWidget(self.current_bias)
        self.current_layout.addWidget(self.current_cmpl)
        self.current_layout.addWidget(self.current_delay)
        self.current_layout.setContentsMargins(0, 0, 0, 0)

        # Set layout
        self.current_src.setLayout(self.current_layout)

    # Dynamic Plotting Capability
    def gen_plot_widget(self):

        # Create QVisaDynamicPlot Object (inherits QWidget)
        self.voltage_plot = QVisaDynamicPlot.QVisaDynamicPlot(self._app)
        self.voltage_plot.add_subplot("111")
        self.voltage_plot.set_axes_labels("111", "Time (s)", "Current (A)")

        self.voltage_plot.refresh_canvas(supress_warning=True)

        # Create QVisaDynamicPlot Object (inherits QWidget)
        self.current_plot = QVisaDynamicPlot.QVisaDynamicPlot(self._app)
        self.current_plot.add_subplot("111")
        self.current_plot.set_axes_labels("111", "Time (s)", "Voltage (V)")
        self.current_plot.refresh_canvas(supress_warning=True)

        # Add to plot stack
        self.plot_stack = QStackedWidget()
        self.plot_stack.addWidget(self.voltage_plot)
        self.plot_stack.addWidget(self.current_plot)
        self.plot_stack.setCurrentIndex(0)

        # Sync plot clear data button with application data
        self.voltage_plot.sync_application_data(True)
        self.current_plot.sync_application_data(True)

        # Sync meta widget when clearing data from plots
        self.voltage_plot.set_mpl_refresh_callback(
            "_sync_meta_widget_to_data_object")
        self.current_plot.set_mpl_refresh_callback(
            "_sync_meta_widget_to_data_object")

    #####################################
    #  BIAS CONTROL UPDATE METHODS
    #

    # Update bias values
    def update_bias(self):

        if self.src_select.currentText() == "Voltage":
            self.keithley().set_voltage(self.voltage_bias.value())

        if self.src_select.currentText() == "Current":
            self.keithley().set_current(self.current_bias.value())

    # Update compliance values
    def update_cmpl(self):

        if self.src_select.currentText() == "Voltage":
            self.keithley().current_cmp(self.voltage_cmpl.value())

        if self.src_select.currentText() == "Current":
            self.keithley().voltage_cmp(self.current_cmpl.value())

    # Update bias control selectors
    def update_bias_ctrl(self):

        # Switch to voltage page
        if self.src_select.currentText() == "Voltage":

            # Update src_pages and plot
            self.src_pages.setCurrentIndex(0)
            self.plot_stack.setCurrentIndex(0)

            # Keithley to voltage source
            if self.keithley() is not None:

                self.keithley().voltage_src()
                self.update_bias()
                self.update_cmpl()

        # Switch to current page
        if self.src_select.currentText() == "Current":

            # Update src_pages and plot
            self.src_pages.setCurrentIndex(1)
            self.plot_stack.setCurrentIndex(1)

            # Keithley to current source
            if self.keithley() is not None:

                self.keithley().current_src()
                self.update_bias()
                self.update_cmpl()

    #####################################
    #  MEASUREMENT EXECUTION THREADS
    #

    # Measurement thread
    def exec_output_thread(self):

        # Check mesurement type for datafile
        if self.src_select.currentText() == "Voltage":
            _type = "v-bias"

        if self.src_select.currentText() == "Current":
            _type = "i-bias"

        # Get QVisaDataObject
        data = self._app._get_data_object()
        key = data.add_hash_key(_type)

        # Add key to meta widget
        self._app.meta_widget.add_meta_key(key)

        # Add data fields to key
        data.set_subkeys(key, ["t", "V", "I", "P"])
        data.set_metadata(key, "__type__", _type)

        # Voltage and current arrays
        _plot = self.plot_stack.currentWidget()
        handle = _plot.add_axes_handle("111", key)
        start = time.time()

        # Thread loop
        while self.thread_running:

            # Get data from buffer
            _buffer = self.keithley().meas().split(",")

            # If in current mode, plot voltage
            if self.src_select.currentText() == "Current":
                _p = _buffer[0]

                # Measurement delay
                if self.current_delay.value() != 0:
                    time.sleep(self.current_delay.value())

            # It in voltage mode plot current
            if self.src_select.currentText() == "Voltage":
                _p = _buffer[1]

                # Measurement delay
                if self.voltage_delay.value() != 0:
                    time.sleep(self.voltage_delay.value())

            # Extract data from buffer
            _now = float(time.time() - start)

            # Append measured values to data arrays
            data.append_subkey_data(key, "t", _now)
            data.append_subkey_data(key, "V", float(_buffer[0]))
            data.append_subkey_data(key, "I", float(_buffer[1]))
            data.append_subkey_data(key, "P",
                                    float(_buffer[0]) * float(_buffer[1]))

            # Append data to handle
            _plot.append_handle_data("111", key, _now, float(_p))
            _plot.update_canvas()

    # UI output on state (measurement)
    def exec_output_on(self):

        if self.keithley() is not None:

            # Update UI for ON state
            self.output_widget[0].setStyleSheet(
                "background-color: #cce6ff; border-style: solid; border-width: 1px; border-color: #1a75ff; padding: 7px;"
            )

            # Disable controls
            self.src_select.setEnabled(False)
            self.voltage_cmpl.setEnabled(False)
            self.current_cmpl.setEnabled(False)
            _plot = self.plot_stack.currentWidget()
            _plot.mpl_refresh_setEnabled(False)

            # Disable save widget if it exists
            if hasattr(self._app, 'save_widget'):
                self._app.save_widget.setEnabled(False)

            # Turn output ON
            self.keithley().output_on()

            # Each output is a list [QPushButton, QStateMachine, thrading.Thread, threadRunning(bool)]
            # Create execution thread for measurement
            self.thread = threading.Thread(target=self.exec_output_thread,
                                           args=())
            self.thread.daemon = True  # Daemonize thread
            self.thread.start()  # Start the execution
            self.thread_running = True

    # UI output on state
    def exec_output_off(self):

        if self.keithley() is not None:

            # Get output name from inst_widget
            self.output_widget[0].setStyleSheet(
                "background-color: #dddddd; border-style: solid; border-width: 1px; border-color: #aaaaaa; padding: 7px;"
            )

            # Set thread halt boolean
            self.thread_running = False

            # Wait for thread termination
            if self.thread is not None:
                self.thread.join()

            # Enable controls
            self.src_select.setEnabled(True)
            self.voltage_cmpl.setEnabled(True)
            self.current_cmpl.setEnabled(True)
            _plot = self.plot_stack.currentWidget()
            _plot.mpl_refresh_setEnabled(True)

            # Enable save widget if it exists
            if hasattr(self._app, 'save_widget'):
                self._app.save_widget.setEnabled(True)

            # Turn output OFF
            self.keithley().output_off()