Ejemplo n.º 1
0
    def __init__(self, api, name="Demo", icon="cwicon"):
        QMainWindow.__init__(self)
        CWMainGUI.instance = self
        self.name = name
        sys.excepthook = self.exceptionHandlerDialog
        self.cwPrefDialog = CWPreferencesDialog(self, api.settings)
        util.setUIupdateFunction(QCoreApplication.processEvents)
        self.api = api
        self.setCentralWidget(None)
        ParameterTypesCustom.helpwnd = HelpBrowser(self).showHelp
        self.setDockNestingEnabled(True)
        self.traceManagerDialog = TraceManagerDialog(self)
        self.projEditWidget = ProjectTextEditor(self)
        self.initUI(icon)
        ResultsBase.sigRegisteredObjectsChanged.connect(self.newResultWidget)
        self.resultDocks = []

        self.loadExtraModules()
        self.addToolMenuItems()
        self.toolMenu.addSeparator()
        self.addSettingsDocks()
        self.restoreSettings()

        self.projectChanged()
        self.api.sigNewProject.connect(self.projectChanged)
        self.api.sigTracesChanged.connect(self.tracesChanged)
        Parameter.sigParametersChanged.connect(self.reloadGuiActions)
        self.show()
Ejemplo n.º 2
0
    def __init__(self, api, name="Demo", icon="cwicon"):
        QMainWindow.__init__(self)
        CWMainGUI.instance = self
        self.name = name
        sys.excepthook = self.exceptionHandlerDialog
        util.setUIupdateFunction(QCoreApplication.processEvents)
        self.api = api
        self.setCentralWidget(None)
        ParameterTypesCustom.helpwnd = HelpBrowser(self).showHelp
        self.setDockNestingEnabled(True)
        self.traceManagerDialog = TraceManagerDialog(self)
        self.projEditWidget = ProjectTextEditor(self)
        self.initUI(icon)
        ResultsBase.sigRegisteredObjectsChanged.connect(self.newResultWidget)
        self.resultDocks = []

        self.loadExtraModules()
        self.addToolMenuItems()
        self.toolMenu.addSeparator()
        self.addSettingsDocks()
        self.restoreSettings()

        self.projectChanged()
        self.api.sigNewProject.connect(self.projectChanged)
        self.api.sigTracesChanged.connect(self.tracesChanged)
        Parameter.sigParametersChanged.connect(self.reloadGuiActions)
Ejemplo n.º 3
0
class CWMainGUI(QMainWindow):
    """
    This is the base GUI class, used for both the Analyzer and Capture software. It defines a number of
    useful features such as the ability to add docks, setting windows, consoles for logging errors, etc.
    """
    maxRecentFiles = 4

    def __init__(self, api, name="Demo", icon="cwicon"):
        QMainWindow.__init__(self)
        CWMainGUI.instance = self
        self.name = name
        sys.excepthook = self.exceptionHandlerDialog
        self.cwPrefDialog = CWPreferencesDialog(self, api.settings)
        util.setUIupdateFunction(QCoreApplication.processEvents)
        self.api = api
        self.setCentralWidget(None)
        ParameterTypesCustom.helpwnd = HelpBrowser(self).showHelp
        self.setDockNestingEnabled(True)
        self.traceManagerDialog = TraceManagerDialog(self)
        self.projEditWidget = ProjectTextEditor(self)
        self.initUI(icon)
        ResultsBase.sigRegisteredObjectsChanged.connect(self.newResultWidget)
        self.resultDocks = []

        self.loadExtraModules()
        self.addToolMenuItems()
        self.toolMenu.addSeparator()
        self.addSettingsDocks()
        self.restoreSettings()

        self.projectChanged()
        self.api.sigNewProject.connect(self.projectChanged)
        self.api.sigTracesChanged.connect(self.tracesChanged)
        Parameter.sigParametersChanged.connect(self.reloadGuiActions)
        self.show()

    def newResultWidget(self, resultWidget):
        # Remove all old actions that don't apply for new selection
        if resultWidget.getWidget():
            self.resultDocks.append(
                self.addDock(resultWidget.getWidget(),
                             name=resultWidget.getName(),
                             area=Qt.TopDockWidgetArea))

    def loadExtraModules(self):
        pass

    def tracesChanged(self):
        pass

    def projectChanged(self):
        self.traceManagerDialog.setTraceManager(
            self.api.project().traceManager())
        self.projEditWidget.setProject(self.api.project())
        self.api.project().sigStatusChanged.connect(self.projectStatusChanged)
        self.projectStatusChanged()

    def addDock(self,
                dockWidget,
                name="Settings",
                area=Qt.TopDockWidgetArea,
                allowedAreas=Qt.AllDockWidgetAreas,
                visible=True,
                addToWindows=True):
        """Add a dockwidget to the main window, which also adds it to the 'Windows' menu"""
        #Configure dock
        dock = QDockWidget(name)
        dock.setWidget(dockWidget)
        dock.setAllowedAreas(allowedAreas)
        dock.setObjectName(name)
        self.addDockWidget(area, dock)

        if not visible:
            dock.toggleViewAction()

        #Add to "Windows" menu
        if addToWindows:
            self.windowMenu.addAction(dock.toggleViewAction())

        return dock

    def addSettings(self, param):
        """Adds a dockwidget designed to store a ParameterTree, also adds to 'Windows' menu"""
        parameterTree = ParameterTree()
        parameterTree.addParameters(param._PyQtGraphParameter)
        dock = self.addDock(parameterTree,
                            name=param.getName(),
                            area=Qt.TopDockWidgetArea)
        dock.setMaximumWidth(560)
        return dock

    def tabifyDocks(self, docks):
        if len(docks) > 0:
            for index in range(1, len(docks)):
                docks[index].show()
                self.tabifyDockWidget(docks[index - 1], docks[index])
            docks[0].raise_()

    def addScriptingDock(self, name="Script Output", visible=True):
        """Add a QTextBrowser, used as a console/debug window"""
        console = qt_tweaks.QTextBrowser()
        return self.addDock(console,
                            name,
                            area=Qt.BottomDockWidgetArea,
                            visible=visible)

    def addLoggingDock(self,
                       name="Debug Logging",
                       visible=True,
                       redirectStdOut=True):
        loggingWidget = LoggingWidget(self)
        loggingWidget.editor.callback = self.updateStatusBar
        if redirectStdOut:
            sys.stdout = loggingWidget.editor
            sys.stderr = loggingWidget.editor

        return self.addDock(loggingWidget,
                            name,
                            area=Qt.BottomDockWidgetArea,
                            visible=visible)

    def addPythonConsole(self, name="Python Console", visible=False):
        """Add a python console, inside which you can access the Python interpreter"""
        # tmp = locals()
        # tmp.update({'self':self})
        # wid = pyqtgraph.console.ConsoleWidget(namespace=tmp, text="")
        wid = chipwhisperer.common.ui.PythonConsole.QPythonConsole(
            self, locals())
        return self.addDock(wid,
                            name,
                            area=Qt.BottomDockWidgetArea,
                            visible=visible)

    def reloadGuiActions(self):
        # Remove all old actions that don't apply for new selection
        if hasattr(self, "_ToolMenuItems"):
            for act in self._ToolMenuItems:
                self.toolMenu.removeAction(act)

        self._ToolMenuItems = []
        self._ToolMenuItems.append(self.toolMenu.addSeparator())
        for act in Parameter.getAllParameters("menu"):
            self._ToolMenuItems.append(
                QAction(act.getName(),
                        self,
                        statusTip=act.getTip(),
                        triggered=act.callAction))

        for act in self._ToolMenuItems:
            self.toolMenu.addAction(act)

    def reset(self):
        """Clear all saved QSettings(), such as window location etc and exit"""
        QSettings().clear()
        self.dontSaveGeometry = True
        self.close()

    def restoreSettings(self):
        self.restoreGeometry(QSettings().value("geometry"))
        self.restoreState(QSettings().value("windowState"))

    def saveSettings(self):
        QSettings().setValue("geometry", self.saveGeometry())
        QSettings().setValue("windowState", self.saveState())

    def closeEvent(self, event):
        """Called when window is closed, attempts to save state/geometry"""
        if not (hasattr(self, "dontSaveGeometry") and self.dontSaveGeometry):
            self.saveSettings()

        if self.okToContinue():
            sys.excepthook = sys.__excepthook__  # Restore exception handlers
            sys.stdout = sys.__stdout__  # Restore print statements
            sys.stderr = sys.__stderr__
            event.accept()
        else:
            event.ignore()

    def helpdialog(self):
        """Helps the User"""
        QMessageBox.about(
            self, 'Link to Documentation',
            'See <a href="http://www.newae.com/sidechannel/cwdocs">newae.com/sidechannel/cwdocs</a> for Tutorials and '
            'Documentation. If you are using an official release, this documentation should have also been present with'
            ' your release.<br><br>'
            'See <a href="http://www.chipwhisperer.com">chipwhisperer.com</a> for Wiki, GIT Code, community information.'
            '<br><br>See the About dialog for Copyright, Trademark, and Authorship information'
        )

    def aboutdialog(self):
        """Tells the User"""
        QMessageBox.about(
            self, 'About', '<h3>ChipWhisperer' + u'\u2122' + '</h3>'
            '<h4>Copyright Information</h4>'
            'Copyright ' + u'\u00A9' +
            ' NewAE Technology Inc., 2013-2016. <br>'
            'Copyright ' + u'\u00A9' + ' Colin O\'Flynn, 2012-2016.'
            '<h4>License Information</h4>'
            'Released under the GPLv3 License, see <a href="http://www.gnu.org/copyleft/gpl.html">License Details</a>.<br>'
            'Various parts of this project may be released under additional open-source licenses such as the BSD License '
            'or LGPL license. See source code for details of specific licenses.'
            '<h4>Projects used in ChipWhisperer</h4>'
            'ChipWhisperer is built on or otherwise uses a number of open-source projects. Many thanks are given to the following '
            'projects, which may also have additional license restrictions or information: '
            '<ul>'
            '<li><a href="https://www.python.org/">Python</a></li>'
            '<li><a href="http://qt-project.org/">Qt</a></li>'
            '<li><a href="http://qt-project.org/wiki/pyside">PySide</a></li>'
            '<li><a href="http://www.pyqtgraph.org/">PyQtGraph</a></li>'
            '<li><a href="http://www.numpy.org/">NumPy</a></li>'
            '<li><a href="http://www.scipy.org/">SciPy</a></li>'
            '<li><a href="http://sourceforge.net/apps/trac/pyusb/">PyUSB</a></li>'
            '<li><a href="http://www.fourwalledcubicle.com/LUFA.php">LUFA USB Library</a></li>'
            '<li><a href="http://www.ztex.de/downloads/#firmware_kit">ZTEX EZ-USB SDK</a></li>'
            '<li><a href="http://www.libusb.org/wiki/libusb-1.0">libusb-1.0</a></li>'
            '<li><a href="http://winavr.sourceforge.net/">WinAVR</a></li>'
            '</ul>'
            'Some projects may be missing from the above list - please let us know, any omission is a mistake!'
            '<h4>Trademark Information</h4>'
            'ChipWhisperer is a Trademark of NewAE Technology Inc.')

    def pluginDialog(self):
        self.dialog = pluginmanager.PluginStatusDialog(self)
        self.dialog.show()

    def createMenus(self):
        """Create all menus (File, Window, etc)"""

        self.fileMenu = self.menuBar().addMenu("&File")
        self.newAct = QAction(QIcon('new.png'),
                              '&New',
                              self,
                              shortcut=QKeySequence.New,
                              statusTip='Create new Project',
                              triggered=self.newProject)
        self.fileMenu.addAction(self.newAct)
        self.openAct = QAction(QIcon('open.png'),
                               '&Open...',
                               self,
                               shortcut=QKeySequence.Open,
                               statusTip='Open Project File',
                               triggered=self.openProject)
        self.fileMenu.addAction(self.openAct)
        self.openRec = QMenu('Open &Recent')
        self.fileMenu.addMenu(self.openRec)
        self.recentFilesAction = []
        for i in range(CWMainGUI.maxRecentFiles):
            self.recentFilesAction.append(
                QAction(self, visible=False, triggered=self.openRecentFile))
            self.openRec.addAction(self.recentFilesAction[i])
        self.saveAct = QAction(QIcon('save.png'),
                               '&Save...',
                               self,
                               shortcut=QKeySequence.Save,
                               statusTip='Save current project to Disk',
                               triggered=self.saveProject)
        self.fileMenu.addAction(self.saveAct)
        self.saveAsAct = QAction(
            QIcon('save.png'),
            '&Save as...',
            self,
            statusTip='Save current project to Disk with a different name',
            triggered=self.saveProjectAs)
        self.fileMenu.addAction(self.saveAsAct)

        self.prefAct = QAction("&Preferences",
                               self,
                               statusTip="Set preferences",
                               triggered=self.cwPrefDialog.show)
        self.fileMenu.addSeparator()
        self.fileMenu.addAction(self.prefAct)

        self.exitAct = QAction("E&xit",
                               self,
                               shortcut="Ctrl+Q",
                               statusTip="Exit the application",
                               triggered=self.close)
        self.fileMenu.addSeparator()
        self.fileMenu.addAction(self.exitAct)

        self.projectMenu = self.menuBar().addMenu("&Project")
        self.saveSettingsAct = QAction(
            '&Save Settings',
            self,
            statusTip='Save parameter settings to a file inside the '
            'project folder so you can load it latter using the load button in the group settings.',
            triggered=self.api.project().saveAllSettings)
        self.projectMenu.addAction(self.saveSettingsAct)
        self.traceManageAct = QAction(
            '&Trace Management',
            self,
            statusTip='Add/Remove traces from project.',
            triggered=self.traceManagerDialog.show)
        self.projectMenu.addAction(self.traceManageAct)
        self.consolidateAct = QAction(
            '&Consolidate',
            self,
            statusTip='Copy trace files to project directory.',
            triggered=self.consolidateDialog)
        self.projectMenu.addAction(self.consolidateAct)
        self.cleanAct = QAction(
            'Clean',
            self,
            statusTip='Delete intermediate files from project directory.',
            triggered=self.cleanDialog)
        self.projectMenu.addAction(self.cleanAct)
        self.showProjFileAct = QAction('&Project File Editor (Text)',
                                       self,
                                       statusTip='Edit project file.',
                                       triggered=self.projEditDock.show)
        self.projectMenu.addAction(self.showProjFileAct)

        self.toolMenu = self.menuBar().addMenu("&Tools")

        self.windowMenu = self.menuBar().addMenu("&Windows")

        self.helpMenu = self.menuBar().addMenu("&Help")
        self.helpMenu.addAction(
            QAction('&Tutorial/User Manual',
                    self,
                    statusTip='Everything you need to know',
                    triggered=self.helpdialog))
        self.helpMenu.addAction(
            QAction('&List Enabled/Disable Plugins',
                    self,
                    statusTip='Check if you\'re missing plugins',
                    triggered=self.pluginDialog))
        # self.helpMenu.addAction(QAction('&ChipWhisperer Documentation', self, statusTip='ChipWisperer Wiki Page', triggered=lambda:QDesktopServices.openUrl(QUrl("http://wiki.newae.com/Main_Page"))))
        self.helpMenu.addAction(
            QAction('&Check for Updates',
                    self,
                    statusTip='Check for new versions',
                    triggered=self.checkForUpdates))
        self.helpMenu.addAction(self.prefAct)
        self.helpMenu.addAction(
            QAction('&About',
                    self,
                    statusTip='About dialog',
                    triggered=self.aboutdialog))

    def checkForUpdates(self):
        try:
            source = urllib.urlopen(
                "https://www.assembla.com/spaces/chipwhisperer/git/source/master/software/chipwhisperer/common/api/CWCoreAPI.py?_format=raw"
            )
            content = source.read()

            version = None
            for line in content.split("\n"):
                if "__version__" in line:
                    version = line.split('=')[1]
                    break
            if version is not None:
                version = version.lstrip(' "').rstrip(' "')
                if self.api.__version__ == version:
                    message = "Your current version is already the most recent one."
                else:
                    message = "There is an updated version available:\n"
                    source = urllib.urlopen(
                        "https://www.assembla.com/spaces/chipwhisperer/git/source/master/CHANGES.txt?_format=raw"
                    )
                    content = source.read()
                    for line in content.split("\n"):
                        if self.api.__version__ + ":" in line:
                            break
                        message += line + "\n"
            else:
                message = "Could not check for the most recent version. Try visiting the project webpage."
        except IOError:
            message = "Could not retrieve the information. Check your internet connection."
        QMessageBox.information(self, "Checking for updates...", message)

    def addToolMenuItems(self):
        pass

    def initUI(self, icon="cwicon"):
        """Setup the UI, creating statusbar, setting title, menus, etc"""
        self.statusBar()
        self.setWindowIcon(QIcon(":/images/%s.png" % icon))
        self.projEditDock = self.addDock(self.projEditWidget,
                                         name="Project Text Editor",
                                         area=Qt.BottomDockWidgetArea,
                                         visible=False,
                                         addToWindows=False)
        self.createMenus()
        self.updateRecentFileActions()
        self.setupToolBar()

        # Project editor dock
        self.paramScriptingDock = self.addScriptingDock("Script Commands",
                                                        visible=False)
        Parameter.scriptingOutput = self.paramScriptingDock.widget(
        )  # set as the default paramenter scripting log output
        self.loggingDock = self.addLoggingDock()
        self.pythonConsoleDock = self.addPythonConsole()
        self.tabifyDocks([
            self.projEditDock, self.paramScriptingDock, self.pythonConsoleDock,
            self.loggingDock
        ])
        self.setBaseSize(800, 600)

    def setupToolBar(self):
        self.toolbar = self.addToolBar('Tools')
        self.toolbar.setObjectName('Tools')
        self.toolbar.addAction(
            QAction(QIcon(":/images/open.png"),
                    'Open',
                    self,
                    triggered=self.openProject))
        self.toolbar.addAction(
            QAction(QIcon(":/images/save.png"),
                    'Save',
                    self,
                    triggered=self.saveProject))
        self.toolbar.addSeparator()
        self.toolbar.addAction(
            QAction(QIcon(":/images/tracemanager.png"),
                    'Trace Manager',
                    self,
                    triggered=self.traceManagerDialog.show))
        # self.toolbar.addSeparator()
        # self.toolbar.addAction(QAction(QIcon(":/images/tracemanager.png"), 'CW Analyzer', self, triggered=self._openAnalyzer))

        self.addToolbarItems(self.toolbar)
        self.toolbar.show()

    def _openAnalyzer(self):
        """Just for test purposes"""
        from chipwhisperer.analyzer.ui.CWAnalyzerGUI import CWAnalyzerGUI
        CWAnalyzerGUI(self.api).show()

    def addToolbarItems(self, toolbar):
        pass

    def addExampleScripts(self, scripts):
        self.exampleScriptAct = QAction('&Example Scripts',
                                        self,
                                        statusTip='Predefined Scripts')
        self.projectMenu.addSeparator()
        self.projectMenu.addAction(self.exampleScriptAct)
        subMenu = QMenu("Submenu", self)

        for name, script in scripts.iteritems():
            subMenu.addAction(
                QAction(name,
                        self,
                        statusTip=script.getDescription(),
                        triggered=util.Command(self.runScript, script)))

        self.exampleScriptAct.setMenu(subMenu)

    def updateTitleBar(self):
        """Update filename shown in title bar"""
        fname = os.path.basename(self.api.project().getFilename())
        self.setWindowTitle("%s - %s[*]" % (self.name, fname))
        self.setWindowModified(True)

    def projectStatusChanged(self):
        """Add File to recent file list"""
        self.updateTitleBar()

        if self.api.project().isUntitled(): return

        files = QSettings().value('recentFileList')
        if files is None or not isinstance(files, list):
            files = []

        try:
            files.remove(self.api.project().getFilename())
        except Exception:
            pass

        files.insert(0, self.api.project().getFilename())
        numRecentFiles = min(len(files), CWMainGUI.maxRecentFiles)
        files = files[:numRecentFiles]

        QSettings().setValue('recentFileList', files)
        self.updateRecentFileActions()

    def updateRecentFileActions(self):
        """Update & Load the list of recent files"""
        files = QSettings().value('recentFileList')
        if files is not None and isinstance(files, list):
            files_no = 0
            for f in files:
                text = "&%d %s" % (files_no + 1, util.strippedName(f))
                self.recentFilesAction[files_no].setText(text)
                self.recentFilesAction[files_no].setData(f)
                self.recentFilesAction[files_no].setVisible(True)
                files_no += 1

            for j in range(files_no, CWMainGUI.maxRecentFiles):
                self.recentFilesAction[j].setVisible(False)

    def openRecentFile(self):
        action = self.sender()
        if action:
            self.openProject(action.data())

    def openProject(self, fname=None):
        if not self.okToContinue():
            return
        if fname is None:
            fname, _ = QFileDialog.getOpenFileName(
                self, 'Open File', self.api.settings.value("project-home-dir"),
                'ChipWhisperer Project (*.cwp)', '',
                QFileDialog.DontUseNativeDialog)
            if not fname: return

        logging.info("Opening Project: " + fname)
        self.api.openProject(fname)

    def saveProject(self):
        fname = self.api.project().getFilename()
        if self.api.project().isUntitled():
            fd = QFileDialog(self, 'Save New File',
                             self.api.settings.value("project-home-dir"),
                             'ChipWhisperer Project (*.cwp)')
            fd.setOption(QFileDialog.DontUseNativeDialog)
            fd.setDefaultSuffix(
                'cwp'
            )  # Will not append the file extension if using the static file dialog
            fd.setAcceptMode(QFileDialog.AcceptSave)
            fd.setViewMode(QFileDialog.Detail)
            if fd.exec_() != QDialog.Accepted:
                return

            fname = fd.selectedFiles()[0]

        self.api.saveProject(fname)
        logging.info("Project Saved")

    def saveProjectAs(self):
        fname = self.api.project().getFilename()

        fd = QFileDialog(self, 'Save as New File', fname,
                         'ChipWhisperer Project (*.cwp)')
        fd.setOption(QFileDialog.DontUseNativeDialog)
        fd.setDefaultSuffix(
            'cwp'
        )  # Will not append the file extension if using the static file dialog
        fd.setAcceptMode(QFileDialog.AcceptSave)
        fd.setViewMode(QFileDialog.Detail)
        if fd.exec_() != QDialog.Accepted:
            return

        fname = fd.selectedFiles()[0]
        if not fname: return

        self.api.saveProject(fname)
        self.updateStatusBar("Project Saved")

    def saveProjectAs(self):
        fname = self.api.project().getFilename()

        fd = QFileDialog(self, 'Save as New File', fname,
                         'ChipWhisperer Project (*.cwp)')
        fd.setOption(QFileDialog.DontUseNativeDialog)
        fd.setDefaultSuffix(
            'cwp'
        )  # Will not append the file extension if using the static file dialog
        fd.setAcceptMode(QFileDialog.AcceptSave)
        fd.setViewMode(QFileDialog.Detail)
        if fd.exec_() != QDialog.Accepted:
            return

        fname = fd.selectedFiles()[0]
        if not fname: return

        self.api.saveProject(fname)
        self.updateStatusBar("Project Saved")

    def newProject(self):
        self.okToContinue()
        self.api.newProject()
        logging.info("New Project Created")

    def setProject(self, proj):
        self.api.setProject(proj)

    def okToContinue(self):
        if self.api.project() is None: return True

        reply = SaveProjectDialog.getSaveProjectDialog(self,
                                                       self.api.project())
        if reply == QDialogButtonBox.RejectRole:
            return False
        elif reply == QDialogButtonBox.YesRole:
            self.saveProject()
            return True
        elif reply == QDialogButtonBox.NoRole:
            return True
        else:
            raise AttributeError("Internal Error: Invalid role: %s" %
                                 str(reply))

    def consolidateDialog(self):
        msgBox = QMessageBox()
        msgBox.setText(
            "Consolidate will copy all data into the project directory.")
        msgBox.setInformativeText(
            "Do you want to keep the original trace files?")
        msgBox.setStandardButtons(QMessageBox.Yes | QMessageBox.No
                                  | QMessageBox.Cancel)
        msgBox.setDefaultButton(QMessageBox.Yes)
        ret = msgBox.exec_()

        if ret == QMessageBox.Yes:
            self.api.project().consolidate(keepOriginals=True)
        elif ret == QMessageBox.No:
            self.api.project().consolidate(keepOriginals=False)

    def cleanDialog(self):
        msgBox = QMessageBox()
        msgBox.setText("Clean will delete temporary and intermediate data from your project directory:\n"\
                       "\n * Partition Comparison aux traces"\
                       )
        msgBox.setInformativeText("Really delete?")
        msgBox.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
        msgBox.setDefaultButton(QMessageBox.No)
        ret = msgBox.exec_()

        if ret == QMessageBox.Yes:
            self.api.project().clean(simulate=False)

    def updateStatusBar(self, message):
        if len(message) > 150:
            message = message[:147] + "..."
        msg = message + " (" + datetime.now().strftime(
            '%d/%m/%y %H:%M:%S') + ")"
        self.statusBar().showMessage(msg)

    def runScript(self, scriptClass, funcName="run"):
        logging.info("Running Script: %s" % scriptClass.getClassName())
        self.api.runScriptClass(scriptClass, funcName=funcName)
        logging.info("Finished Script: %s" % scriptClass.getClassName())

    def exceptionHandlerDialog(self, etype, value, trace):
        """ Handler for uncaught exceptions (for unknown Errors only - fix when you find one)."""

        if issubclass(etype,
                      KeyboardInterrupt):  # So program can exit with Ctrl + C.
            self.close()
            return

        details = "".join(traceback.format_exception(etype, value, trace))

        if issubclass(etype, Warning):
            logging.warning(str(value))
            dialog = QMessageBox(QMessageBox.Warning, "Warning", str(value),
                                 QMessageBox.Close, self)
            dialog.setDetailedText(details)
            dialog.exec_()
            return

        logging.error(details)
        dialog = QMessageBox(
            QMessageBox.Critical, "Error",
            "An error has occurred:<br>%s<br><br>It is usually safe to continue, but save your work just in case.<br>"
            "If it persists, try reseting the settings first before creating a <a href='https://www.assembla.com/spaces/chipwhisperer/tickets'>new ticket</a> informing the details bellow."
            % value, QMessageBox.Close, self)
        dialog.setTextFormat(
            Qt.RichText)  # this is what makes the links clickable
        dialog.setDetailedText(details)
        dialog.exec_()

    @staticmethod
    def getInstance():
        return CWMainGUI.instance

    def clearFocus(self):
        """Accept the current parameter edition by removing its focus"""
        if QApplication.focusWidget() is not None:
            QApplication.focusWidget().clearFocus()
Ejemplo n.º 4
0
class CWMainGUI(QMainWindow):
    """
    This is the base GUI class, used for both the Analyzer and Capture software. It defines a number of
    useful features such as the ability to add docks, setting windows, consoles for logging errors, etc.
    """
    maxRecentFiles = 4

    def __init__(self, api, name="Demo", icon="cwicon"):
        QMainWindow.__init__(self)
        CWMainGUI.instance = self
        self.name = name
        sys.excepthook = self.exceptionHandlerDialog
        util.setUIupdateFunction(QCoreApplication.processEvents)
        self.api = api
        self.setCentralWidget(None)
        ParameterTypesCustom.helpwnd = HelpBrowser(self).showHelp
        self.setDockNestingEnabled(True)
        self.traceManagerDialog = TraceManagerDialog(self)
        self.projEditWidget = ProjectTextEditor(self)
        self.initUI(icon)
        ResultsBase.sigRegisteredObjectsChanged.connect(self.newResultWidget)
        self.resultDocks = []

        self.loadExtraModules()
        self.addToolMenuItems()
        self.toolMenu.addSeparator()
        self.addSettingsDocks()
        self.restoreSettings()

        self.projectChanged()
        self.api.sigNewProject.connect(self.projectChanged)
        self.api.sigTracesChanged.connect(self.tracesChanged)
        Parameter.sigParametersChanged.connect(self.reloadGuiActions)

    def newResultWidget(self, resultWidget):
        # Remove all old actions that don't apply for new selection
        if resultWidget.getWidget():
            self.resultDocks.append(self.addDock(resultWidget.getWidget(), name=resultWidget.getName(), area=Qt.TopDockWidgetArea))

    def loadExtraModules(self):
        pass

    def tracesChanged(self):
        pass

    def projectChanged(self):
        self.traceManagerDialog.setTraceManager(self.api.project().traceManager())
        self.projEditWidget.setProject(self.api.project())
        self.api.project().sigStatusChanged.connect(self.projectStatusChanged)
        self.projectStatusChanged()

    def addDock(self, dockWidget, name="Settings", area=Qt.TopDockWidgetArea,
                allowedAreas=Qt.AllDockWidgetAreas,
                visible=True, addToWindows=True):
        """Add a dockwidget to the main window, which also adds it to the 'Windows' menu"""
        #Configure dock
        dock = QDockWidget(name)
        dock.setWidget(dockWidget)
        dock.setAllowedAreas(allowedAreas)
        dock.setObjectName(name)
        self.addDockWidget(area, dock)
        if(hasattr(dockWidget,"visibilityChanged")):
            dockWidget.visibilityChanged.connect(dock.setVisible)
            dock.visibilityChanged.connect(lambda: dockWidget.updateVisibility(dock.isVisible()))

        if visible == False:
            dock.toggleViewAction()
        
        #Add to "Windows" menu
        if addToWindows:
            self.windowMenu.addAction(dock.toggleViewAction())

        return dock
    
    def addSettings(self, param):
        """Adds a dockwidget designed to store a ParameterTree, also adds to 'Windows' menu"""
        parameterTree = ParameterTree()
        parameterTree.addParameters(param._PyQtGraphParameter)
        dock = self.addDock(parameterTree, name=param.getName(), area=Qt.TopDockWidgetArea)
        dock.setMaximumWidth(560)
        return dock

    def tabifyDocks(self, docks):
        if len(docks) > 0:
            for index in range(1, len(docks)):
                docks[index].show()
                self.tabifyDockWidget(docks[index-1], docks[index])
            docks[0].raise_()

    def getTraceSource(self):
        raise self.api.project().traceManager()

    def addConsole(self, name="Debug Logging", visible=True, redirectStdOut=True):
        """Add a QTextBrowser, used as a console/debug window"""
        console = QTextBrowser()
        console.write = console.insertPlainText
        if redirectStdOut:
            self.originalStdout = sys.stdout
            sys.stdout = OutLog(console, sys.stdout, origStdout=self.originalStdout)
            sys.stderr = OutLog(console, sys.stderr, QColor(255, 0, 0), origStdout=self.originalStdout)

        return self.addDock(console, name, area=Qt.BottomDockWidgetArea, visible=visible)
    
    def addPythonConsole(self, name="Python Console", visible=False):
        """Add a python console, inside which you can access the Python interpreter"""
        # tmp = locals()
        # tmp.update({'self':self})
        # wid = pyqtgraph.console.ConsoleWidget(namespace=tmp, text="")
        wid = chipwhisperer.common.ui.PythonConsole.QPythonConsole(self, locals())
        return self.addDock(wid, name, area=Qt.BottomDockWidgetArea, visible=visible)

    def reloadGuiActions(self):
        # Remove all old actions that don't apply for new selection
        if hasattr(self,"_ToolMenuItems"):
            for act in self._ToolMenuItems:
                self.toolMenu.removeAction(act)

        self._ToolMenuItems = []
        self._ToolMenuItems.append(self.toolMenu.addSeparator())
        for act in Parameter.getAllParameters("menu"):
            self._ToolMenuItems.append(QAction(act.getName(), self, statusTip=act.getTip(), triggered=act.callAction))

        for act in self._ToolMenuItems:
            self.toolMenu.addAction(act)

    def reset(self):
        """Clear all saved QSettings(), such as window location etc and exit"""
        QSettings().clear()
        self.dontSaveGeometry = True
        self.close()

    def restoreSettings(self):
        self.restoreGeometry(QSettings().value("geometry"))
        self.restoreState(QSettings().value("windowState"))

    def saveSettings(self):
        QSettings().setValue("geometry", self.saveGeometry())
        QSettings().setValue("windowState", self.saveState())

    def closeEvent(self, event):
        """Called when window is closed, attempts to save state/geometry"""
        if not (hasattr(self, "dontSaveGeometry") and self.dontSaveGeometry):
            self.saveSettings()

        if self.okToContinue():
            QMainWindow.closeEvent(self, event)
        else:
            event.ignore()

    def helpdialog(self):
        """Helps the User"""
        QMessageBox.about(self, 'Link to Documentation',
            'See <a href="http://www.newae.com/sidechannel/cwdocs">newae.com/sidechannel/cwdocs</a> for Tutorials and '
            'Documentation. If you are using an official release, this documentation should have also been present with'
            ' your release.<br><br>'
            'See <a href="http://www.chipwhisperer.com">chipwhisperer.com</a> for Wiki, GIT Code, community information.'
            '<br><br>See the About dialog for Copyright, Trademark, and Authorship information'
        )

    def aboutdialog(self):
        """Tells the User"""
        QMessageBox.about(self, 'About',
            '<h3>ChipWhisperer' + u'\u2122' + '</h3>'
            '<h4>Copyright Information</h4>'
            'Copyright ' + u'\u00A9' + ' NewAE Technology Inc., 2013-2016. <br>'
            'Copyright ' + u'\u00A9' + ' Colin O\'Flynn, 2012-2016.'
            '<h4>License Information</h4>'
            'Released under the GPLv3 License, see <a href="http://www.gnu.org/copyleft/gpl.html">License Details</a>.<br>'
            'Various parts of this project may be released under additional open-source licenses such as the BSD License '
            'or LGPL license. See source code for details of specific licenses.'
            '<h4>Projects used in ChipWhisperer</h4>'
            'ChipWhisperer is built on or otherwise uses a number of open-source projects. Many thanks are given to the following '
            'projects, which may also have additional license restrictions or information: '
            '<ul>'
            '<li><a href="https://www.python.org/">Python</a></li>'
            '<li><a href="http://qt-project.org/">Qt</a></li>'
            '<li><a href="http://qt-project.org/wiki/pyside">PySide</a></li>'
            '<li><a href="http://www.pyqtgraph.org/">PyQtGraph</a></li>'
            '<li><a href="http://www.numpy.org/">NumPy</a></li>'
            '<li><a href="http://www.scipy.org/">SciPy</a></li>'
            '<li><a href="http://sourceforge.net/apps/trac/pyusb/">PyUSB</a></li>'
            '<li><a href="http://www.fourwalledcubicle.com/LUFA.php">LUFA USB Library</a></li>'
            '<li><a href="http://www.ztex.de/downloads/#firmware_kit">ZTEX EZ-USB SDK</a></li>'
            '<li><a href="http://www.libusb.org/wiki/libusb-1.0">libusb-1.0</a></li>'
            '<li><a href="http://winavr.sourceforge.net/">WinAVR</a></li>'
            '</ul>'
            'Some projects may be missing from the above list - please let us know, any omission is a mistake!'
            '<h4>Trademark Information</h4>'
            'ChipWhisperer is a Trademark of NewAE Technology Inc.'
        )

    def pluginDialog(self):
        self.dialog = pluginmanager.PluginStatusDialog(self)
        self.dialog.show()

    def createMenus(self):
        """Create all menus (File, Window, etc)"""

        self.fileMenu= self.menuBar().addMenu("&File")
        self.newAct = QAction(QIcon('new.png'), '&New', self, shortcut=QKeySequence.New,
                               statusTip='Create new Project', triggered=self.newProject)
        self.fileMenu.addAction(self.newAct)
        self.openAct = QAction(QIcon('open.png'), '&Open...', self, shortcut=QKeySequence.Open,
                               statusTip='Open Project File', triggered=self.openProject)
        self.fileMenu.addAction(self.openAct)
        self.openRec = QMenu('Open &Recent')
        self.fileMenu.addMenu(self.openRec)
        self.recentFilesAction = []
        for i in range(CWMainGUI.maxRecentFiles):
            self.recentFilesAction.append(QAction(self, visible=False, triggered=self.openRecentFile))
            self.openRec.addAction(self.recentFilesAction[i])
        self.saveAct = QAction(QIcon('save.png'), '&Save...', self, shortcut=QKeySequence.Save,
                               statusTip='Save current project to Disk', triggered=self.saveProject)
        self.fileMenu.addAction(self.saveAct)
        self.exitAct = QAction("E&xit", self, shortcut="Ctrl+Q",
                               statusTip="Exit the application", triggered=self.close)
        self.fileMenu.addSeparator()
        self.fileMenu.addAction(self.exitAct)
        
        self.projectMenu = self.menuBar().addMenu("&Project")
        self.traceManageAct = QAction('&Trace Management', self, statusTip='Add/Remove traces from project', triggered=self.traceManagerDialog.show)
        self.projectMenu.addAction(self.traceManageAct)
        self.consolidateAct = QAction('&Consolidate', self, statusTip='Copy trace files to project directory', triggered=self.consolidateDialog)
        self.projectMenu.addAction(self.consolidateAct)
        self.showProjFileAct = QAction('&Project File Editor (Text)', self, statusTip='Edit project file', triggered=self.projEditDock.show)
        self.projectMenu.addAction(self.showProjFileAct)

        self.toolMenu = self.menuBar().addMenu("&Tools")

        self.windowMenu = self.menuBar().addMenu("&Windows")        
                
        self.helpMenu = self.menuBar().addMenu("&Help")
        self.helpMenu.addAction(QAction('&Save Settings', self, statusTip='Save all settings', triggered=self.saveSettings))
        self.helpMenu.addAction(QAction('&Restore Settings', self, statusTip='Restore all settings to previous saved state', triggered=self.restoreSettings))
        self.helpMenu.addAction(QAction('Reset Settings and &Exit', self, statusTip='Clear all settings and exit', triggered=self.reset))
        self.helpMenu.addAction(QAction('&Tutorial/User Manual', self, statusTip='Everything you need to know', triggered=self.helpdialog))
        self.helpMenu.addAction(QAction('&List Enabled/Disable Plugins', self, statusTip='Check if you\'re missing plugins', triggered=self.pluginDialog))
        # self.helpMenu.addAction(QAction('&ChipWhisperer Documentation', self, statusTip='ChipWisperer Wiki Page', triggered=lambda:QDesktopServices.openUrl(QUrl("http://wiki.newae.com/Main_Page"))))
        self.helpMenu.addAction(QAction('&Check for Updates', self, statusTip='Check for new versions', triggered=self.checkForUpdates))
        self.helpMenu.addAction(QAction('&About', self, statusTip='About dialog', triggered=self.aboutdialog))

    def checkForUpdates(self):
        try:
            source = urllib.urlopen("https://www.assembla.com/spaces/chipwhisperer/git/source/master/software/chipwhisperer/common/api/CWCoreAPI.py?_format=raw")
            content = source.read()

            version = None
            for line in content.split("\n"):
                if "__version__" in line:
                    version = line.split('=')[1]
                    break
            if version is not None:
                version = version.lstrip(' "').rstrip(' "')
                if self.api.__version__ == version:
                    message = "Your current version is already the most recent one."
                else:
                    message = "There is an updated version available:\n"
                    source = urllib.urlopen("https://www.assembla.com/spaces/chipwhisperer/git/source/master/CHANGES.txt?_format=raw")
                    content = source.read()
                    for line in content.split("\n"):
                        if self.api.__version__+":" in line:
                            break
                        message += line + "\n"
            else:
                message = "Could not check for the most recent version. Try visiting the project webpage."
        except IOError:
            message = "Could not retrieve the information. Check your internet connection."
        QMessageBox.information(self, "Checking for updates...", message)

    def addToolMenuItems(self):
        pass

    def initUI(self, icon="cwicon"):
        """Setup the UI, creating statusbar, setting title, menus, etc"""
        self.statusBar()
        self.setWindowIcon(QIcon(":/images/%s.png" % icon))
        self.projEditDock = self.addDock(self.projEditWidget, name="Project Text Editor", area=Qt.BottomDockWidgetArea, visible=False, addToWindows=False)
        self.createMenus()
        self.updateRecentFileActions()
        self.setupToolBar()

        # Project editor dock
        self.paramScriptingDock = self.addConsole("Script Commands", visible=False, redirectStdOut=False)
        Parameter.scriptingOutput = self.paramScriptingDock.widget()  # set as the default paramenter scripting log output
        self.consoleDock = self.addConsole()
        self.pythonConsoleDock = self.addPythonConsole()
        self.tabifyDocks([self.projEditDock, self.paramScriptingDock, self.pythonConsoleDock, self.consoleDock])
        self.setBaseSize(800,600)

    def setupToolBar(self):
        self.toolbar = self.addToolBar('Tools')
        self.toolbar.setObjectName('Tools')
        self.toolbar.addAction(QAction(QIcon(":/images/open.png"), 'Open', self, triggered=self.openProject))
        self.toolbar.addAction(QAction(QIcon(":/images/save.png"), 'Save', self, triggered=self.saveProject))
        self.toolbar.addSeparator()
        self.toolbar.addAction(QAction(QIcon(":/images/tracemanager.png"), 'Trace Manager', self, triggered=self.traceManagerDialog.show))

        self.addToolbarItems(self.toolbar)
        self.toolbar.show()

    def addToolbarItems(self, toolbar):
        pass

    def addExampleScripts(self, scripts):
        self.exampleScriptAct = QAction('&Example Scripts', self, statusTip='Predefined Scripts')
        self.projectMenu.addSeparator()
        self.projectMenu.addAction(self.exampleScriptAct)
        subMenu = QMenu("Submenu", self)

        for name, script in scripts.iteritems():
            subMenu.addAction(QAction(name, self, statusTip=script.getDescription(), triggered=partial(self.runScript, script)))

        self.exampleScriptAct.setMenu(subMenu)

    def updateTitleBar(self):
        """Update filename shown in title bar"""
        fname = os.path.basename(self.api.project().getFilename())
        self.setWindowTitle("%s - %s[*]" %(self.name, fname))
        self.setWindowModified(True)

    def projectStatusChanged(self):
        """Add File to recent file list"""
        self.updateTitleBar()
        
        if self.api.project().isUntitled(): return
        
        files = QSettings().value('recentFileList')
        if files is None or not isinstance(files, list):
            files = []

        try:
            files.remove(self.api.project().getFilename())
        except Exception:
            pass

        files.insert(0, self.api.project().getFilename())
        numRecentFiles = min(len(files), CWMainGUI.maxRecentFiles)
        files = files[:numRecentFiles]

        QSettings().setValue('recentFileList', files)
        self.updateRecentFileActions()

    def updateRecentFileActions(self):
        """Update & Load the list of recent files"""
        files = QSettings().value('recentFileList')
        if files is not None and isinstance(files, list):
            files_no = 0
            for f in files:
                text = "&%d %s" % (files_no + 1, util.strippedName(f))
                self.recentFilesAction[files_no].setText(text)
                self.recentFilesAction[files_no].setData(f)
                self.recentFilesAction[files_no].setVisible(True)
                files_no += 1

            for j in range(files_no, CWMainGUI.maxRecentFiles):
                self.recentFilesAction[j].setVisible(False)

    def openRecentFile(self):
        action = self.sender()
        if action:
            self.openProject(action.data())

    def openProject(self, fname = None):
        if not self.okToContinue():
            return
        if fname is None:
            fname, _ = QFileDialog.getOpenFileName(self, 'Open File', './projects/','ChipWhisperer Project (*.cwp)','', QFileDialog.DontUseNativeDialog)
            if not fname: return

        self.updateStatusBar("Opening Project: " + fname)
        self.api.openProject(fname)

    def saveProject(self):
        fname = self.api.project().getFilename()
        if self.api.project().isUntitled():
            fd = QFileDialog(self, 'Save New File', './projects/', 'ChipWhisperer Project (*.cwp)')
            fd.setOption(QFileDialog.DontUseNativeDialog)
            fd.setDefaultSuffix('cwp') # Will not append the file extension if using the static file dialog
            fd.setAcceptMode(QFileDialog.AcceptSave)
            fd.setViewMode(QFileDialog.Detail)
            if fd.exec_() != QDialog.Accepted:
                return

            fname = fd.selectedFiles()[0]

        self.api.saveProject(fname)
        self.updateStatusBar("Project Saved")

    def newProject(self):
        self.okToContinue()
        self.api.newProject()
        self.updateStatusBar("New Project Created")

    def setProject(self, proj):
        self.api.setProject(proj)

    def okToContinue(self):
        if self.api.project() is None: return False

        reply = SaveProjectDialog.getSaveProjectDialog(self, self.api.project())
        if reply == QDialogButtonBox.RejectRole:
            return False
        elif reply == QDialogButtonBox.YesRole:
            self.saveProject()
            return True
        elif reply == QDialogButtonBox.NoRole:
            return True
        else:
            raise AttributeError("Internal Error: Invalid role: %s" % str(reply))

    def consolidateDialog(self):
        msgBox = QMessageBox()
        msgBox.setText("Consolidate will copy all data into the project directory.")
        msgBox.setInformativeText("Do you want to keep the original trace files?")
        msgBox.setStandardButtons(QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel)
        msgBox.setDefaultButton(QMessageBox.Yes)
        ret = msgBox.exec_()

        if ret == QMessageBox.Yes:
            self.api.project().consolidate(keepOriginals = True)
        elif ret == QMessageBox.No:
            self.api.project().consolidate(keepOriginals = False)

    def updateStatusBar(self, message):
        msg = message + " (" +  datetime.now().strftime('%d/%m/%y %H:%M:%S') + ")"
        print "Status: " + msg
        self.statusBar().showMessage(msg)

    def runScript(self, scriptClass, funcName="run"):
        self.updateStatusBar("Running Script: %s" % scriptClass.getClassName())
        self.api.runScriptClass(scriptClass, funcName="run")
        self.updateStatusBar("Finished Script: %s" % scriptClass.getClassName())

    def exceptionHandlerDialog(self, etype, value, trace):
        """ Handler for uncaught exceptions (for unknown Errors only - fix when you find one)."""

        if issubclass(etype, KeyboardInterrupt): # So program can exit with Ctrl + C.
            self.close()
            return

        details = "".join(traceback.format_exception(etype, value, trace))

        if issubclass(etype, Warning):
            print "WARNING: " + str(value)
            dialog = QMessageBox(QMessageBox.Warning, "Warning", str(value), QMessageBox.Close, self)
            dialog.setDetailedText(details)
            dialog.exec_()
            return

        print details
        dialog = QMessageBox(QMessageBox.Critical, "Error",
                    "An error has occurred:<br>%s<br><br>It is usually safe to continue, but save your work just in case.<br>"
                    "If the error occurs again, please create a new ticket <a href='https://www.assembla.com/spaces/chipwhisperer/tickets'>here</a> informing the details bellow." % value, QMessageBox.Close, self)
        dialog.setTextFormat(Qt.RichText) # this is what makes the links clickable
        dialog.setDetailedText(details)
        dialog.exec_()

    @staticmethod
    def getInstance():
        return CWMainGUI.instance