view python/ide.py @ 284:05184b95fa16

Moved tests to seperate folder
author Windel Bouwman
date Fri, 15 Nov 2013 13:43:22 +0100
parents c137f1fe3e65
children 1c7c1e619be8
line wrap: on
line source

#!/usr/bin/python

import sys
import os
import logging

from PyQt4.QtCore import *
from PyQt4.QtGui import *

# Compiler imports:
import ppci
from astviewer import AstViewer
from codeedit import CodeEdit
from logview import LogView as BuildOutput
stutil = __import__('st-util')
import c3
import zcc
import outstream
import traceback


def handle_exception(tp, v, tb):
    logging.critical(str(v))
    tb = traceback.format_tb(tb)
    for i in tb:
        logging.critical(i.strip())

sys.excepthook = handle_exception

class BuildErrors(QTreeView):
    sigErrorSelected = pyqtSignal(object)

    def __init__(self, parent=None):
        super(BuildErrors, self).__init__(parent)
        model = QStandardItemModel()
        self.setModel(model)
        self.clicked.connect(self.itemSelected)
        self.errorIcon = QIcon('icons/error.png')
        self.model = QStandardItemModel()
        self.model.setHorizontalHeaderLabels(['Message', 'Row', 'Column'])
        self.header().setStretchLastSection(True)
        self.setModel(self.model)

    def setErrorList(self, errorlist):
        c = self.model.rowCount()
        self.model.removeRows(0, c)
        for e in errorlist:
            item = QStandardItem(self.errorIcon, str(e.msg))
            item.setData(e)
            row = str(e.loc.row) if e.loc else ''
            irow = QStandardItem(row)
            irow.setData(e)
            col = str(e.loc.col) if e.loc else ''
            icol = QStandardItem(col)
            icol.setData(e)
            self.model.appendRow([item, irow, icol])
        for i in range(3):
            self.resizeColumnToContents(i)

    def itemSelected(self, index):
        if not index.isValid():
            return
        item = self.model.itemFromIndex(index)
        err = item.data()
        self.sigErrorSelected.emit(err)


class DisAsmModel(QAbstractTableModel):
    def __init__(self):
        super().__init__()
        self.outs = None
        self.instructions = []
        self.headers = ['Address', 'Bytes', 'Instruction']
        self.txts = []
        self.txts.append(lambda i: '0x{:08x}'.format(i.address))
        self.txts.append(lambda i: str(i.encode()))
        self.txts.append(lambda i: str(i))

    def rowCount(self, parent):
        return len(self.instructions)
        
    def columnCount(self, parent):
        return len(self.headers)

    def data(self, index, role):
        if not index.isValid():
            return
        row, col = index.row(), index.column()
        if role == Qt.DisplayRole:
            i = self.instructions[row]
            return self.txts[col](i)

    def headerData(self, section, orientation, role):
        if orientation == Qt.Horizontal and role == Qt.DisplayRole:
            return self.headers[section]
            
    def setInstructions(self, ins):
        self.instructions = ins
        self.modelReset.emit()
    

class Disassembly(QTableView):
    def __init__(self):
        super().__init__()
        self.dm = DisAsmModel()
        self.setModel(self.dm)

    def showPos(self, p):
        for i in self.dm.instructions:
            if i.address == p:
                row = self.dm.instructions.index(i)
                self.selectRow(row)

class AboutDialog(QDialog):
    def __init__(self, parent=None):
        super(AboutDialog, self).__init__(parent)
        self.setWindowTitle('About')
        l = QVBoxLayout(self)
        txt = QTextEdit(self)
        txt.setReadOnly(True)
        with open(os.path.join('..', 'readme.rst'), 'r') as f:
            aboutText = f.read()
        txt.append(aboutText)
        l.addWidget(txt)
        but = QPushButton('OK')
        but.setDefault(True)
        but.clicked.connect(self.close)
        l.addWidget(but)


class Ide(QMainWindow):
    def __init__(self, parent=None):
        super(Ide, self).__init__(parent)
        self.to_open_files = []
        self.logger = logging.getLogger('ide')

        self.setWindowTitle('LCFOS IDE')
        icon = QIcon('icons/logo.png')
        self.setWindowIcon(icon)

        # Create menus:
        mb = self.menuBar()
        self.fileMenu = mb.addMenu('File')
        self.viewMenu = mb.addMenu('View')
        self.helpMenu = mb.addMenu('Help')

        # Create mdi area:
        self.mdiArea = QMdiArea()
        self.mdiArea.setViewMode(QMdiArea.TabbedView)
        self.mdiArea.setTabsClosable(True)
        self.mdiArea.setTabsMovable(True)
        self.setCentralWidget(self.mdiArea)

        # Create components:
        def addComponent(name, widget):
            dw = QDockWidget(name)
            dw.setWidget(widget)
            dw.setObjectName(name)
            self.addDockWidget(Qt.RightDockWidgetArea, dw)
            self.viewMenu.addAction(dw.toggleViewAction())
            return widget

        self.buildOutput = addComponent('Build output', BuildOutput())
        self.astViewer = addComponent('AST viewer', AstViewer())
        self.astViewer.sigNodeSelected.connect(lambda node: self.showLoc(node.loc))
        self.builderrors = addComponent('Build errors', BuildErrors())
        self.builderrors.sigErrorSelected.connect(lambda err: self.showLoc(err.loc))
        self.devxplr = addComponent('Device explorer', stutil.DeviceExplorer())
        self.regview = addComponent('Registers', stutil.RegisterView())
        self.memview = addComponent('Memory', stutil.MemoryView())
        self.disasm = addComponent('Disasm', Disassembly())
        self.ctrlToolbar = stutil.DebugToolbar()
        self.addToolBar(self.ctrlToolbar)
        self.ctrlToolbar.setObjectName('debugToolbar')
        self.devxplr.deviceSelected.connect(self.regview.mdl.setDevice)
        self.ctrlToolbar.statusChange.connect(self.memview.refresh)
        self.devxplr.deviceSelected.connect(self.memview.setDevice)
        self.devxplr.deviceSelected.connect(self.ctrlToolbar.setDevice)
        self.ctrlToolbar.statusChange.connect(self.regview.refresh)
        self.ctrlToolbar.codePosition.connect(self.pointCode)

        # About dialog:
        self.aboutDialog = AboutDialog()
        self.aboutDialog.setWindowIcon(icon)

        # Create actions:
        def addMenuEntry(name, menu, callback, shortcut=None):
            a = QAction(name, self)
            menu.addAction(a)
            a.triggered.connect(callback)
            if shortcut:
                a.setShortcut(shortcut)

        addMenuEntry("New", self.fileMenu, self.newFile, shortcut=QKeySequence(QKeySequence.New))
        addMenuEntry("Open", self.fileMenu, self.openFile, shortcut=QKeySequence(QKeySequence.Open))
        addMenuEntry("Save", self.fileMenu, self.saveFile, shortcut=QKeySequence(QKeySequence.Save))
        addMenuEntry("Build", self.fileMenu, self.buildFile, shortcut=QKeySequence("F7"))
        addMenuEntry("Build and flash", self.fileMenu, self.buildFileAndFlash, shortcut=QKeySequence("F8"))

        self.helpAction = QAction('Help', self)
        self.helpAction.setShortcut(QKeySequence('F1'))
        self.helpMenu.addAction(self.helpAction)
        addMenuEntry('About', self.helpMenu, self.aboutDialog.open)

        addMenuEntry('Cascade windows', self.viewMenu, self.mdiArea.cascadeSubWindows)
        addMenuEntry('Tile windows', self.viewMenu, self.mdiArea.tileSubWindows)
        sb = self.statusBar()

        # Load settings:
        self.settings = QSettings('windelsoft', 'lcfoside')
        self.loadSettings()
        self.diag = ppci.DiagnosticsManager()
        self.c3front = c3.Builder(self.diag)

    # File handling:
    def newFile(self):
        self.newCodeEdit()

    def openFile(self):
        filename = QFileDialog.getOpenFileName(self, "Open C3 file...", "*.c3",
                    "C3 source files (*.c3)")
        if filename:
            self.loadFile(filename)

    def saveFile(self):
        ac = self.activeMdiChild()
        if ac:
            ac.save()

    def loadFile(self, filename):
        ce = self.newCodeEdit()
        try:
            with open(filename) as f:
                ce.Source = f.read()
                ce.FileName = filename
        except Exception as e:
            print('exception opening file', e)

    # MDI:
    def newCodeEdit(self):
        ce = CodeEdit()
        ce.textChanged.connect(self.parseFile)
        w = self.mdiArea.addSubWindow(ce)
        self.mdiArea.setActiveSubWindow(w)
        ce.showMaximized()
        return ce

    def activeMdiChild(self):
        aw = self.mdiArea.activeSubWindow()
        if aw:
            return aw.widget()

    def findMdiChild(self, filename):
        for wid in self.allChildren():
            if wid.filename == filename:
                return wid

    def allChildren(self):
        return [w.widget() for w in self.mdiArea.subWindowList()]

    # Settings:
    def loadSettings(self):
        if self.settings.contains('mainwindowstate'):
            self.restoreState(self.settings.value('mainwindowstate'))
        if self.settings.contains('mainwindowgeometry'):
            self.restoreGeometry(self.settings.value('mainwindowgeometry'))
        if self.settings.contains('lastfiles'):
            lfs = self.settings.value('lastfiles')
            if lfs:
                self.to_open_files.extend(lfs)

    def showEvent(self, ev):
        super().showEvent(ev)
        while self.to_open_files:
            fn = self.to_open_files.pop(0)
            self.loadFile(fn)

    def closeEvent(self, ev):
        self.settings.setValue('mainwindowstate', self.saveState())
        self.settings.setValue('mainwindowgeometry', self.saveGeometry())
        ac = self.activeMdiChild()
        lfs = [ce.FileName for ce in self.allChildren() if ce.FileName]
        self.settings.setValue('lastfiles', lfs)
        ev.accept()

    # Error handling:
    def nodeSelected(self, node):
        self.showLoc(node.loc)

    def showLoc(self, loc):
        ce = self.activeMdiChild()
        if not ce:
            return
        if loc:
            ce.setRowCol(loc.row, loc.col)
            ce.setFocus()

    def pointCode(self, p):
        # Lookup pc in debug infos:
        loc = None
        print(p)
        self.disasm.showPos(p)
        if hasattr(self, 'debugInfo'):
            for di in self.debugInfo:
                if di.address > p:
                    loc = di.info
                    break
        if loc:
            ce = self.activeMdiChild()
            if ce:
                ce.ic.arrow = loc
            self.showLoc(loc)

    # Build recepy:
    def parseFile(self):
        self.logger.info('Parsing!')
        ce = self.activeMdiChild()
        if not ce:
            return
        self.diag.clear()
        pkg = self.c3front.parse(ce.Source)

        # Set errors:
        self.builderrors.setErrorList(self.diag.diags)
        ce.setErrors(self.diag.diags)
        self.astViewer.setAst(pkg)
        if pkg:
            c3.AstPrinter().printAst(pkg)
        self.logger.info('Done!')

    def buildFile(self):
        ce = self.activeMdiChild()
        if not ce:
            return
        fn = ce.FileName
        wd = os.path.dirname(fn)
        self.diag.clear()
        outs = outstream.TextOutputStream()
        if not zcc.zcc(ce.Source, outs, self.diag, pack_dir=wd):
            # Set errors:
            self.builderrors.setErrorList(self.diag.diags)
            ce.setErrors(self.diag.diags)
            return

    def buildFileAndFlash(self):
        ce = self.activeMdiChild()
        if not ce:
            return
        fn = ce.FileName
        wd = os.path.dirname(fn)
        self.diag.clear()
        outs = outstream.TextOutputStream()
        if not zcc.zcc(ce.Source, outs, self.diag, do_optimize=True, pack_dir=wd):
            # Set errors:
            self.builderrors.setErrorList(self.diag.diags)
            ce.setErrors(self.diag.diags)
            return

        outs.dump()
        code_s = outs.getSection('code')
        self.disasm.dm.setInstructions(code_s.instructions)
        self.debugInfo = code_s.debugInfos()
        if self.ctrlToolbar.device:
            logging.info('Flashing stm32f4 discovery')
            bts = code_s.to_bytes()
            self.ctrlToolbar.device.writeFlash(0x08000000, bts)
            stl = self.ctrlToolbar.device.iface
            stl.reset()
            stl.halt()
            stl.run()
            logging.info('Done!')
        else:
            self.logger.warning('Not connected to device, skipping flash')


if __name__ == '__main__':
    logging.basicConfig(format=zcc.logformat, level=logging.DEBUG)
    app = QApplication(sys.argv)
    ide = Ide()
    ide.show()
    ide.logger.info('IDE started')
    app.exec_()