view python/ide/ide.py @ 326:2fa27c44e3c9

Changed hasbang
author Windel Bouwman
date Sat, 01 Feb 2014 16:54:33 +0100
parents ff665880a6b0
children dcae6574c974
line wrap: on
line source

#!/usr/bin/python

import sys
import os
import logging
import traceback
import io

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

# Compiler imports:
p = os.path.join(os.path.dirname(__file__), '..')
sys.path.insert(0, p)
sys.path.insert(0, os.path.join(p, 'utils'))
import ppci
from astviewer import AstViewer
from codeedit import CodeEdit
from logview import LogView as BuildOutput
from disasm import Disassembly
stutil = __import__('st-util')
import zcc
import outstream
from target import armtarget


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 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()

    # 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!')

    def doBuild(self):
        ce = self.activeMdiChild()
        if not ce:
            return
        fn = ce.FileName
        self.diag.clear()
        outs = outstream.TextOutputStream()
        imps = [open(ce.FileName, 'r') for ce in self.allChildren() if ce.FileName and ce.FileName != fn]
        if not zcc.zcc([open(fn, 'r')], imps, armtarget, outs, self.diag):
            # Set errors:
            self.builderrors.setErrorList(self.diag.diags)
            ce.setErrors(self.diag.diags)
            return
        return outs

    def buildFile(self):
        self.doBuild()

    def buildFileAndFlash(self):
        outs = self.doBuild()
        if not outs:
            return

        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_()