view python/ide.py @ 256:225f444019b1

Added build and flash menu option
author Windel Bouwman
date Sun, 04 Aug 2013 18:32:04 +0200
parents 7416c923a02a
children 04c19282a5aa
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
stutil = __import__('st-util')
import c3
import zcc
import outstream

logformat='%(asctime)s|%(levelname)s|%(name)s|%(msg)s'

class BuildOutput(QTextEdit):
    """ Build output component """
    def __init__(self, parent=None):
        super(BuildOutput, self).__init__(parent)
        fmt = logging.Formatter(fmt=logformat)
        class MyHandler(logging.Handler):
            def emit(self2, x):
                self.append(str(fmt.format(x)))

        logging.getLogger().addHandler(MyHandler())
        self.setCurrentFont(QFont('Courier'))
        self.setReadOnly(True)
        logging.info('Build output will appear here!')


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.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)
         irow = QStandardItem(str(e.loc.row))
         irow.setData(e)
         icol = QStandardItem(str(e.loc.col))
         icol.setData(e)
         self.model.appendRow([item, irow, icol])

    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)
        aboutText = """<h1>lcfOS IDE</h1>
        <p>An all-in-one IDE for OS development.</p>
        <p>https://www.assembla.com/spaces/lcfOS/wiki</p>
        <p>Author: Windel Bouwman</p>
        """
        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.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.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')
            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()
        else:
            ce.clearErrors()

    def pointCode(self, p):
        # Lookup pc in debug infos:
        loc = None
        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):
        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)
        logging.info('Done!')

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

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

        code_s = outs.getSection('code')
        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!')


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