Beispiel #1
0
    def __init__(self, parent=None):
        QtGui.QWidget.__init__(self, parent)
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)
        # TODO: figure out way to configure for different comm types (TCP, MAVLINK, etc) 
        self.comm = AQSerial()
        
        # Default main window conditions
        self.ui.buttonDisconnect.setEnabled(False)
        self.ui.buttonConnect.setEnabled(True)
        self.ui.comPort.setEnabled(True)
        self.ui.baudRate.setEnabled(True)
        self.ui.status.setText("Not connected to the AeroQuad")
        self.availablePorts = []
        self.updateComPortSelection()
        self.updateBaudRates()
        self.boardConfiguration = {}
        self.manualConnect = True
        
        # Update comm port combo box to use last used comm port
        defaultComPort = xml.find("./Settings/DefaultComPort").text
        commIndex = self.ui.comPort.findText(defaultComPort)
        if commIndex == -1:
            commIndex = 0
        self.ui.comPort.setCurrentIndex(commIndex)
        
        # Load splash screen
        splash = Ui_splashScreen()
        splash.setupUi(splash)
        self.ui.subPanel.addWidget(splash)
        
        # Dynamically configure board type menu and subPanel menu from XML configuration file
        self.configureSubPanelMenu()
        self.activeSubPanel = None
        self.activeSubPanelName = ""

        # Connect GUI slots and signals
        self.ui.comPort.return_handler = self.connectBoard
        self.ui.buttonConnect.clicked.connect(self.connectBoard)
        self.ui.buttonDisconnect.clicked.connect(self.disconnectBoard)
        self.ui.actionExit.triggered.connect(QtGui.qApp.quit)
        self.ui.comPort.currentIndexChanged.connect(self.updateDetectedPorts)
        self.ui.actionBootUpDelay.triggered.connect(self.updateBootUpDelay)
        self.ui.actionCommTimeout.triggered.connect(self.updateCommTimeOut)
    def __init__(self, parent=None):
        QtGui.QWidget.__init__(self, parent)
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)
        background = xml.find("./Settings/Background").text
        self.ui.subPanel.setStyleSheet("QStackedWidget{background-image: url(" + background + ");}")
        
        # TODO: figure out way to configure for different comm types (TCP, MAVLINK, etc) 
        self.comm = AQSerial()
        
        # Default main window conditions
        self.ui.buttonDisconnect.setEnabled(False)
        self.ui.buttonConnect.setEnabled(True)
        self.ui.comPort.setEnabled(True)
        self.ui.baudRate.setEnabled(True)
        self.ui.status.setText("Not connected to the AeroQuad")
        self.availablePorts = []
        self.updateComPortSelection()
        self.updateBaudRates()
        self.boardConfiguration = {}
        self.manualConnect = True
        
        # Update comm port combo box to use last used comm port
        defaultComPort = xml.find("./Settings/DefaultComPort").text
        commIndex = self.ui.comPort.findText(defaultComPort)
        if commIndex == -1:
            commIndex = 0
        self.ui.comPort.setCurrentIndex(commIndex)
        
        # Load splash screen
        splash = Ui_splashScreen()
        splash.setupUi(splash)
        self.ui.subPanel.addWidget(splash)
        
        # Dynamically configure board type menu and subPanel menu from XML configuration file
        self.configureSubPanelMenu()
        self.activeSubPanel = None
        self.activeSubPanelName = ""

        # Connect GUI slots and signals
        self.ui.comPort.return_handler = self.connectBoard
        self.ui.buttonConnect.clicked.connect(self.connectBoard)
        self.ui.buttonDisconnect.clicked.connect(self.disconnectBoard)
        self.ui.actionExit.triggered.connect(QtGui.qApp.quit)
        self.ui.comPort.currentIndexChanged.connect(self.updateDetectedPorts)
        self.ui.actionBootUpDelay.triggered.connect(self.updateBootUpDelay)
        self.ui.actionCommTimeout.triggered.connect(self.updateCommTimeOut)
        self.ui.buttonMenu.clicked.connect(self.returnToMenu)
Beispiel #3
0
class AQMain(QtGui.QMainWindow):
    def __init__(self, parent=None):
        QtGui.QWidget.__init__(self, parent)
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)
        # TODO: figure out way to configure for different comm types (TCP, MAVLINK, etc) 
        self.comm = AQSerial()
        
        # Default main window conditions
        self.ui.buttonDisconnect.setEnabled(False)
        self.ui.buttonConnect.setEnabled(True)
        self.ui.comPort.setEnabled(True)
        self.ui.baudRate.setEnabled(True)
        self.ui.status.setText("Not connected to the AeroQuad")
        self.availablePorts = []
        self.updateComPortSelection()
        self.updateBaudRates()
        self.boardConfiguration = {}
        self.manualConnect = True
        
        # Update comm port combo box to use last used comm port
        defaultComPort = xml.find("./Settings/DefaultComPort").text
        commIndex = self.ui.comPort.findText(defaultComPort)
        if commIndex == -1:
            commIndex = 0
        self.ui.comPort.setCurrentIndex(commIndex)
        
        # Load splash screen
        splash = Ui_splashScreen()
        splash.setupUi(splash)
        self.ui.subPanel.addWidget(splash)
        
        # Dynamically configure board type menu and subPanel menu from XML configuration file
        self.configureSubPanelMenu()
        self.activeSubPanel = None
        self.activeSubPanelName = ""

        # Connect GUI slots and signals
        self.ui.comPort.return_handler = self.connectBoard
        self.ui.buttonConnect.clicked.connect(self.connectBoard)
        self.ui.buttonDisconnect.clicked.connect(self.disconnectBoard)
        self.ui.actionExit.triggered.connect(QtGui.qApp.quit)
        self.ui.comPort.currentIndexChanged.connect(self.updateDetectedPorts)
        self.ui.actionBootUpDelay.triggered.connect(self.updateBootUpDelay)
        self.ui.actionCommTimeout.triggered.connect(self.updateCommTimeOut)

    ####### Communication Methods #######       
    def connectBoard(self):
        '''Initiates communication with the AeroQuad'''
        # Setup GUI
        self.ui.status.setText("Connecting...")
        self.ui.buttonDisconnect.setEnabled(True)
        self.ui.buttonConnect.setEnabled(False)
        self.ui.comPort.setEnabled(False)
        self.ui.baudRate.setEnabled(False)
        # Update the GUI
        app.processEvents()
        
        # Setup serial port
        bootupDelay = float(xml.find("./Settings/BootUpDelay").text)
        commTimeOut = float(xml.find("./Settings/CommTimeOut").text)
        try:
            self.comm.connect(str(self.ui.comPort.currentText()), int(self.ui.baudRate.currentText()), bootupDelay, commTimeOut)
            # Stop and flush any previous telemetry being streamed
            stopTelemetry = xml.find("./Settings/StopTelemetry").text
            self.comm.write(stopTelemetry)
            self.comm.flushResponse()
            # Request version number to identify AeroQuad board
            versionRequest = xml.find("./Settings/SoftwareVersion").text
            self.comm.write(versionRequest)
            version = self.comm.waitForRead()

            if version != "":
                self.storeComPortSelection()
                self.ui.status.setText("Connected to AeroQuad Flight Software v" + version)
                # Read board configuration
                config = xml.find("./Settings/BoardConfiguration").text
                self.comm.write(config)
                size = int(self.comm.waitForRead())
                for index in range(size):
                    response = self.comm.waitForRead()
                    configuration = response.split(':')
                    self.boardConfiguration[configuration[0]] = configuration[1].strip()
            
                # Hide menu items that don't match board configuration
                for index in range(len(self.subPanelMenu)):
                    hide = self.checkRequirementsMatch(self.subPanelList[index])
                    self.subPanelMenu[index].setVisible(hide)
                # Load configuration screen
                self.selectSubPanel("Vehicle Configuration")
                self.restartSubPanel()
                return True
            else:
                self.disconnectBoard()
                self.ui.status.setText("Not connected to the AeroQuad")
                if self.manualConnect:
                    QtGui.QMessageBox.information(self, "Connection Error", "Unable to connect to the AeroQuad.  Try increasing the Boot Up Delay.\nThis is found under File->Preferences->Boot Up Delay.")
                return False
        except SerialException:
            self.ui.buttonDisconnect.setEnabled(False)
            self.ui.buttonConnect.setEnabled(True)
            self.ui.comPort.setEnabled(True)
            self.ui.baudRate.setEnabled(True)
            self.ui.status.setText("Connection Failed")
            self.boardConfiguration = {}
            return False
        
    def disconnectBoard(self):
        '''Disconnect from the AeroQuad'''
        self.comm.write(xml.find("./Settings/StopTelemetry").text)
        self.comm.disconnect()
        # Update GUI
        self.ui.buttonDisconnect.setEnabled(False)
        self.ui.buttonConnect.setEnabled(True)
        self.ui.comPort.setEnabled(True)
        self.ui.baudRate.setEnabled(True)
        self.ui.status.setText("Disconnected from the AeroQuad")
        self.boardConfiguration = {}
        self.restartSubPanel()

    def updateDetectedPorts(self):
        '''Cycles through 256 ports and checks if there is a response from them.'''
        selection = self.ui.comPort.currentText()
        if selection == "Refresh":
            self.updateComPortSelection()
            self.ui.comPort.setCurrentIndex(0)
            self.ui.status.setText("Updated list of available COM ports")
        elif selection == "Autoconnect":
            self.updateComPortSelection()
            self.ui.comPort.setCurrentIndex(0)
            self.ui.status.setText("Beginning autoconnect...")
            self.autoConnect()
            
    def autoConnect(self):
        self.manualConnect = False
        for port in xrange(self.ui.comPort.count() - 2):
            self.ui.comPort.setCurrentIndex(port)
            self.ui.status.setText("Attempting to connect to " + self.ui.comPort.currentText() + "...")
            if self.connectBoard():
                self.ui.status.setText("Autoconnect successful!")
                break
            else:
                self.ui.status.setText("Autoconnect not successful...")
        self.manualConnect = True
    
    def updateBootUpDelay(self):
        '''Creates dialog box to ask user for desired boot up delay.
        This delay waits for Arduino based boards to finish booting up before sending commands.
        '''
        bootUpDelay = float(xml.find("./Settings/BootUpDelay").text)
        data, ok = QtGui.QInputDialog.getDouble(self, "Boot Up Delay", "Boot Up Delay:", bootUpDelay, 0, 60, 3)
        if ok:
            xml.find("./Settings/BootUpDelay").text = str(data)
            xml.write("AeroQuadConfigurator.xml")
 
    def updateCommTimeOut(self):
        '''Creates dialog box to ask user for desired comm timeout.
        This is timeout value used by serial drivers to wait for response from device
        '''
        commTimeOut = float(xml.find("./Settings/CommTimeOut").text)
        data, ok = QtGui.QInputDialog.getDouble(self, "Comm Time Out", "Comm Time Out:", commTimeOut, 0, 60, 3)
        if ok:
            xml.find("./Settings/CommTimeOut").text = str(data)
            xml.write("AeroQuadConfigurator.xml")

    def updateComPortSelection(self):
        '''Look for available comm ports and updates combo box'''
        self.ui.comPort.clear()
        for n in self.comm.detectPorts():
            self.ui.comPort.addItem(n)
        self.ui.comPort.insertSeparator(self.ui.comPort.count())
        self.ui.comPort.addItem("Autoconnect")
        self.ui.comPort.addItem("Refresh")
        
    def storeComPortSelection(self):
        '''Stores comm port selection to xml file for later recall'''
        xml.find("./Settings/DefaultBaudRate").text = str(self.ui.baudRate.currentText())
        xml.find("./Settings/DefaultComPort").text = str(self.ui.comPort.currentText())
        xml.write("AeroQuadConfigurator.xml")
               
    def updateBaudRates(self):
        '''Reads baud rates from xml and displays in combo box.
        Updates the xml file to display different baud rates
        '''
        defaultBaudRate = xml.find("./Settings/DefaultBaudRate").text
        baudRates = xml.find("./Settings/AvailableBaudRates").text
        baudRate = baudRates.split(',')
        for i in baudRate:
            self.ui.baudRate.addItem(i)
        self.ui.baudRate.setCurrentIndex(baudRate.index(defaultBaudRate))     


    ####### SubPanel Methods #######
    def configureSubPanelMenu(self):
        '''Dynamically add subpanels to View menu based on XML file configuration
        This also adds the subpanel to a stacked widget and stores object instances so that they can run when selected'''
        subPanels = xml.findall("./Subpanels/Subpanel")
        subPanelCount = 1
        self.subPanelList = [] # Stores subpanel names
        self.subPanelClasses = [] # Stores subpanel object instances
        for subPanel in subPanels:
            self.subPanelList.append(subPanel.get("Name"))
            pathName = xml.find("./Subpanels/Subpanel/[@Name='" + subPanel.get("Name") +"']/Path").text
            className = xml.find("./Subpanels/Subpanel/[@Name='" + subPanel.get("Name") +"']/Class").text
            packageList = pathName.split('.')
            packageList.insert(0, 'subpanel')
            packageString = packageList[0] + '.' + packageList[1] + '.' + packageList[2]
            module = __import__(packageString)
            for package in packageList[1:]: # In case the module is buried into a deep package folder, loop until module is reached
                module = getattr(module, package)
            module = getattr(module, className)
            tempSubPanel = module()          
            tempSubPanel.initialize(self.comm, xml, self.ui)
            self.ui.subPanel.addWidget(tempSubPanel)
            self.subPanelClasses.append(tempSubPanel)
            subPanelCount += 1
        self.subPanelMapper = QtCore.QSignalMapper(self)
        self.subPanelMenu = []
        for subPanelName in self.subPanelList:
            subPanel = self.ui.menuView.addAction(subPanelName)
            self.subPanelMenu.append(subPanel) # Need to store this separately because Python only binds stuff at runtime
            self.subPanelMapper.setMapping(subPanel, subPanelName)
            subPanel.triggered.connect(self.subPanelMapper.map)
            subPanel.setCheckable(True)
        self.subPanelMapper.mapped[str].connect(self.selectSubPanel)       
  
    def selectSubPanel(self, subPanelName):
        '''Places check mark beside selected subpanel name
        Menu item instances stored in dedicated list because Python only updates during runtime making everything point to the last item in the list
        '''
        if self.activeSubPanel != None:
            self.activeSubPanel.stop()
        types = len(self.subPanelList)
        for index in range(types):
            self.subPanelMenu[index].setChecked(False)
        selected = self.subPanelList.index(subPanelName)
        self.subPanelMenu[selected].setChecked(True)
        self.ui.subPanel.setCurrentIndex(selected+1) # index 0 is splash screen
        self.activeSubPanel = self.subPanelClasses[selected]
        self.activeSubPanelName = "./Subpanels/Subpanel/[@Name='" + str(subPanelName) + "']" 
        self.activeSubPanel.start(self.activeSubPanelName, self.boardConfiguration)
        self.ui.status.setText(subPanelName)
        app.processEvents()

    def clearSubPanelMenu(self):
        ''' Clear subPanel menu and disconnect subPanel related signals'''
        self.ui.menuView.clear()
        self.subPanelMapper.mapped[str].disconnect(self.selectSubPanel)
        
    def restartSubPanel(self):
        if self.activeSubPanel != None: # Restart any running subpanels
            self.activeSubPanel.stop()
            self.activeSubPanel.start(self.activeSubPanelName, self.boardConfiguration)
            app.processEvents()
            
    def checkRequirementsMatch(self, subPanelName):
        # Read requirements for the specified subpanel form the XML config file
        xmlRequirement = "./Subpanels/Subpanel/[@Name='" + subPanelName +"']/Requirement"
        subPanelRequirements = xml.findall(xmlRequirement)
        
        panelRequirements = {}
        booleanOperation = {}      
        for requirements in subPanelRequirements:
            requirement = requirements.text.split(':')
            if requirement[0] == "All": # Need element 1 populated if "All" detected
                requirement.append("All")
            panelRequirements[requirement[0]] = requirement[1].strip()
            booleanOperation[requirement[0]] = requirements.get("type")

        # Go through each subpanel requirement and check against board configuration
        # If no boolean type defined, assume AND
        requirementType = panelRequirements.keys()
        # If no Requirement found, assume ALL
        try:
            if (requirementType[0] == "All"):
                check = True
            else:
                check = any(panelRequirements[requirementType[0]] in s for s in self.boardConfiguration.values())
                for testRequirement in requirementType[1:]:
                    if (booleanOperation[testRequirement] == "or") or (booleanOperation[testRequirement] == "OR"):
                        check = check or any(panelRequirements[testRequirement] in s for s in self.boardConfiguration.values())
                    else:
                        check = check and any(panelRequirements[testRequirement] in s for s in self.boardConfiguration.values())
        except:
            check = True
        return check

    ####### Housekeeping Functions #######
    def exit(self):
        self.comm.disconnect()
        sys.exit(app.exec_())

    def center(self):
        qr = self.frameGeometry()
        cp = QtGui.QDesktopWidget().availableGeometry().center()
        qr.moveCenter(cp)
        self.move(qr.topLeft())
Beispiel #4
0
    def __init__(self, ):
        super(MainWindow, self).__init__()
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)
        self.modelArray = [QStandardItemModel(), QStandardItemModel()]
        self.filterProxyArray = [RecursiveProxyModel(), RecursiveProxyModel()]
        self.treeViewArray = [self.ui.treeView, self.ui.treeView_2]
        self.pathLabelArray = [self.ui.labelPath, self.ui.labelPath_2]

        self.setWindowTitle('QDICOMDiffer ' + version)

        # Read the settings from the settings.ini file
        system_location = os.path.dirname(os.path.abspath(sys.argv[0]))
        QSettings.setPath(QSettings.IniFormat, QSettings.SystemScope,
                          system_location)
        self.settings = QSettings("settings.ini", QSettings.IniFormat)
        if os.path.exists(system_location + "/settings.ini"):
            print("Loading settings from " + system_location + "/settings.ini")

        col = self.settings.value('Appearance/directMatchColour')
        if col is None:
            direct_match_colour = default_direct_match_colour
        else:
            direct_match_colour = QColor(col)

        col = self.settings.value('Appearance/indirectMatchColour')
        if col is None:
            indirect_match_colour = default_indirect_match_colour
        else:
            indirect_match_colour = QColor(col)

        font = self.settings.value('Appearance/new_font')

        for i in range(2):
            self.modelArray[i].setHorizontalHeaderLabels(
                ['Tag', 'Description', 'Value', 'Different', 'Index'])
            self.filterProxyArray[i].setSourceModel(self.modelArray[i])
            self.ui.lineEditTagFilter.textChanged.connect(
                self.filterProxyArray[i].set_tag_filter)
            self.ui.lineEditDescFilter.textChanged.connect(
                self.filterProxyArray[i].set_desc_filter)
            self.ui.lineEditValFilter.textChanged.connect(
                self.filterProxyArray[i].set_value_filter)
            self.treeViewArray[i].setModel(self.filterProxyArray[i])
            self.treeViewArray[i].setEditTriggers(
                QAbstractItemView.NoEditTriggers)
            self.treeViewArray[i].setColumnHidden(3, True)
            self.treeViewArray[i].setColumnHidden(4, True)
            # the i=i here is needed to ensure i is within the local namespace, without it i evaluates to 1 both times
            self.ui.checkBoxShowOnlyDifferent.stateChanged.connect(
                lambda state, i=i: self.filterProxyArray[
                    i].set_show_only_different(bool(state)))
            self.treeViewArray[i].file_dropped.connect(
                lambda filepath, i=i: self.load_file(filepath, i))
            self.treeViewArray[i].indirect_match_colour = indirect_match_colour
            self.treeViewArray[i].direct_match_colour = direct_match_colour
            if font is not None:
                self.treeViewArray[i].setFont(font)

        self.ui.splitter.setSizes([100, 0])

        self.ui.actionOpen.triggered.connect(self.open_files)
        self.ui.actionDiff.triggered.connect(self.do_diff)
        self.ui.actionHTML_diff.triggered.connect(self.open_html_diff_window)
        self.ui.actionAbout.triggered.connect(self.open_about_window)
        self.ui.actionAppearance.triggered.connect(self.open_appearance_window)
        self.raw_diff_window = None
        self.html_diff_window = None
        self.appearance_window = None
        self.ui.actionText_diff.triggered.connect(self.open_text_diff_window)
        self.ui.actionExpand_all.triggered.connect(self.expand_all)
        self.ui.actionCollapse_all.triggered.connect(self.collapse_all)
        self.dc_array = [None] * 2

        self.diff_result = None
        self.html_diff_result = None

        # If we were given command line arguments, try and load them
        arguments = sys.argv[1:]
        for number, line in enumerate(arguments):
            self.load_file(line, number)
            # Only ever handle two files
            if number == 1:
                self.ui.splitter.setSizes([50, 50])
                break

        self.show()
Beispiel #5
0
class MainWindow(QtWidgets.QMainWindow):
    def __init__(self, ):
        super(MainWindow, self).__init__()
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)
        self.modelArray = [QStandardItemModel(), QStandardItemModel()]
        self.filterProxyArray = [RecursiveProxyModel(), RecursiveProxyModel()]
        self.treeViewArray = [self.ui.treeView, self.ui.treeView_2]
        self.pathLabelArray = [self.ui.labelPath, self.ui.labelPath_2]

        self.setWindowTitle('QDICOMDiffer ' + version)

        # Read the settings from the settings.ini file
        system_location = os.path.dirname(os.path.abspath(sys.argv[0]))
        QSettings.setPath(QSettings.IniFormat, QSettings.SystemScope,
                          system_location)
        self.settings = QSettings("settings.ini", QSettings.IniFormat)
        if os.path.exists(system_location + "/settings.ini"):
            print("Loading settings from " + system_location + "/settings.ini")

        col = self.settings.value('Appearance/directMatchColour')
        if col is None:
            direct_match_colour = default_direct_match_colour
        else:
            direct_match_colour = QColor(col)

        col = self.settings.value('Appearance/indirectMatchColour')
        if col is None:
            indirect_match_colour = default_indirect_match_colour
        else:
            indirect_match_colour = QColor(col)

        font = self.settings.value('Appearance/new_font')

        for i in range(2):
            self.modelArray[i].setHorizontalHeaderLabels(
                ['Tag', 'Description', 'Value', 'Different', 'Index'])
            self.filterProxyArray[i].setSourceModel(self.modelArray[i])
            self.ui.lineEditTagFilter.textChanged.connect(
                self.filterProxyArray[i].set_tag_filter)
            self.ui.lineEditDescFilter.textChanged.connect(
                self.filterProxyArray[i].set_desc_filter)
            self.ui.lineEditValFilter.textChanged.connect(
                self.filterProxyArray[i].set_value_filter)
            self.treeViewArray[i].setModel(self.filterProxyArray[i])
            self.treeViewArray[i].setEditTriggers(
                QAbstractItemView.NoEditTriggers)
            self.treeViewArray[i].setColumnHidden(3, True)
            self.treeViewArray[i].setColumnHidden(4, True)
            # the i=i here is needed to ensure i is within the local namespace, without it i evaluates to 1 both times
            self.ui.checkBoxShowOnlyDifferent.stateChanged.connect(
                lambda state, i=i: self.filterProxyArray[
                    i].set_show_only_different(bool(state)))
            self.treeViewArray[i].file_dropped.connect(
                lambda filepath, i=i: self.load_file(filepath, i))
            self.treeViewArray[i].indirect_match_colour = indirect_match_colour
            self.treeViewArray[i].direct_match_colour = direct_match_colour
            if font is not None:
                self.treeViewArray[i].setFont(font)

        self.ui.splitter.setSizes([100, 0])

        self.ui.actionOpen.triggered.connect(self.open_files)
        self.ui.actionDiff.triggered.connect(self.do_diff)
        self.ui.actionHTML_diff.triggered.connect(self.open_html_diff_window)
        self.ui.actionAbout.triggered.connect(self.open_about_window)
        self.ui.actionAppearance.triggered.connect(self.open_appearance_window)
        self.raw_diff_window = None
        self.html_diff_window = None
        self.appearance_window = None
        self.ui.actionText_diff.triggered.connect(self.open_text_diff_window)
        self.ui.actionExpand_all.triggered.connect(self.expand_all)
        self.ui.actionCollapse_all.triggered.connect(self.collapse_all)
        self.dc_array = [None] * 2

        self.diff_result = None
        self.html_diff_result = None

        # If we were given command line arguments, try and load them
        arguments = sys.argv[1:]
        for number, line in enumerate(arguments):
            self.load_file(line, number)
            # Only ever handle two files
            if number == 1:
                self.ui.splitter.setSizes([50, 50])
                break

        self.show()

    def open_about_window(self):
        msgBox = QMessageBox()
        msgBox.setWindowTitle("About")
        msgBox.setTextFormat(Qt.RichText)
        msgBox.setText(
            "QDICOMDiffer version " + version + "<br> Written by Keith Offer" +
            "<br> Relies heavily on the <a href='http://www.pydicom.org/'>pydicom</a> library"
        )
        msgBox.setIcon(QMessageBox.Information)
        msgBox.exec()

    def open_appearance_window(self):
        self.appearance_window = AppearanceWindow(self.settings, parent=self)
        if self.appearance_window.exec():
            self.settings.setValue('Appearance/new_font',
                                   self.appearance_window.new_font)
            self.settings.setValue(
                'Appearance/directMatchColour',
                self.appearance_window.direct_match_colour.name())
            self.settings.setValue(
                'Appearance/indirectMatchColour',
                self.appearance_window.indirect_match_colour.name())
            for i in range(2):
                self.treeViewArray[
                    i].direct_match_colour = self.appearance_window.direct_match_colour
                self.treeViewArray[
                    i].indirect_match_colour = self.appearance_window.indirect_match_colour
                if self.appearance_window.new_font is not None:
                    self.treeViewArray[i].setFont(
                        self.appearance_window.new_font)
                self.treeViewArray[i].repaint()

    def open_text_diff_window(self):
        if self.diff_result is not None:
            self.raw_diff_window = TextDiffWindow(self.diff_result)
        else:
            if self.dc_array[0] is not None and self.dc_array[0] is not None:
                self.ui.splitter.setSizes([50, 50])
                self.do_diff()
                # Note that the IDE complains that self.diff_result is still None here
                # It should have a value set by self.do_diff(), so this is not the case
                if self.diff_result is not None:
                    self.raw_diff_window = TextDiffWindow(self.diff_result)

    def open_html_diff_window(self):
        if self.html_diff_result is not None:
            self.html_diff_window = HTMLDiffWindow(self.html_diff_result)
        else:
            if self.dc_array[0] is not None and self.dc_array[0] is not None:
                self.ui.splitter.setSizes([50, 50])
                self.do_diff()
                # Note that the IDE complains that self.diff_result is still None here
                # It should have a value set by self.do_diff(), so this is not the case
                if self.diff_result is not None:
                    self.html_diff_window = HTMLDiffWindow(
                        self.html_diff_result)

    def collapse_all(self):
        for i in range(2):
            self.treeViewArray[i].collapseAll()
            for n in range(3):
                self.treeViewArray[i].resizeColumnToContents(n)

    def expand_all(self):
        for i in range(2):
            self.treeViewArray[i].expandAll()
            for n in range(3):
                self.treeViewArray[i].resizeColumnToContents(n)
                self.treeViewArray[i].resizeColumnToContents(n)

    def set_value_filter(self, filter):
        for i in range(2):
            self.filterProxyArray[i].setFilterKeyColumn(2)
            self.filterProxyArray[i].setFilterFixedString(filter)

    def show_only_diff(self, new_state):
        state = (new_state == 2)
        for i in range(2):
            self.filterProxyArray[i].setFilterKeyColumn(3)
            if state:
                self.filterProxyArray[i].setFilterFixedString(str(int(state)))
            else:
                self.filterProxyArray[i].setFilterFixedString('')

    def open_files(self):
        filepaths = self.get_file_paths()

        if len(filepaths) > 2:
            msgBox = QMessageBox()
            msgBox.setWindowTitle("Warning")
            msgBox.setText('Ignoring all but the first two files')
            msgBox.setIcon(QMessageBox.Warning)
            msgBox.exec()

        if len(filepaths) != 0:
            folder = os.path.dirname(filepaths[0])
            if os.path.exists(folder):
                self.settings.setValue('Browse/LastOpenedLocation', folder)

        if len(filepaths) == 1:
            filepath = filepaths[0]
            if self.dc_array[0] is not None:
                # We are loading a file and there is already one loaded. Do we want to just view or diff?
                msgBox = QMessageBox()
                msgBox.setText(
                    "A file is already loaded. Do you want to diff with the currently loaded file or just load the new file?"
                )
                load_button = QPushButton('Just load the new file')
                msgBox.addButton(load_button, QMessageBox.YesRole)
                diff_button = QPushButton(
                    'Diff with the currently loaded file')
                msgBox.addButton(diff_button, QMessageBox.NoRole)
                msgBox.addButton(QPushButton('Cancel'), QMessageBox.RejectRole)
                msgBox.exec()
                # Note if no button was pressed, no action is taken
                if msgBox.clickedButton() == load_button:
                    self.load_file(filepath, 0)
                elif msgBox.clickedButton() == diff_button:
                    self.load_file(filepath, 1)
                    self.ui.splitter.setSizes([50, 50])
                else:
                    pass
                return

        for file_number, filepath in enumerate(filepaths):
            self.load_file(filepath, file_number)
            if file_number == 1:
                # If more than two files selected, just break after handling the first two
                self.ui.splitter.setSizes([50, 50])
                break

    def get_file_paths(self):
        path_from_settings = self.settings.value('Browse/LastOpenedLocation')
        default_location = '.'
        if path_from_settings is not None:
            if os.path.exists(
                    self.settings.value('Browse/LastOpenedLocation')):
                default_location = self.settings.value(
                    'Browse/LastOpenedLocation')
        """
        This looks a bit strange, but filenames are the first return value of this function
        so we need the [0] on the end to grab what we need
        """
        return QFileDialog.getOpenFileNames(self, 'Open DICOM file ...',
                                            default_location)[0]

    def load_file(self, filepath, file_number):
        try:
            dc = pydicom.read_file(filepath)
            self.dc_array[file_number] = dc
            self.modelArray[file_number].removeRows(
                0, self.modelArray[file_number].rowCount(
                ))  # Remove any rows from old loaded files
            dict_to_tree(
                dc, parent=self.modelArray[file_number].invisibleRootItem())
            self.pathLabelArray[file_number].setText(filepath)
            for n in range(3):
                self.treeViewArray[file_number].resizeColumnToContents(n)
                if file_number == 1:
                    reset_tree_diff_state(
                        self.modelArray[0].invisibleRootItem())
                else:
                    reset_tree_diff_state(
                        self.modelArray[1].invisibleRootItem())
                self.diff_result = None
                self.html_diff_result = None
        except pydicom.errors.InvalidDicomError:
            msgBox = QMessageBox()
            msgBox.setWindowTitle("Error")
            msgBox.setText('Failed to open ' + filepath +
                           ' (is it a valid DICOM file?)')
            msgBox.setIcon(QMessageBox.Critical)
            msgBox.exec()

    def do_diff(self):
        self.diffProgressWindow = DiffProgressWindow(self.dc_array,
                                                     self.modelArray,
                                                     parent=self)
        if self.diffProgressWindow.exec():
            self.html_diff_result = self.diffProgressWindow.get_html_diff_result(
            )
            self.diff_result = self.diffProgressWindow.get_diff_result()
Beispiel #6
0
    def __init__(self):
        super(MainWindow, self).__init__()
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)

        self.setWindowTitle('QDICOMMiner ' + __version__)

        # TODO: Find a better way of stopping the selection of list items. This only sortof works
        self.ui.listWidget.setSelectionMode(QAbstractItemView.NoSelection)

        system_location = os.path.dirname(os.path.realpath(__file__))
        plugin_locations = [os.path.join(system_location, 'Plugins')]
        self.plugin_manager = PluginManager()
        self.plugin_manager.setPluginPlaces(plugin_locations)
        self.plugin_manager.collectPlugins()
        self.plugin_list = []
        for plugin in self.plugin_manager.getAllPlugins():
            self.plugin_list.append(plugin.name)

        # Read the settings from the settings.ini file
        system_location = os.path.dirname(os.path.realpath(__file__))
        QSettings.setPath(QSettings.IniFormat, QSettings.SystemScope,
                          system_location)
        self.settings = QSettings("settings.ini", QSettings.IniFormat)
        if os.path.exists(system_location + "/settings.ini"):
            print("Loading settings from " + system_location + "/settings.ini")

        # Set the last used output file and analyse folder locations
        output_file = self.settings.value('main/lastOutputFile')
        if output_file is None:
            output_file = 'data.csv'
        if not os.path.isabs(output_file):
            abs_output_location = os.path.join(system_location, output_file)
            self.ui.labelOutputFile.setText(abs_output_location)
        else:
            self.ui.labelOutputFile.setText(output_file)

        folder_to_analyse = self.settings.value('main/lastAnalyseFolder')
        if folder_to_analyse is None:
            folder_to_analyse = ''
        if not os.path.isabs(folder_to_analyse):
            abs_folder_to_analyse = os.path.join(system_location,
                                                 folder_to_analyse)
            self.ui.labelFolderToAnalysePath.setText(abs_folder_to_analyse)
        else:
            self.ui.labelFolderToAnalysePath.setText(folder_to_analyse)

        self.ui.labelFolderToAnalysePath.clicked.connect(
            lambda: self.open_folder_in_explorer(
                self.ui.labelFolderToAnalysePath.text()))
        self.ui.labelOutputFile.clicked.connect(
            lambda: self.open_folder_in_explorer(self.ui.labelOutputFile.text(
            )))

        # Setup a dictionary of key DICOM description and value the DICOM tag
        names = []
        self.DICOM_dic = {}
        for key in dicom_dict.DicomDictionary:
            names.append(dicom_dict.DicomDictionary[key][2])
            self.DICOM_dic[dicom_dict.DicomDictionary[key][2]] = key

        self.completer = QCompleter()
        self.completer.setCaseSensitivity(Qt.CaseInsensitive)
        self.model = QStringListModel()
        self.model.setStringList(names)
        self.completer.setModel(self.model)

        self.ui.pushButtonAddListWidget.clicked.connect(
            self.add_new_list_widget)

        self.ui.pushButtonBrowseFolderToAnalysePath.clicked.connect(
            self.browse_for_input_folder)
        self.ui.pushButtonBrowseOutputFilePath.clicked.connect(
            self.browse_for_output_file)
        self.ui.pushButtonDoAnalysis.clicked.connect(self.do_analysis)

        self.count_num_of_files_thread = CountFilesThread()
        self.count_num_of_files_thread.num_of_files.connect(
            self.update_number_of_files)
        self.count_file_number.connect(self.count_num_of_files_thread.count)
        self.count_file_number.emit(self.ui.labelFolderToAnalysePath.text()
                                    )  # Using a signal to keep thread safety

        self.ui.progressBar.setFormat(' %v/%m (%p%)')
        self.ui.progressBar.hide()

        self.ui.actionSave_Template.triggered.connect(self.save_template)
        self.ui.actionLoad_Template.triggered.connect(self.load_template)
        self.ui.actionAbout.triggered.connect(self.open_about_window)

        self.analyse_and_output_data_thread = AnalyseAndOutputDataThread()

        self.show()
Beispiel #7
0
class MainWindow(QtWidgets.QMainWindow):
    def __init__(self):
        super(MainWindow, self).__init__()
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)

        self.setWindowTitle('QDICOMMiner ' + __version__)

        # TODO: Find a better way of stopping the selection of list items. This only sortof works
        self.ui.listWidget.setSelectionMode(QAbstractItemView.NoSelection)

        system_location = os.path.dirname(os.path.realpath(__file__))
        plugin_locations = [os.path.join(system_location, 'Plugins')]
        self.plugin_manager = PluginManager()
        self.plugin_manager.setPluginPlaces(plugin_locations)
        self.plugin_manager.collectPlugins()
        self.plugin_list = []
        for plugin in self.plugin_manager.getAllPlugins():
            self.plugin_list.append(plugin.name)

        # Read the settings from the settings.ini file
        system_location = os.path.dirname(os.path.realpath(__file__))
        QSettings.setPath(QSettings.IniFormat, QSettings.SystemScope,
                          system_location)
        self.settings = QSettings("settings.ini", QSettings.IniFormat)
        if os.path.exists(system_location + "/settings.ini"):
            print("Loading settings from " + system_location + "/settings.ini")

        # Set the last used output file and analyse folder locations
        output_file = self.settings.value('main/lastOutputFile')
        if output_file is None:
            output_file = 'data.csv'
        if not os.path.isabs(output_file):
            abs_output_location = os.path.join(system_location, output_file)
            self.ui.labelOutputFile.setText(abs_output_location)
        else:
            self.ui.labelOutputFile.setText(output_file)

        folder_to_analyse = self.settings.value('main/lastAnalyseFolder')
        if folder_to_analyse is None:
            folder_to_analyse = ''
        if not os.path.isabs(folder_to_analyse):
            abs_folder_to_analyse = os.path.join(system_location,
                                                 folder_to_analyse)
            self.ui.labelFolderToAnalysePath.setText(abs_folder_to_analyse)
        else:
            self.ui.labelFolderToAnalysePath.setText(folder_to_analyse)

        self.ui.labelFolderToAnalysePath.clicked.connect(
            lambda: self.open_folder_in_explorer(
                self.ui.labelFolderToAnalysePath.text()))
        self.ui.labelOutputFile.clicked.connect(
            lambda: self.open_folder_in_explorer(self.ui.labelOutputFile.text(
            )))

        # Setup a dictionary of key DICOM description and value the DICOM tag
        names = []
        self.DICOM_dic = {}
        for key in dicom_dict.DicomDictionary:
            names.append(dicom_dict.DicomDictionary[key][2])
            self.DICOM_dic[dicom_dict.DicomDictionary[key][2]] = key

        self.completer = QCompleter()
        self.completer.setCaseSensitivity(Qt.CaseInsensitive)
        self.model = QStringListModel()
        self.model.setStringList(names)
        self.completer.setModel(self.model)

        self.ui.pushButtonAddListWidget.clicked.connect(
            self.add_new_list_widget)

        self.ui.pushButtonBrowseFolderToAnalysePath.clicked.connect(
            self.browse_for_input_folder)
        self.ui.pushButtonBrowseOutputFilePath.clicked.connect(
            self.browse_for_output_file)
        self.ui.pushButtonDoAnalysis.clicked.connect(self.do_analysis)

        self.count_num_of_files_thread = CountFilesThread()
        self.count_num_of_files_thread.num_of_files.connect(
            self.update_number_of_files)
        self.count_file_number.connect(self.count_num_of_files_thread.count)
        self.count_file_number.emit(self.ui.labelFolderToAnalysePath.text()
                                    )  # Using a signal to keep thread safety

        self.ui.progressBar.setFormat(' %v/%m (%p%)')
        self.ui.progressBar.hide()

        self.ui.actionSave_Template.triggered.connect(self.save_template)
        self.ui.actionLoad_Template.triggered.connect(self.load_template)
        self.ui.actionAbout.triggered.connect(self.open_about_window)

        self.analyse_and_output_data_thread = AnalyseAndOutputDataThread()

        self.show()

    @staticmethod
    def open_folder_in_explorer(location):
        if os.path.isfile(location):
            folder_location = os.path.dirname(location)
        else:
            folder_location = location
        QDesktopServices.openUrl(QUrl.fromLocalFile(folder_location))

    def save_template(self):
        dic = {
            'DICOM_tag': [],
            'File_information': [],
            'Custom_plugins': [],
            'Version': __version__
        }

        for index in range(self.ui.listWidget.count()):
            custom_widget = self.ui.listWidget.itemWidget(
                self.ui.listWidget.item(index))
            if custom_widget.comboBoxAttributeChoice.currentText(
            ) == AttributeOptions.DICOM_TAG.value:
                text = custom_widget.lineEdit.text()
                dic['DICOM_tag'].append(text)
            elif custom_widget.comboBoxAttributeChoice.currentText(
            ) == AttributeOptions.FILE_INFORMATION.value:
                text = custom_widget.comboBoxFileOption.currentText()
                dic['File_information'].append(text)
            elif custom_widget.comboBoxAttributeChoice.currentText(
            ) == AttributeOptions.CUSTOM_PLUGIN.value:
                text = custom_widget.comboBoxPluginOption.currentText()
                dic['Custom_plugins'].append(text)
            else:
                raise NotImplementedError

        filepath = QFileDialog.getSaveFileName(self, 'Save template file', '.',
                                               '(*.json)')[0]
        if filepath != '':
            if not filepath.endswith('.json'):
                filepath += '.json'
            with open(filepath, 'w') as f:
                json.dump(dic, f)

    def load_template(self):
        filepath = QFileDialog.getOpenFileName(self, 'Load template file', '.',
                                               '*.json')[0]
        if filepath != '':
            with open(filepath, 'r') as f:
                try:
                    dic = json.load(f)
                except json.JSONDecodeError:
                    msg_box = QMessageBox()
                    msg_box.setWindowTitle("Error")
                    msg_box.setText('Failed to open ' + filepath +
                                    ' (not a valid JSON file)')
                    msg_box.setIcon(QMessageBox.Critical)
                    msg_box.exec()
                    return
                self.ui.listWidget.clear()
                try:
                    if 'DICOM_tag' in dic:
                        for tag in dic['DICOM_tag']:
                            self.add_new_list_widget(default_text=tag)
                    if 'File_information' in dic:
                        for file_option in dic['File_information']:
                            self.add_new_list_widget(
                                attribute_type=AttributeOptions.
                                FILE_INFORMATION,
                                combo_box_text=file_option)
                    if 'Custom_plugins' in dic:
                        for plugin_name in dic['Custom_plugins']:
                            if plugin_name not in [
                                    plugin.name for plugin in
                                    self.plugin_manager.getAllPlugins()
                            ]:
                                msg_box = QMessageBox()
                                msg_box.setWindowTitle("Warning")
                                msg_box.setText(
                                    'A required custom plugin (' +
                                    plugin_name +
                                    ") isn't installed and will be skipped")
                                msg_box.setIcon(QMessageBox.Warning)
                                msg_box.exec()
                            else:
                                self.add_new_list_widget(
                                    attribute_type=AttributeOptions.
                                    CUSTOM_PLUGIN,
                                    combo_box_text=plugin_name)
                except KeyError:
                    msg_box = QMessageBox()
                    msg_box.setWindowTitle("Error")
                    msg_box.setText('Failed to apply template file ' +
                                    filepath +
                                    ' (Missing information in template file)')
                    msg_box.setIcon(QMessageBox.Critical)
                    msg_box.exec()
                    return

    @staticmethod
    def open_about_window():
        msg_box = QMessageBox()
        msg_box.setWindowTitle("About")
        msg_box.setTextFormat(Qt.RichText)
        msg_box.setText(
            "QDICOMMiner version " + __version__ +
            "<br> Written by Keith Offer" +
            "<br> Relies heavily on the <a href='http://www.pydicom.org/'>pydicom</a> library"
        )
        msg_box.setIcon(QMessageBox.Information)
        msg_box.exec()

    def update_number_of_files(self, num):
        self.ui.labelNumberOfFiles.setText(str(num) + ' files')
        self.ui.progressBar.setMaximum(num)

    # The checked variable is emitted from the signal, but we don't use it here
    def add_new_list_widget(self,
                            checked=False,
                            default_text='',
                            attribute_type=AttributeOptions.DICOM_TAG,
                            combo_box_text=None):
        new_list_widget_item = QListWidgetItem()
        custom_widget = CustomListWidget(self, self.plugin_list)
        new_list_widget_item.setSizeHint(custom_widget.sizeHint())
        custom_widget.lineEdit.setCompleter(self.completer)
        custom_widget.lineEdit.textChanged.connect(self.line_edit_text_changed)
        custom_widget.pushButton.clicked.connect(
            lambda: self.remove_widget_from_list(new_list_widget_item))
        if attribute_type == AttributeOptions.DICOM_TAG:
            custom_widget.lineEdit.setText(default_text)
        elif attribute_type == AttributeOptions.FILE_INFORMATION:
            # TODO: Should I throw an exception / error message if the index isn't >= 0?
            file_info_index = custom_widget.comboBoxFileOption.findText(
                combo_box_text)
            if file_info_index >= 0:
                custom_widget.comboBoxFileOption.setCurrentIndex(
                    file_info_index)
                custom_widget.comboBoxFileOption.setVisible(True)
                custom_widget.lineEdit.setVisible(False)
        elif attribute_type == AttributeOptions.CUSTOM_PLUGIN:
            plugin_index = custom_widget.comboBoxPluginOption.findText(
                combo_box_text)
            if plugin_index >= 0:
                custom_widget.comboBoxPluginOption.setCurrentIndex(
                    plugin_index)
                custom_widget.comboBoxPluginOption.setVisible(True)
                custom_widget.lineEdit.setVisible(False)
        else:
            raise NotImplementedError

        attribute_index = custom_widget.comboBoxAttributeChoice.findText(
            attribute_type.value)
        if attribute_index >= 0:
            custom_widget.comboBoxAttributeChoice.setCurrentIndex(
                attribute_index)

        self.ui.listWidget.addItem(new_list_widget_item)
        self.ui.listWidget.setItemWidget(new_list_widget_item, custom_widget)

    def line_edit_text_changed(self, new_string):
        sending_line_edit = self.sender()
        if new_string != '' and (new_string in self.DICOM_dic
                                 or re.match(dicom_tag_regex, new_string)):
            sending_line_edit.setStyleSheet(
                "QLineEdit { background: rgb(0, 255, 0); }")
        else:
            sending_line_edit.setStyleSheet(
                "QLineEdit { background: rgb(255, 0, 0); }")

    def remove_widget_from_list(self, list_widget_item):
        self.ui.listWidget.takeItem(self.ui.listWidget.row(list_widget_item))

    def browse_for_input_folder(self):
        starting_location = self.settings.value('main/lastAnalyseFolder')
        if starting_location is None:
            starting_location = '.'
        filepath = QFileDialog.getExistingDirectory(self, 'Input directory',
                                                    starting_location)
        if filepath != '':
            self.ui.labelFolderToAnalysePath.setText(filepath)
            self.count_file_number.emit(filepath)
            self.settings.setValue('main/lastAnalyseFolder', filepath)

    def browse_for_output_file(self):
        starting_location = self.settings.value('main/lastOutputFile')
        if starting_location is None:
            starting_location = '.'
        # This looks a bit strange, but filenames are the first return value of this function
        # so we need the [0] on the end to grab what we need
        filepath = QFileDialog.getSaveFileName(self, 'Output file',
                                               starting_location, '(*.csv)')[0]
        if filepath != '':
            if not filepath.endswith('.csv'):
                filepath += '.csv'
            self.ui.labelOutputFile.setText(filepath)
            self.settings.setValue('main/lastOutputFile', filepath)

    def do_analysis(self):
        if os.path.exists(self.ui.labelOutputFile.text()):
            msg_box = QMessageBox()
            msg_box.setIcon(QMessageBox.Question)
            msg_box.setText(
                "The output file " + self.ui.labelOutputFile.text() +
                " already exists. Are you sure you want to overwrite it?")
            overwrite_button = QPushButton('Overwrite')
            msg_box.addButton(overwrite_button, QMessageBox.YesRole)
            msg_box.addButton(QPushButton('Cancel'), QMessageBox.RejectRole)
            msg_box.exec()
            if msg_box.clickedButton() != overwrite_button:
                return
        header_DICOM = ''
        header_file_info = ''
        header_custom_plugins = ''
        dicom_tags = []
        file_attributes = []
        selected_plugins = []
        # Generate the CSV header
        for index in range(self.ui.listWidget.count()):
            custom_widget = self.ui.listWidget.itemWidget(
                self.ui.listWidget.item(index))
            if custom_widget.comboBoxAttributeChoice.currentText(
            ) == AttributeOptions.FILE_INFORMATION.value:
                # Handle file attributes (e.g. path, size etc)
                header_file_info += custom_widget.comboBoxFileOption.currentText(
                ) + ','
                file_attributes.append(
                    custom_widget.comboBoxFileOption.currentText())
            elif custom_widget.comboBoxAttributeChoice.currentText(
            ) == AttributeOptions.DICOM_TAG.value:
                # Handle DICOM tags
                text = custom_widget.lineEdit.text()
                try:
                    if text == '':
                        # We have to manually raise this as searching for '' won't throw an exception but won't work
                        raise KeyError
                    if re.match(dicom_tag_regex, text):
                        search_results = re.search(dicom_tag_regex, text)
                        # Note that group 0 is the whole match that we don't want
                        dicom_tags.append(
                            (search_results.group(1), search_results.group(2)))
                    else:
                        dicom_tags.append(
                            self.DICOM_dic[custom_widget.lineEdit.text()])
                except KeyError:
                    msg_box = QMessageBox()
                    msg_box.setWindowTitle("Error")
                    msg_box.setText('"' + text + '" is not a valid attribute')
                    msg_box.setIcon(QMessageBox.Critical)
                    msg_box.exec()
                    return
                header_DICOM += text.replace(',', ' ') + ','
            elif custom_widget.comboBoxAttributeChoice.currentText(
            ) == AttributeOptions.CUSTOM_PLUGIN.value:
                plugin_name = custom_widget.comboBoxPluginOption.currentText()
                if plugin_name != '':
                    header_custom_plugins += self.plugin_manager.getPluginByName(
                        plugin_name).plugin_object.column_headers()
                    selected_plugins.append(plugin_name)
            else:
                raise NotImplementedError

        csv_header = (header_file_info + header_DICOM +
                      header_custom_plugins)[0:-1]  # Remove the last comma

        self.ui.progressBar.show()
        self.analyse_and_output_data_thread.current_file.connect(
            lambda num: self.ui.progressBar.setValue(num))
        self.create_csv.connect(self.analyse_and_output_data_thread.run)
        self.analyse_and_output_data_thread.finished.connect(
            self.csv_making_finished)
        self.create_csv.emit(self.ui.labelOutputFile.text(),
                             self.ui.labelFolderToAnalysePath.text(),
                             csv_header, dicom_tags, file_attributes,
                             selected_plugins)

    def csv_making_finished(self):
        self.ui.progressBar.hide()

    create_csv = pyqtSignal(str, str, str, list, list, list)
    count_file_number = pyqtSignal(str)
Beispiel #8
0
        self.ui.pushButton.setProperty('enabled', False)
        self.ui.progressBar.setProperty('value', 0)
        cursor = self.ui.textBrowser.textCursor()
        cursor.select(QTextCursor.Document)
        cursor.removeSelectedText()
        self.ui.textBrowser.setTextCursor(cursor)

    @pyqtSlot(dict)
    def callback(self, params: dict):
        self.ui.textBrowser.insertPlainText(params['msg'] + '\n')
        self.ui.textBrowser.moveCursor(QTextCursor.End)
        if 'num' in params.keys():
            self.ui.progressBar.setProperty('value', params['num'] if params['num'] >= 0 else 0)
            self.progress_num = params['num']
            if self.progress_num >= 100 or self.progress_num == -1:
                self.ui.pushButton.setProperty('enabled', True)
                self.thread.exit()


if __name__ == '__main__':
    app = QApplication(sys.argv)

    file = QFile("./BreezeStyleSheets/light.qss")
    file.open(QFile.ReadOnly | QFile.Text)
    stream = QTextStream(file)
    app.setStyleSheet(stream.readAll())

    main = MainWindow(Ui_MainWindow())
    main.show()
    sys.exit(app.exec_())
Beispiel #9
0
import sys
from controller.MyMainWindow import MyMainWindow
from ui.mainWindow import Ui_MainWindow
from PyQt5 import QtWidgets

if __name__ == '__main__':
    app = QtWidgets.QApplication(sys.argv)
    mainWindow = MyMainWindow()
    Ui_MainWindow().setupUi(mainWindow)
    mainWindow.show()
    sys.exit(app.exec_())

Beispiel #10
0
class AQMain(QtGui.QMainWindow):
    def __init__(self, parent=None):
        QtGui.QWidget.__init__(self, parent)
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)
        # TODO: figure out way to configure for different comm types (TCP, MAVLINK, etc)
        self.comm = AQSerial()

        # Default main window conditions
        self.ui.buttonDisconnect.setEnabled(False)
        self.ui.buttonConnect.setEnabled(True)
        self.ui.comPort.setEnabled(True)
        self.ui.baudRate.setEnabled(True)
        self.ui.status.setText("Not connected to the AeroQuad")
        self.availablePorts = []
        self.updateComPortSelection()
        self.updateBaudRates()
        self.boardConfiguration = []

        # Update comm port combo box to use last used comm port
        defaultComPort = xml.find("./Settings/DefaultComPort").text
        commIndex = self.ui.comPort.findText(defaultComPort)
        if commIndex == -1:
            commIndex = 0
        self.ui.comPort.setCurrentIndex(commIndex)

        # Load splash screen
        splash = Ui_splashScreen()
        splash.setupUi(splash)
        self.ui.subPanel.addWidget(splash)

        # Dynamically configure board type menu and subPanel menu from XML configuration file
        self.configureSubPanelMenu()
        self.activeSubPanel = None
        self.activeSubPanelName = ""

        # Connect GUI slots and signals
        self.ui.comPort.return_handler = self.connect
        self.ui.buttonConnect.clicked.connect(self.connect)
        self.ui.buttonDisconnect.clicked.connect(self.disconnect)
        self.ui.actionExit.triggered.connect(QtGui.qApp.quit)
        self.ui.comPort.currentIndexChanged.connect(self.updateDetectedPorts)
        self.ui.actionBootUpDelay.triggered.connect(self.updateBootUpDelay)
        self.ui.actionCommTimeout.triggered.connect(self.updateCommTimeOut)

    ####### Communication Methods #######
    def connect(self):
        '''Initiates communication with the AeroQuad'''
        # Setup GUI
        self.ui.status.setText("Connecting...")
        self.ui.buttonDisconnect.setEnabled(True)
        self.ui.buttonConnect.setEnabled(False)
        self.ui.comPort.setEnabled(False)
        self.ui.baudRate.setEnabled(False)
        # Update the GUI
        app.processEvents()

        # Setup serial port
        bootupDelay = float(xml.find("./Settings/BootUpDelay").text)
        commTimeOut = float(xml.find("./Settings/CommTimeOut").text)
        try:
            self.comm.connect(str(self.ui.comPort.currentText()),
                              int(self.ui.baudRate.currentText()), bootupDelay,
                              commTimeOut)
            # Stop and flush any previous telemetry being streamed
            stopTelemetry = xml.find("./Settings/StopTelemetry").text
            self.comm.write(stopTelemetry)
            self.comm.flushResponse()
            # Request version number to identify AeroQuad board
            versionRequest = xml.find("./Settings/SoftwareVersion").text
            self.comm.write(versionRequest)
            version = self.comm.waitForRead()

            if version != "":
                self.storeComPortSelection()
                self.ui.status.setText(
                    "Connected to AeroQuad Flight Software v" + version)
                # Read board configuration
                config = xml.find("./Settings/BoardConfiguration").text
                self.comm.write(config)
                size = int(self.comm.waitForRead())
                for index in range(size):
                    response = self.comm.waitForRead()
                    self.boardConfiguration.append(response)
                # Hide menu items that don't match board configuration
                for index in range(len(self.subPanelMenu)):
                    hide = self.checkRequirementsMatch(
                        self.subPanelList[index])
                    self.subPanelMenu[index].setVisible(hide)
                # Load configuration screen
                self.selectSubPanel("Vehicle Configuration")
                self.restartSubPanel()
            else:
                self.disconnect()
                self.ui.status.setText("Not connected to the AeroQuad")
                QtGui.QMessageBox.information(
                    self, "Connection Error",
                    "Unable to connect to the AeroQuad.  Try increasing the Boot Up Delay.\nThis is found under File->Preferences->Boot Up Delay."
                )
        except SerialException, se:
            self.ui.buttonDisconnect.setEnabled(False)
            self.ui.buttonConnect.setEnabled(True)
            self.ui.comPort.setEnabled(True)
            self.ui.baudRate.setEnabled(True)
            self.ui.status.setText("Connection Failed")
            self.boardConfiguration = []
Beispiel #11
0
    if mode == 'CLI':  #CLI
        logger.info("Mode selected ({})".format(mode))
        from Console import Console
        console = Console()
        console.run()

    elif mode == 'GUI':  #GUI
        logger.info("Mode selected ({})".format(mode))
        from PyQt5.QtWidgets import QApplication, QMainWindow
        from ui.mainWindow import Ui_MainWindow

        app = QApplication(sys.argv)
        logger.info("QApp initialized...")
        MainWindow = QMainWindow()
        logger.info("QMainWindow Initialized")
        ui = Ui_MainWindow()
        ui.setupUi(MainWindow)
        logger.info("ui setup with MainWindow as parent...")
        MainWindow.show()
        logger.info("MainWindow shown...")
        sys.exit(app.exec_())

    elif mode == 'TEST':  #Debug mode in CLI
        output_stream.setLevel(logging.DEBUG)
        logger.info("Mode selected ({})".format(mode))

        fsm = FSM("fsm_setup.txt")
        result = fsm.evaluate("213")
        logger.info("Result: {}".format(result))
        #from Console import Console
        #console = Console()
    def __init__(self, model):
        ''' initialization of class '''
        super().__init__()

        self.value = str()
        # Current Model we use
        self._model = model

        # Application's UI main window
        self._ui = Ui_MainWindow()

        # Setup main window
        self._ui.setupUi(self)

        #setup numpad
        self.qDialog = QDialog()
        self.numpadWindow = Ui_Numpad()
        self.numpadWindow.setupUi(self.qDialog)
        self.numpadWindow.numberToSet.setAlignment(QtCore.Qt.AlignRight)
        self.numpadWindow.numberToSet.setMaxLength(6)
        self.numpadWindow.numberToSet.setReadOnly(True)
        self.qLineValidator = QtGui.QIntValidator

        self.numpadButtons = [
            self.numpadWindow.button0, self.numpadWindow.button1,
            self.numpadWindow.button2, self.numpadWindow.button3,
            self.numpadWindow.button4, self.numpadWindow.button5,
            self.numpadWindow.button6, self.numpadWindow.button7,
            self.numpadWindow.button8, self.numpadWindow.button9
        ]
        for idx, item in enumerate(self.numpadButtons):
            #print(type(item))
            #print(idx)
            item.setText(str(idx))
            item.clicked.connect(lambda: self._buttonpres())

        self.numpadWindow.buttonBackspace.clicked.connect(self._dellast)
        self.numpadWindow.buttonSub.clicked.connect(self._negateValue)

        # Set UI input objects to default state/value
        self._setUiInputsToDefault()

        # Connect UI elements/objects signals and slots
        self._connectSignalSlots()

        # Add menu bar items
        self._addMenubarItems()

        # Setup Plots
        self._bvddPlot = self._ui.graphicsViewBVDD.plot([], [],
                                                        pen=(0, 0, 255))
        self._ui.graphicsViewBVDD.setLabel('left', 'BVDD', units='V')
        self._ui.graphicsViewBVDD.setLabel('bottom', 'Time', units='s')
        self._ui.graphicsViewBVDD.showGrid(x=True, y=True)
        self._ui.graphicsViewBVDD.setYRange(
            DefinedValues.BVDD_Y_MIN_RANGE.value,
            DefinedValues.BVDD_Y_MAX_RANGE.value, 0, True)

        self._temperaturePlot = self._ui.graphicsViewTemperature.plot([], [],
                                                                      pen=(0,
                                                                           255,
                                                                           0))
        self._ui.graphicsViewTemperature.setLabel('left', 'Tj', units='°C')
        self._ui.graphicsViewTemperature.setLabel('bottom', 'Time', units='s')
        self._ui.graphicsViewTemperature.showGrid(x=True, y=True)
        self._ui.graphicsViewTemperature.setYRange(
            DefinedValues.TJ_Y_MIN_RANGE.value,
            DefinedValues.TJ_Y_MAX_RANGE.value, 0, True)

        self._rotorSpeedPlot = self._ui.graphicsViewRotorSpeed.plot([], [],
                                                                    pen=(255,
                                                                         0, 0))
        self._ui.graphicsViewRotorSpeed.setLabel('left',
                                                 'Rotor Speed',
                                                 units='PPS')
        self._ui.graphicsViewRotorSpeed.setLabel('bottom', 'Time', units='s')
        self._ui.graphicsViewRotorSpeed.showGrid(x=True, y=True)
        self._ui.graphicsViewRotorSpeed.setYRange(
            DefinedValues.RS_Y_MIN_RANGE.value,
            DefinedValues.RS_Y_MAX_RANGE.value, 0, True)

        self.__rawCurrentSpeed = 0
        self.__currentSpeed = 0

        self._picIndex = 0  # first image to be replaced

        self._picTimer = QTimer()
        self._picTimer.timeout.connect(self.updatePic)
        self._picTimer.start(DefinedValues.PIC_SWAP_TIME.value)
class Controller(QtWidgets.QMainWindow):
    '''
        Responsible for updating of UI/View.
        Control and updates of model with new data.
    '''
    def __init__(self, model):
        ''' initialization of class '''
        super().__init__()

        self.value = str()
        # Current Model we use
        self._model = model

        # Application's UI main window
        self._ui = Ui_MainWindow()

        # Setup main window
        self._ui.setupUi(self)

        #setup numpad
        self.qDialog = QDialog()
        self.numpadWindow = Ui_Numpad()
        self.numpadWindow.setupUi(self.qDialog)
        self.numpadWindow.numberToSet.setAlignment(QtCore.Qt.AlignRight)
        self.numpadWindow.numberToSet.setMaxLength(6)
        self.numpadWindow.numberToSet.setReadOnly(True)
        self.qLineValidator = QtGui.QIntValidator

        self.numpadButtons = [
            self.numpadWindow.button0, self.numpadWindow.button1,
            self.numpadWindow.button2, self.numpadWindow.button3,
            self.numpadWindow.button4, self.numpadWindow.button5,
            self.numpadWindow.button6, self.numpadWindow.button7,
            self.numpadWindow.button8, self.numpadWindow.button9
        ]
        for idx, item in enumerate(self.numpadButtons):
            #print(type(item))
            #print(idx)
            item.setText(str(idx))
            item.clicked.connect(lambda: self._buttonpres())

        self.numpadWindow.buttonBackspace.clicked.connect(self._dellast)
        self.numpadWindow.buttonSub.clicked.connect(self._negateValue)

        # Set UI input objects to default state/value
        self._setUiInputsToDefault()

        # Connect UI elements/objects signals and slots
        self._connectSignalSlots()

        # Add menu bar items
        self._addMenubarItems()

        # Setup Plots
        self._bvddPlot = self._ui.graphicsViewBVDD.plot([], [],
                                                        pen=(0, 0, 255))
        self._ui.graphicsViewBVDD.setLabel('left', 'BVDD', units='V')
        self._ui.graphicsViewBVDD.setLabel('bottom', 'Time', units='s')
        self._ui.graphicsViewBVDD.showGrid(x=True, y=True)
        self._ui.graphicsViewBVDD.setYRange(
            DefinedValues.BVDD_Y_MIN_RANGE.value,
            DefinedValues.BVDD_Y_MAX_RANGE.value, 0, True)

        self._temperaturePlot = self._ui.graphicsViewTemperature.plot([], [],
                                                                      pen=(0,
                                                                           255,
                                                                           0))
        self._ui.graphicsViewTemperature.setLabel('left', 'Tj', units='°C')
        self._ui.graphicsViewTemperature.setLabel('bottom', 'Time', units='s')
        self._ui.graphicsViewTemperature.showGrid(x=True, y=True)
        self._ui.graphicsViewTemperature.setYRange(
            DefinedValues.TJ_Y_MIN_RANGE.value,
            DefinedValues.TJ_Y_MAX_RANGE.value, 0, True)

        self._rotorSpeedPlot = self._ui.graphicsViewRotorSpeed.plot([], [],
                                                                    pen=(255,
                                                                         0, 0))
        self._ui.graphicsViewRotorSpeed.setLabel('left',
                                                 'Rotor Speed',
                                                 units='PPS')
        self._ui.graphicsViewRotorSpeed.setLabel('bottom', 'Time', units='s')
        self._ui.graphicsViewRotorSpeed.showGrid(x=True, y=True)
        self._ui.graphicsViewRotorSpeed.setYRange(
            DefinedValues.RS_Y_MIN_RANGE.value,
            DefinedValues.RS_Y_MAX_RANGE.value, 0, True)

        self.__rawCurrentSpeed = 0
        self.__currentSpeed = 0

        self._picIndex = 0  # first image to be replaced

        self._picTimer = QTimer()
        self._picTimer.timeout.connect(self.updatePic)
        self._picTimer.start(DefinedValues.PIC_SWAP_TIME.value)

    def updatePic(self):
        ''' cyclical switch of banner pic '''
        self._picIndex += 1  # next image
        if (self._picIndex > 5):  # last image reached ?
            self._picIndex = 0

        if (self._picIndex == 0):
            self._ui.ImageButton.setStyleSheet(
                "border-image: url(:/Image/resources/NT_BannerSW31.png);")
        elif (self._picIndex == 1):
            self._ui.ImageButton.setStyleSheet(
                "border-image: url(:/Image/resources/NT_BannerSW32.png);")
        elif (self._picIndex == 2):
            self._ui.ImageButton.setStyleSheet(
                "border-image: url(:/Image/resources/NT_BannerSW33.png);")
        elif (self._picIndex == 3):
            self._ui.ImageButton.setStyleSheet(
                "border-image: url(:/Image/resources/NT_BannerSW34.png);")
        elif (self._picIndex == 4):
            self._ui.ImageButton.setStyleSheet(
                "border-image: url(:/Image/resources/NT_BannerSW35.png);")
        elif (self._picIndex == 5):
            self._ui.ImageButton.setStyleSheet(
                "border-image: url(:/Image/resources/NT_BannerSW36.png);")
        else:
            self._picIndex = 0  #default never reached

    def updateCurrentPosition(self, position):
        ''' Set current position displayed in 'Current Status' LCD element. '''
        self._ui.currentPositionLCD.display(position)

    def setStatusIndicator(self, statusIndicator):
        ''' Set status indicator elements. '''
        if Status.OVER_CURRENT == statusIndicator:
            self._ui.labelOverCurrentLED.setPixmap(
                QtGui.QPixmap(":/led/resources/red-led-on.png"))

        elif Status.NO_OVER_CURRENT == statusIndicator:
            self._ui.labelOverCurrentLED.setPixmap(
                QtGui.QPixmap(":/led/resources/red-led-off.png"))

        elif Status.OVER_TEMPERATURE == statusIndicator:
            self._ui.labelOverTemperatureLED.setPixmap(
                QtGui.QPixmap(":/led/resources/red-led-on.png"))

        elif Status.NO_OVER_TEMPERATURE == statusIndicator:
            self._ui.labelOverTemperatureLED.setPixmap(
                QtGui.QPixmap(":/led/resources/red-led-off.png"))

        elif Status.ERROR == statusIndicator:
            self._ui.labelErrorLED.setPixmap(
                QtGui.QPixmap(":/led/resources/red-led-on.png"))

        elif Status.NO_ERROR == statusIndicator:
            self._ui.labelErrorLED.setPixmap(
                QtGui.QPixmap(":/led/resources/red-led-off.png"))

        elif Status.TARGET_ONLINE == statusIndicator:
            self._ui.labelTargetOnlineLED.setPixmap(
                QtGui.QPixmap(":/led/resources/green-led-on.png"))

        elif Status.TARGET_OFFLINE == statusIndicator:
            self._ui.labelTargetOnlineLED.setPixmap(
                QtGui.QPixmap(":/led/resources/green-led-off.png"))
        else:
            #Nothing to do here
            pass

    def updateBvddPlot(self, plotData):
        ''' Updates BVDD plot with plotData. '''
        self._bvddPlot.setData(plotData.x, plotData.y)

    def updateTemeperatuePlot(self, plotData):
        ''' Updates Temperature plot with plotData. '''
        self._temperaturePlot.setData(plotData.x, plotData.y)

    def updateRotorSpeedPlot(self, plotData):
        ''' Updates Rotor Speed plot with plotData. '''
        self._rotorSpeedPlot.setData(plotData.x, plotData.y)

    def updateCurrentSpeed(self, speed):
        ''' get current speed value '''
        self.__rawCurrentSpeed = speed

    def getCurrentSpeedRPM(self):
        ''' returns the current speed'''
        self.__currentSpeedRPM = (self.__rawCurrentSpeed *
                                  DefinedValues.RPM_FACTOR.value)

        return self.__currentSpeedRPM

    def buttonLearnMore(self):
        ''' open micronas webaddress '''
        webbrowser.open(DefinedValues.LEARN_MORE_WEB_ADDRESS.value)

    def showErrorDialog(self, errorMsg):
        ''' Shows a modal error message dialog. 
            errorMsg: Message (string) displayed in dialog.
        '''
        msg = QtWidgets.QErrorMessage()
        msg.setWindowTitle("ERROR MESSAGE")
        msg.setWindowModality(QtCore.Qt.ApplicationModal)
        msg.showMessage(errorMsg)
        msg.exec_()

    def _addMenubarItems(self):
        ''' create sub items in menu bar and connect with actions '''
        action = QtWidgets.QAction("Connect", self)
        action.triggered.connect(self._onMenuBarItemSelectComPort)
        self._ui.menuSettings.addAction(action)

        action = QtWidgets.QAction("License", self)
        action.triggered.connect(self._onLicense)
        self._ui.menuClose.addAction(action)

        action = QtWidgets.QAction("Exit", self)
        action.triggered.connect(self._closeApp)
        self._ui.menuClose.addAction(action)

    def _connectSignalSlots(self):
        ''' Connect UI's signals and slots connections. '''

        # Connect push buttons
        self._ui.pushButtonEnableMotorCtrl.clicked.connect(
            self._onPushButtonEnableMotorCtrl)
        self._ui.pushButtonResetInputs.clicked.connect(
            self._onPushButtonResetInputs)

        # Connect Direction radio buttons
        self._ui.radioButtonDirectionStop.clicked.connect(
            self._onRadioButtonDirection)
        self._ui.radioButtonDirectionClockWise.clicked.connect(
            self._onRadioButtonDirection)
        self._ui.radioButtonDirectionAntiClockwise.clicked.connect(
            self._onRadioButtonDirection)

        # Connect Control radio buttons
        self._ui.radioButtonPositionCtrl.clicked.connect(
            self._onRadioButtonControlSelection)
        self._ui.radioButtonSpeedCtrl.clicked.connect(
            self._onRadioButtonControlSelection)

        # Connect speed slider control
        self._ui.speedSlider.valueChanged.connect(self._updateSpeedGroupBox)

        # Connect speed spinBox
        self._ui.speedspinBox.editingFinished.connect(self._updateSpeedSpinBox)

        self._ui.speedChangeButton.clicked.connect(self._ChangeSpeed)

        # Connect position slider control
        self._ui.positionSlider.valueChanged.connect(
            self._updatePositionGroupBox)

        # Connect position spinBox
        self._ui.positionspinBox.editingFinished.connect(
            self._updatePositionSpinBox)

        self._ui.posChangeButton.clicked.connect(self._ChangePos)

        # Connect pushButton_learnMore
        self._ui.ImageButton.clicked.connect(self.buttonLearnMore)

    def _onPushButtonEnableMotorCtrl(self):
        ''' action for Button to Enable/Disable Motor control'''
        if self._ui.pushButtonEnableMotorCtrl.isChecked():
            self._ui.pushButtonEnableMotorCtrl.setText("Disable Motor Control")
            self._model.ctrlFrame.motorEnabled = True
            #we want only a change of speed and position if the motor is enabled
            if self._ui.radioButtonPositionCtrl.isChecked():
                self._ui.groupBoxAcceleration.setEnabled(True)
                self._ui.groupBoxPosition.setEnabled(True)
                self._ui.groupBoxSpeed.setEnabled(True)
                self._model.ctrlFrame.opMode = rpc.OpMode.POSITION_CTRL
            else:
                self._ui.groupBoxAcceleration.setEnabled(True)
                self._ui.groupBoxPosition.setEnabled(False)
                self._ui.groupBoxDirection.setEnabled(True)
                self._ui.groupBoxSpeed.setEnabled(True)
                self._model.ctrlFrame.opMode = rpc.OpMode.SPEED_CTRL

        else:
            self._ui.pushButtonEnableMotorCtrl.setText("Enable Motor Control")
            self._model.ctrlFrame.motorEnabled = False
            #motor disable -> no changes on speed and position
            self._ui.groupBoxPosition.setEnabled(False)
            self._ui.groupBoxSpeed.setEnabled(False)
            self._ui.groupBoxDirection.setEnabled(False)
            self._ui.groupBoxAcceleration.setEnabled(False)
            if self._ui.radioButtonPositionCtrl.isChecked():
                self._model.ctrlFrame.opMode = rpc.OpMode.POSITION_CTRL
                #pass
            elif self._ui.radioButtonSpeedCtrl.isChecked():
                self._model.ctrlFrame.opMode = rpc.OpMode.SPEED_CTRL
                #pass

    def _onPushButtonResetInputs(self):
        ''' Reset inputs to default values '''
        self._setUiInputsToDefault()

    def _onRadioButtonControlSelection(self):
        ''' action to activate speed or position control'''

        if self._ui.pushButtonEnableMotorCtrl.isChecked():

            if self._ui.radioButtonPositionCtrl.isChecked(
            ) and self.getCurrentSpeedRPM() < 1:
                self._ui.groupBoxDirection.setEnabled(False)
                self._ui.groupBoxPosition.setEnabled(True)
                self._ui.groupBoxSpeed.setEnabled(True)
                self._model.ctrlFrame.opMode = rpc.OpMode.POSITION_CTRL

            elif self._ui.radioButtonSpeedCtrl.isChecked(
            ) and self.getCurrentSpeedRPM() < 1:
                self._ui.groupBoxDirection.setEnabled(True)
                self._ui.groupBoxPosition.setEnabled(False)
                self._ui.groupBoxSpeed.setEnabled(True)
                self._model.ctrlFrame.opMode = rpc.OpMode.SPEED_CTRL

            elif self._ui.radioButtonPositionCtrl.isChecked(
            ) and self.getCurrentSpeedRPM() > 1:
                self._ui.radioButtonPositionCtrl.setChecked(False)
                self._ui.radioButtonSpeedCtrl.setChecked(True)

            elif self._ui.radioButtonSpeedCtrl.isChecked(
            ) and self.getCurrentSpeedRPM() > 1:
                self._ui.radioButtonSpeedCtrl.setChecked(False)
                self._ui.radioButtonPositionCtrl.setChecked(True)

    def _onRadioButtonDirection(self):
        ''' Update model with selected direction. '''
        if self._ui.radioButtonDirectionStop.isChecked():
            self._model.ctrlFrame.direction = rpc.Direction.STOP

        elif self._ui.radioButtonDirectionClockWise.isChecked():
            self._model.ctrlFrame.direction = rpc.Direction.CLOCKWISE

        elif self._ui.radioButtonDirectionAntiClockwise.isChecked():
            self._model.ctrlFrame.direction = rpc.Direction.ANTI_CLOCKWISE

        else:
            #Nothing to do here
            pass

    def _updateSpeedGroupBox(self):
        ''' Updates Speed Group Box elements. '''
        speed = self._ui.speedSlider.value()
        #self._ui.speedLCD.display(speed)
        self._model.ctrlFrame.speed = int(speed /
                                          DefinedValues.RPM_FACTOR.value)
        # Sync between slider and Spinbox
        self._syncSpeedSpinBoxAndSpeedSlider(speed)

    def _updateSpeedSpinBox(self):
        ''' update speed in speed display'''
        speed = self._ui.speedspinBox.value()
        #self._ui.speedLCD.display(speed)
        self._model.ctrlFrame.speed = int(speed /
                                          DefinedValues.RPM_FACTOR.value)
        # Sync between slider and Spinbox
        self._syncSpeedSpinBoxAndSpeedSlider(speed)

    def _syncSpeedSpinBoxAndSpeedSlider(self, speed):
        ''' Update and Sync of the GUI-Values '''
        self._ui.speedSlider.setValue(speed)
        self._ui.speedspinBox.setValue(speed)

    def _updatePositionGroupBox(self):
        ''' Updates Position Group Box elements. '''
        position = self._ui.positionSlider.value()
        #self._ui.positionLCD.display(position)
        self._model.ctrlFrame.newPosition = position
        # Sync between slider and Spinbox
        self._syncPositionSpinBoxAndPositionSlider(position)

    def _updatePositionSpinBox(self):
        '''update position in position display'''
        position = self._ui.positionspinBox.value()
        #self._ui.positionLCD.display(position)
        self._model.ctrlFrame.newPosition = position
        # Sync between slider and Spinbox
        self._syncPositionSpinBoxAndPositionSlider(position)

    def _syncPositionSpinBoxAndPositionSlider(self, position):
        ''' Update and Sync of the GUI-Values '''
        self._ui.positionSlider.setValue(position)
        self._ui.positionspinBox.setValue(position)

    def _setUiInputsToDefault(self):
        ''' Set UI elements to default values. '''
        self._ui.pushButtonEnableMotorCtrl.setChecked(
            model.DefaultValues.MOTOR_ENABLED)
        self._onPushButtonEnableMotorCtrl()

        self._ui.speedSlider.setValue(model.DefaultValues.SPEED)
        self._ui.positionSlider.setValue(model.DefaultValues.INIT_CURRENT_POS)

        #self._ui.radioButtonAcceleration1.setChecked(model.DefaultValues.ACCELERATION)
        self._ui.radioButtonDirectionStop.setChecked(
            model.DefaultValues.DIRECTION)
        self._onRadioButtonControlSelection()

    def _onLicense(self):
        ''' open message box and show license information
        '''
        # create message box
        msg = QtWidgets.QMessageBox()
        # icon information (I)
        msg.setIcon(QtWidgets.QMessageBox.Information)
        # text format RichText to interprete links in html style
        msg.setTextFormat(QtCore.Qt.RichText)
        # Text string with license text
        msg.setText(LicenseText)
        # title of window
        msg.setWindowTitle("License Information")
        # show message box
        msg.exec_()

    def _onMenuBarItemSelectComPort(self):
        ''' Selection of COM port.
            Starts model on valid port selection.
        '''

        #Stop model
        self._model.stop()

        #We are no more connected
        self.setStatusIndicator(Status.TARGET_OFFLINE)

        #Create dialog for COM port selection
        qDialog = QDialog()
        settingWindow = Ui_AppSettings()
        settingWindow.setupUi(qDialog)

        #Get COM ports with connected devices
        ports = sorted([port.device for port in list_ports.comports()])

        #Populate comboBox with found COM ports
        settingWindow.comboBoxComPorts.clear()
        settingWindow.comboBoxComPorts.addItems(ports)

        #Show dialog
        qDialog.exec_()

        #Evaluate user selection
        if QDialog.Accepted == qDialog.result():
            # User has accepted by pressing OK button

            #Get selected COM-Port
            selectedIdx = settingWindow.comboBoxComPorts.currentIndex()
            comPort = ports[selectedIdx]

            self._model.start(comPort)
        else:
            # User has rejected by pressing CANCEL button
            pass

    def _buttonpres(self):
        ''' action asoziated with Button 1 on the numpad
        '''
        #print(type(self.sender()))
        buttonpressed = self.sender()
        #print(type(buttonpressed))
        #print(self.sender().text())
        self.value = self.numpadWindow.numberToSet.text()
        self.value = self.value + buttonpressed.text()
        self.numpadWindow.numberToSet.setText(self.value)

    def _dellast(self):
        ''' usebackspace from qLineEdit to remove last number
        '''
        self.numpadWindow.numberToSet.backspace()

    def _negateValue(self):
        '''negate value for numpad window '''
        currentstring = self.numpadWindow.numberToSet.text()
        if currentstring:
            number = int(currentstring)
        else:
            number = 0

        number = number * (-1)
        self.numpadWindow.numberToSet.setText(str(number))

    def _ChangeSpeed(self):
        ''' Change speed via Numpad.
        '''
        self.numpadWindow.numberToSet.setValidator(self.qLineValidator(
            0, 4992))  # needs to be the same range as set in UI of qt designer

        #Show dialog
        self.qDialog.exec_()

        #Evaluate user selection
        if QDialog.Accepted == self.qDialog.result():
            # User has accepted by pressing OK button

            #Get selected COM-Port
            if (self.numpadWindow.numberToSet.text()):
                speed = int(self.numpadWindow.numberToSet.text())
                self._syncSpeedSpinBoxAndSpeedSlider(speed)
                self.value = str()
                self.numpadWindow.numberToSet.setText(self.value)
            else:
                self._syncSpeedSpinBoxAndSpeedSlider(0)
                self.value = str()
                self.numpadWindow.numberToSet.setText(self.value)

        else:
            # User has rejected by pressing CANCEL button
            self.value = str()
            self.numpadWindow.numberToSet.setText(self.value)
            pass

    def _ChangePos(self):
        ''' Change Position via Numpad
        '''
        self.numpadWindow.numberToSet.setValidator(
            self.qLineValidator(-16000, 16000)
        )  # needs to be the same range as set in UI of qt designer

        #Show dialog
        self.qDialog.exec_()

        #Evaluate user selection
        if QDialog.Accepted == self.qDialog.result():
            # User has accepted by pressing OK button

            #Get selected COM-Port
            if (self.numpadWindow.numberToSet.text()):
                pos = int(self.numpadWindow.numberToSet.text()
                          )  #need to protect against no return value
                self._syncPositionSpinBoxAndPositionSlider(pos)
                self.value = str()
                self.numpadWindow.numberToSet.setText(self.value)
            else:
                self._syncPositionSpinBoxAndPositionSlider(0)
                self.value = str()
                self.numpadWindow.numberToSet.setText(self.value)

        else:
            # User has rejected by pressing CANCEL button
            self.value = str()
            self.numpadWindow.numberToSet.setText(self.value)
            pass

    def _closeApp(self):
        ''' Close and exit application. '''
        sys.exit()
class AQMain(QtGui.QMainWindow):
    def __init__(self, parent=None):
        QtGui.QWidget.__init__(self, parent)
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)
        # TODO: figure out way to configure for different comm types (TCP, MAVLINK, etc) 
        self.comm = AQSerial()
        
        # Default main window conditions
        self.ui.buttonDisconnect.setEnabled(False)
        self.ui.buttonConnect.setEnabled(True)
        self.ui.comPort.setEnabled(True)
        self.ui.baudRate.setEnabled(True)
        self.ui.status.setText("Not connected to the AeroQuad")
        self.availablePorts = []
        self.updateComPortSelection()
        self.updateBaudRates()
        self.boardConfiguration = []
        
        # Update comm port combo box to use last used comm port
        defaultComPort = xml.find("./Settings/DefaultComPort").text
        commIndex = self.ui.comPort.findText(defaultComPort)
        if commIndex == -1:
            commIndex = 0
        self.ui.comPort.setCurrentIndex(commIndex)
        
        # Load splash screen
        splash = Ui_splashScreen()
        splash.setupUi(splash)
        self.ui.subPanel.addWidget(splash)
        
        # Dynamically configure board type menu and subPanel menu from XML configuration file
        self.configureSubPanelMenu()
        self.activeSubPanel = None
        self.activeSubPanelName = ""

        # Connect GUI slots and signals
        self.ui.comPort.return_handler = self.connect
        self.ui.buttonConnect.clicked.connect(self.connect)
        self.ui.buttonDisconnect.clicked.connect(self.disconnect)
        self.ui.actionExit.triggered.connect(QtGui.qApp.quit)
        self.ui.comPort.currentIndexChanged.connect(self.updateDetectedPorts)
        self.ui.actionBootUpDelay.triggered.connect(self.updateBootUpDelay)
        self.ui.actionCommTimeout.triggered.connect(self.updateCommTimeOut)

    ####### Communication Methods #######       
    def connect(self):
        '''Initiates communication with the AeroQuad'''
        # Setup GUI
        self.ui.status.setText("Connecting...")
        self.ui.buttonDisconnect.setEnabled(True)
        self.ui.buttonConnect.setEnabled(False)
        self.ui.comPort.setEnabled(False)
        self.ui.baudRate.setEnabled(False)
        # Update the GUI
        app.processEvents()
        
        # Setup serial port
        bootupDelay = float(xml.find("./Settings/BootUpDelay").text)
        commTimeOut = float(xml.find("./Settings/CommTimeOut").text)
        try:
            self.comm.connect(str(self.ui.comPort.currentText()), int(self.ui.baudRate.currentText()), bootupDelay, commTimeOut)
            # Stop and flush any previous telemetry being streamed
            stopTelemetry = xml.find("./Settings/StopTelemetry").text
            self.comm.write(stopTelemetry)
            self.comm.flushResponse()
            # Request version number to identify AeroQuad board
            versionRequest = xml.find("./Settings/SoftwareVersion").text
            self.comm.write(versionRequest)
            version = self.comm.waitForRead()

            if version != "":
                self.storeComPortSelection()
                self.ui.status.setText("Connected to AeroQuad Flight Software v" + version)
                # Read board configuration
                config = xml.find("./Settings/BoardConfiguration").text
                self.comm.write(config)
                size = int(self.comm.waitForRead())
                for index in range(size):
                    response = self.comm.waitForRead()
                    self.boardConfiguration.append(response)
                # Hide menu items that don't match board configuration
                for index in range(len(self.subPanelMenu)):
                    hide = self.checkRequirementsMatch(self.subPanelList[index])
                    self.subPanelMenu[index].setVisible(hide)
                # Load configuration screen
                self.selectSubPanel("Vehicle Configuration")
                self.restartSubPanel()
            else:
                self.disconnect()
                self.ui.status.setText("Not connected to the AeroQuad")
                QtGui.QMessageBox.information(self, "Connection Error", "Unable to connect to the AeroQuad.  Try increasing the Boot Up Delay.\nThis is found under File->Preferences->Boot Up Delay.")
        except SerialException, se:
            self.ui.buttonDisconnect.setEnabled(False)
            self.ui.buttonConnect.setEnabled(True)
            self.ui.comPort.setEnabled(True)
            self.ui.baudRate.setEnabled(True)
            self.ui.status.setText("Connection Failed")
            self.boardConfiguration = []