changeset 64:4a27c28c7d0f

File movage
author windel
date Sun, 07 Oct 2012 17:13:47 +0200
parents 32078200cdd6
children 361f7d13adea
files python/apps/build.py python/apps/ide.py python/apps/ide/__init__.py python/apps/ide/astviewer.py python/apps/ide/codeeditor.py python/apps/ide/ide.py python/apps/lcfos.png python/apps/runbuild.py python/apps/runide.py python/libs/widgets/astviewer.py python/libs/widgets/codeeditor.py
diffstat 11 files changed, 516 insertions(+), 516 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/python/apps/build.py	Sun Oct 07 17:13:47 2012 +0200
@@ -0,0 +1,18 @@
+import sys, os
+
+sys.path.insert(0, os.path.join('..','libs'))
+print(sys.path)
+# Compiler imports:
+from compiler.compiler import Compiler
+from project import Project
+
+if __name__ == '__main__':
+   if len(sys.argv) < 2:
+      print('Use {0} projectfile'.format(sys.argv[0]))
+      sys.exit(-1)
+   filename = sys.argv[1]
+   project = Project()
+   project.load(filename)
+   pc = Compiler()
+   pc.compileProject(project)
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/python/apps/ide.py	Sun Oct 07 17:13:47 2012 +0200
@@ -0,0 +1,322 @@
+import sys
+
+from PyQt4.QtCore import *
+from PyQt4.QtGui import *
+# ide components:
+from .codeeditor import CodeEdit
+from .astviewer import AstViewer
+import base64
+import os.path
+
+# Compiler imports:
+from project import Project
+from compiler.compiler import Compiler
+
+lcfospng = base64.decodestring(b'iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A\n/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9sJEhMKBk7B678AAAA/SURBVFjD\n7dbBCQAgDATBi9h/y7EFA4Kf2QLCwH1S6XQu6sqoujublc8BAAAAAAAAAAB8B+zXT6YJAAAAAKYd\nWSgFQNUyijIAAAAASUVORK5CYII=\n')
+
+class BuildOutput(QTextEdit):
+   """ Build output component """
+   def __init__(self, parent=None):
+      super(BuildOutput, self).__init__(parent)
+      self.setCurrentFont(QFont('Courier'))
+      self.setReadOnly(True)
+      self.append('Build output will appear here!')
+
+class BuildErrors(QListView):
+   sigErrorSelected = pyqtSignal(object)
+   def __init__(self, parent=None):
+      super(BuildErrors, self).__init__(parent)
+      model = QStandardItemModel()
+      self.setModel(model)
+      self.clicked.connect(self.itemSelected)
+   def setErrorList(self, errorlist):
+      model = QStandardItemModel()
+      for e in errorlist:
+         row, col, msg = e
+         item = QStandardItem(str(msg))
+         item.setData(e)
+         model.appendRow(item)
+      self.setModel(model)
+   def itemSelected(self, index):
+      if not index.isValid():
+         return
+      model = self.model()
+      item = model.itemFromIndex(index)
+      err = item.data()
+      self.sigErrorSelected.emit(err)
+
+class ProjectView(QWidget):
+   sigLoadFile = pyqtSignal(str)
+   def __init__(self, parent=None):
+      super(ProjectView, self).__init__(parent)
+      self.treeview = QTreeView(self)
+      self.treeview.setContextMenuPolicy(Qt.CustomContextMenu)
+      l = QVBoxLayout(self)
+      l.addWidget(self.treeview)
+      pm = QPixmap()
+      pm.loadFromData(lcfospng)
+      self.projectIcon = QIcon(pm)
+      # Connect signals:
+      self.treeview.activated.connect(self.activate)
+      self.treeview.customContextMenuRequested.connect(self.contextMenu)
+   def setProject(self, project):
+      self.project = project
+      model = QStandardItemModel()
+      root = model.invisibleRootItem()
+      pitem = QStandardItem(self.projectIcon, project.name)
+      pitem.setEditable(False)
+      pitem.setData(project)
+      root.appendRow(pitem)
+      for f in self.project.files:
+         fitem = QStandardItem(f)
+         pitem.appendRow(fitem)
+         fitem.setEditable(False)
+         fitem.setData(f)
+      self.treeview.setModel(model)
+      self.treeview.expandAll()
+   def contextMenu(self, pos):
+      idx = self.treeview.indexAt(pos)
+      if not idx.isValid():
+         return
+      item = self.treeview.model().itemFromIndex(idx)
+   def activate(self, index):
+      if not index.isValid():
+         return
+      model = self.treeview.model()
+      item = model.itemFromIndex(index)
+      fn = item.data()
+      if type(fn) is str:
+         self.sigLoadFile.emit(fn)
+
+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 ProjectOptions(QDialog):
+   pass
+   # TODO: project options in here
+
+class Ide(QMainWindow):
+  def __init__(self, parent=None):
+    super(Ide, self).__init__(parent)
+    self.setWindowTitle('LCFOS IDE')
+    icon = QPixmap()
+    icon.loadFromData(lcfospng)
+    self.setWindowIcon(QIcon(icon))
+
+    # Create menus:
+    self.fileMenu = self.menuBar().addMenu('File')
+    self.viewMenu = self.menuBar().addMenu('View')
+    self.projectMenu = self.menuBar().addMenu('Project')
+    self.helpMenu = self.menuBar().addMenu('Help')
+
+    # Create mdi area:
+    self.mdiArea = QMdiArea()
+    self.setCentralWidget(self.mdiArea)
+
+    # Create components:
+    self.buildOutput = BuildOutput()
+    self.addComponent('Build output', self.buildOutput)
+
+    self.astViewer = AstViewer()
+    self.addComponent('AST viewer', self.astViewer)
+    self.astViewer.sigNodeSelected.connect(self.nodeSelected)
+
+    self.builderrors = BuildErrors()
+    self.addComponent('Build errors', self.builderrors)
+    self.builderrors.sigErrorSelected.connect(self.errorSelected)
+
+    self.projectview = ProjectView()
+    self.addComponent('Project', self.projectview)
+    self.projectview.sigLoadFile.connect(self.loadFile)
+
+    # About dialog:
+    self.aboutDialog = AboutDialog()
+    self.aboutDialog.setWindowIcon(QIcon(icon))
+    # Create actions:
+    self.buildAction = QAction('Build!', self)
+    self.buildAction.setShortcut(QKeySequence('F7'))
+    self.projectMenu.addAction(self.buildAction)
+    self.buildAction.triggered.connect(self.buildFile)
+    self.openProjectAction = QAction("Open project", self)
+    self.openProjectAction.triggered.connect(self.openProject)
+    self.projectMenu.addAction(self.openProjectAction)
+    self.helpAction  = QAction('Help', self)
+    self.helpAction.setShortcut(QKeySequence('F1'))
+    self.helpMenu.addAction(self.helpAction)
+    self.aboutAction = QAction('About', self)
+    self.helpMenu.addAction(self.aboutAction)
+    self.aboutAction.triggered.connect(self.aboutDialog.open)
+
+    self.newFileAction = QAction("New File", self)
+    self.fileMenu.addAction(self.newFileAction)
+    self.newFileAction.triggered.connect(self.newFile)
+    self.saveFileAction = QAction("Save File", self)
+    self.fileMenu.addAction(self.saveFileAction)
+    self.saveFileAction.triggered.connect(self.saveFile)
+    self.closeFileAction = QAction("Close File", self)
+    self.fileMenu.addAction(self.closeFileAction)
+    self.closeFileAction.triggered.connect(self.closeFile)
+
+    cascadeAction = QAction("Cascade windows", self)
+    cascadeAction.triggered.connect(self.mdiArea.cascadeSubWindows)
+    self.viewMenu.addAction(cascadeAction)
+    tileAction = QAction('Tile windows', self)
+    tileAction.triggered.connect(self.mdiArea.tileSubWindows)
+    self.viewMenu.addAction(tileAction)
+
+    # Load settings:
+    self.settings = QSettings('windelsoft', 'lcfoside')
+    self.loadSettings()
+
+  def addComponent(self, name, widget):
+     dw = QDockWidget(name)
+     dw.setWidget(widget)
+     dw.setObjectName(name)
+     self.addDockWidget(Qt.RightDockWidgetArea, dw)
+     self.viewMenu.addAction(dw.toggleViewAction())
+
+  # File handling:
+  def newFile(self):
+     ce = CodeEdit()
+     w = self.mdiArea.addSubWindow(ce)
+     ce.show()
+
+  def saveFile(self):
+     ac = self.activeMdiChild()
+     if ac:
+        ac.saveFile()
+
+  def saveAll(self):
+     pass
+
+  def openFile(self):
+     # TODO
+     pass
+
+  def closeFile(self):
+     ac = self.activeMdiChild()
+     if ac:
+        self.mdiArea.removeSubWindow(ac)
+  
+  def loadFile(self, filename):
+     # Find existing mdi widget:
+     wid = self.findMdiChild(filename)
+     if wid:
+        self.mdiArea.setActiveSubWindow(wid.parent())
+        return wid
+
+     # Create a new one:
+     ce = CodeEdit()
+     source = self.project.loadProjectFile(filename)
+     ce.setSource(source)
+     self.mdiArea.addSubWindow(ce)
+     ce.show()
+     return ce
+
+  # MDI:
+  def activeMdiChild(self):
+     aw = self.mdiArea.activeSubWindow()
+     if aw:
+        return aw.widget()
+     else:
+        return None
+
+  def findMdiChild(self, filename):
+     for window in self.mdiArea.subWindowList():
+        wid = window.widget()
+        if wid.filename == filename:
+           return wid
+     return None
+  
+  def allChildren(self):
+     c = []
+     for window in self.mdiArea.subWindowList():
+        wid = window.widget()
+        c.append(wid)
+     return c
+
+  # 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('openedproject'):
+        projectfile = self.settings.value('openedproject')
+        self.loadProject(projectfile)
+  
+  def closeEvent(self, ev):
+     self.settings.setValue('mainwindowstate', self.saveState())
+     self.settings.setValue('mainwindowgeometry', self.saveGeometry())
+     if self.project:
+        self.settings.setValue('openedproject', self.project.filename)
+        # TODO: ask for save of opened files
+     ev.accept()
+
+  # Error handling:
+  def nodeSelected(self, node):
+      ce = self.activeMdiChild()
+      if not ce:
+         return
+      if node.location:
+         row, col = node.location
+         ce.highlightErrorLocation( row, col )
+      else:
+         ce.clearErrors()
+
+  def errorSelected(self, err):
+     row, col, msg = err
+     ce = self.activeMdiChild()
+     if not ce:
+        return
+     ce.highlightErrorLocation(row, col)
+
+  # Project loading:
+  def loadProject(self, filename):
+     self.project = Project(filename)
+     self.projectview.setProject(self.project)
+
+  def openProject(self):
+     filename = QFileDialog.getOpenFileName(self, \
+       "Choose project file", "", "lcfos Project files (*.lcp)")
+     if filename:
+        self.loadProject(filename)
+
+  # Build recepy:
+  def buildFile(self):
+     """ Build project """
+     self.saveAll()
+     self.buildOutput.clear()
+     self.buildOutput.append(str(self.compiler))
+     mods = self.compiler.compileProject(self.project)
+
+     self.builderrors.setErrorList(self.compiler.errorlist)
+     self.astViewer.setAst(mods[0])
+     for err in self.compiler.errorlist:
+        self.buildOutput.append(str(err))
+     self.buildOutput.append("Done!")
+
+
+if __name__ == '__main__':
+   app = QApplication(sys.argv)
+   ide = Ide()
+   ide.compiler = Compiler()
+   ide.show()
+   app.exec_()
+
--- a/python/apps/ide/__init__.py	Sun Oct 07 17:04:10 2012 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,2 +0,0 @@
-# Package
-
--- a/python/apps/ide/astviewer.py	Sun Oct 07 17:04:10 2012 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,36 +0,0 @@
-from PyQt4.QtCore import *
-from PyQt4.QtGui import *
-
-def astToNamedElement(astNode, parentNode):
-   """ Helper to convert and AST tree to NamedElement tree: """
-   item = QStandardItem(str(astNode))
-   item.setData(astNode)
-   parentNode.appendRow(item)
-   for c in astNode.getChildren():
-      astToNamedElement(c, item)
-
-# The actual widget:
-class AstViewer(QTreeView):
-   sigNodeSelected = pyqtSignal(object)
-   def __init__(self, parent=None):
-      super(AstViewer, self).__init__(parent)
-      self.setHeaderHidden(True)
-      self.clicked.connect(self.selectHandler)
-
-   def setAst(self, ast):
-      """ Create a new model and add all ast elements to it """
-      model = QStandardItemModel()
-      if ast:
-         astToNamedElement(ast, model.invisibleRootItem())
-      self.setModel( model )
-      self.expandAll()
-
-   def selectHandler(self, index):
-      if not index.isValid():
-         return
-      model = self.model()
-      item = model.itemFromIndex(index)
-      node = item.data()
-      self.sigNodeSelected.emit(node)
-
-
--- a/python/apps/ide/codeeditor.py	Sun Oct 07 17:04:10 2012 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,140 +0,0 @@
-from PyQt4.QtCore import *
-from PyQt4.QtGui import *
-import compiler.lexer
-import os.path
-
-class MySyntaxHighlighter(QSyntaxHighlighter):
-   def __init__(self, parent=None):
-      super(MySyntaxHighlighter, self).__init__(parent)
-      # Syntax highlighting:
-      self.rules = []
-      fmt = QTextCharFormat()
-      fmt.setForeground(Qt.darkBlue)
-      fmt.setFontWeight(QFont.Bold)
-      for kw in compiler.lexer.keywords:
-         pattern = '\\b'+kw+'\\b'
-         self.rules.append( (pattern, fmt) )
-
-      # Comments:
-      fmt = QTextCharFormat()
-      fmt.setForeground(Qt.gray)
-      fmt.setFontItalic(True)
-      pattern = '\{.*\}'
-      self.rules.append( (pattern, fmt) )
-
-      # Procedure:
-      fmt = QTextCharFormat()
-      fmt.setForeground(Qt.blue)
-      fmt.setFontItalic(True)
-      #pattern = '(?<=procedure )[A-Za-z]'
-      # TODO lookbehind does not work, think something else
-      #self.rules.append( (pattern, fmt) )
-
-   def highlightBlock(self, text):
-      for pattern, fmt in self.rules:
-         expression = QRegExp(pattern)
-         index = expression.indexIn(text)
-         while index >= 0:
-            length = expression.matchedLength()
-            self.setFormat(index, length, fmt)
-            index = expression.indexIn(text, index + length)
-
-class LineNumberArea(QWidget):
-   def __init__(self, codeedit):
-      super(LineNumberArea, self).__init__(codeedit)
-      self.codeedit = codeedit
-      # TODO: display error in this: self.setToolTip('hello world')
-   def sizeHint(self):
-      return QSize(self.codeedit.lineNumberAreaWidth(), 0)
-   def paintEvent(self, ev):
-      self.codeedit.lineNumberAreaPaintEvent(ev)
-
-class CodeEdit(QPlainTextEdit):
-   def __init__(self, parent=None):
-      super(CodeEdit, self).__init__(parent)
-      # members:
-      self.isUntitled = True
-      self.filename = None
-      self.setFont(QFont('Courier'))
-      self.lineNumberArea = LineNumberArea(self)
-
-      self.blockCountChanged.connect(self.updateLineNumberAreaWidth)
-      self.updateRequest.connect(self.updateLineNumberArea)
-
-      # Syntax highlighter:
-      self.highlighter = MySyntaxHighlighter(self.document())
-
-   def setFileName(self, filename):
-      self.filename = filename
-      self.isUntitled = False
-      self.setWindowTitle(filename)
-   def setSource(self, source):
-      self.setPlainText(source)
-
-   def save(self):
-      pass
-   def saveAs(self):
-      pass
-
-   def saveFile(self):
-      if self.isUntitled:
-         self.saveAs()
-      else:
-         source = str(self.toPlainText())
-         f = open(self.filename, 'w')
-         f.write(source)
-         f.close()
-
-   def highlightErrorLocation(self, row, col):
-      tc = QTextCursor(self.document())
-      tc.clearSelection()
-      tc.movePosition(tc.Down, tc.MoveAnchor, row - 1)
-      tc.movePosition(tc.Right, tc.MoveAnchor, col - 1)
-      tc.movePosition(tc.NextCharacter, tc.KeepAnchor) # Select 1 character
-      selection = QTextEdit.ExtraSelection()
-      lineColor = QColor(Qt.red).lighter(160)
-      selection.format.setBackground(lineColor)
-      #selection.format.setProperty(QTextFormat.FullWidthSelection, True)
-      selection.cursor = tc
-      self.setExtraSelections( [ selection ] )
-   def clearErrors(self):
-      self.setExtraSelections( [  ] )
-
-   def lineNumberAreaWidth(self):
-      digits = 1
-      mx = max(1, self.blockCount())
-      while mx >= 10:
-         mx = mx / 10
-         digits += 1
-      space = 3 + self.fontMetrics().width('8') * digits
-      return space
-   def lineNumberAreaPaintEvent(self, ev):
-      painter = QPainter(self.lineNumberArea)
-      painter.fillRect(ev.rect(), Qt.lightGray)
-      block = self.firstVisibleBlock()
-      blockNumber = block.blockNumber()
-      top = self.blockBoundingGeometry(block).translated(self.contentOffset()).top()
-      bottom = top + self.blockBoundingRect(block).height()
-      while block.isValid() and top <= ev.rect().bottom():
-         if block.isVisible() and bottom >= ev.rect().top():
-            num = str(blockNumber + 1)
-            painter.setPen(Qt.black)
-            painter.drawText(0, top, self.lineNumberArea.width(), self.fontMetrics().height(), Qt.AlignRight, num)
-         block = block.next()
-         top = bottom
-         bottom = top + self.blockBoundingRect(block).height()
-         blockNumber += 1
-   def resizeEvent(self, ev):
-      super(CodeEdit, self).resizeEvent(ev)
-      cr = self.contentsRect()
-      self.lineNumberArea.setGeometry(QRect(cr.left(), cr.top(), self.lineNumberAreaWidth(), cr.height() ))
-   def updateLineNumberAreaWidth(self, newBlockCount):
-      self.setViewportMargins(self.lineNumberAreaWidth(), 0, 0, 0)
-   def updateLineNumberArea(self, rect, dy):
-      if dy > 0:
-         self.lineNumberArea.scroll(0, dy)
-      else:
-         self.lineNumberArea.update(0, rect.y(), self.lineNumberArea.width(), rect.height())
-      if rect.contains(self.viewport().rect()):
-         self.updateLineNumberAreaWidth(0)
-
--- a/python/apps/ide/ide.py	Sun Oct 07 17:04:10 2012 +0200
+++ b/python/apps/ide/ide.py	Sun Oct 07 17:13:47 2012 +0200
@@ -1,309 +1,1 @@
-from PyQt4.QtCore import *
-from PyQt4.QtGui import *
-# ide components:
-from .codeeditor import CodeEdit
-from .astviewer import AstViewer
-import base64
-from project import Project
-import os.path
 
-lcfospng = base64.decodestring(b'iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A\n/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9sJEhMKBk7B678AAAA/SURBVFjD\n7dbBCQAgDATBi9h/y7EFA4Kf2QLCwH1S6XQu6sqoujublc8BAAAAAAAAAAB8B+zXT6YJAAAAAKYd\nWSgFQNUyijIAAAAASUVORK5CYII=\n')
-
-class BuildOutput(QTextEdit):
-   """ Build output component """
-   def __init__(self, parent=None):
-      super(BuildOutput, self).__init__(parent)
-      self.setCurrentFont(QFont('Courier'))
-      self.setReadOnly(True)
-      self.append('Build output will appear here!')
-
-class BuildErrors(QListView):
-   sigErrorSelected = pyqtSignal(object)
-   def __init__(self, parent=None):
-      super(BuildErrors, self).__init__(parent)
-      model = QStandardItemModel()
-      self.setModel(model)
-      self.clicked.connect(self.itemSelected)
-   def setErrorList(self, errorlist):
-      model = QStandardItemModel()
-      for e in errorlist:
-         row, col, msg = e
-         item = QStandardItem(str(msg))
-         item.setData(e)
-         model.appendRow(item)
-      self.setModel(model)
-   def itemSelected(self, index):
-      if not index.isValid():
-         return
-      model = self.model()
-      item = model.itemFromIndex(index)
-      err = item.data()
-      self.sigErrorSelected.emit(err)
-
-class ProjectView(QWidget):
-   sigLoadFile = pyqtSignal(str)
-   def __init__(self, parent=None):
-      super(ProjectView, self).__init__(parent)
-      self.treeview = QTreeView(self)
-      self.treeview.setContextMenuPolicy(Qt.CustomContextMenu)
-      l = QVBoxLayout(self)
-      l.addWidget(self.treeview)
-      pm = QPixmap()
-      pm.loadFromData(lcfospng)
-      self.projectIcon = QIcon(pm)
-      # Connect signals:
-      self.treeview.activated.connect(self.activate)
-      self.treeview.customContextMenuRequested.connect(self.contextMenu)
-   def setProject(self, project):
-      self.project = project
-      model = QStandardItemModel()
-      root = model.invisibleRootItem()
-      pitem = QStandardItem(self.projectIcon, project.name)
-      pitem.setEditable(False)
-      pitem.setData(project)
-      root.appendRow(pitem)
-      for f in self.project.files:
-         fitem = QStandardItem(f)
-         pitem.appendRow(fitem)
-         fitem.setEditable(False)
-         fitem.setData(f)
-      self.treeview.setModel(model)
-      self.treeview.expandAll()
-   def contextMenu(self, pos):
-      idx = self.treeview.indexAt(pos)
-      if not idx.isValid():
-         return
-      item = self.treeview.model().itemFromIndex(idx)
-   def activate(self, index):
-      if not index.isValid():
-         return
-      model = self.treeview.model()
-      item = model.itemFromIndex(index)
-      fn = item.data()
-      if type(fn) is str:
-         self.sigLoadFile.emit(fn)
-
-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 ProjectOptions(QDialog):
-   pass
-   # TODO: project options in here
-
-class Ide(QMainWindow):
-  def __init__(self, parent=None):
-    super(Ide, self).__init__(parent)
-    self.setWindowTitle('LCFOS IDE')
-    icon = QPixmap()
-    icon.loadFromData(lcfospng)
-    self.setWindowIcon(QIcon(icon))
-
-    # Create menus:
-    self.fileMenu = self.menuBar().addMenu('File')
-    self.viewMenu = self.menuBar().addMenu('View')
-    self.projectMenu = self.menuBar().addMenu('Project')
-    self.helpMenu = self.menuBar().addMenu('Help')
-
-    # Create mdi area:
-    self.mdiArea = QMdiArea()
-    self.setCentralWidget(self.mdiArea)
-
-    # Create components:
-    self.buildOutput = BuildOutput()
-    self.addComponent('Build output', self.buildOutput)
-
-    self.astViewer = AstViewer()
-    self.addComponent('AST viewer', self.astViewer)
-    self.astViewer.sigNodeSelected.connect(self.nodeSelected)
-
-    self.builderrors = BuildErrors()
-    self.addComponent('Build errors', self.builderrors)
-    self.builderrors.sigErrorSelected.connect(self.errorSelected)
-
-    self.projectview = ProjectView()
-    self.addComponent('Project', self.projectview)
-    self.projectview.sigLoadFile.connect(self.loadFile)
-
-    # About dialog:
-    self.aboutDialog = AboutDialog()
-    self.aboutDialog.setWindowIcon(QIcon(icon))
-    # Create actions:
-    self.buildAction = QAction('Build!', self)
-    self.buildAction.setShortcut(QKeySequence('F7'))
-    self.projectMenu.addAction(self.buildAction)
-    self.buildAction.triggered.connect(self.buildFile)
-    self.openProjectAction = QAction("Open project", self)
-    self.openProjectAction.triggered.connect(self.openProject)
-    self.projectMenu.addAction(self.openProjectAction)
-    self.helpAction  = QAction('Help', self)
-    self.helpAction.setShortcut(QKeySequence('F1'))
-    self.helpMenu.addAction(self.helpAction)
-    self.aboutAction = QAction('About', self)
-    self.helpMenu.addAction(self.aboutAction)
-    self.aboutAction.triggered.connect(self.aboutDialog.open)
-
-    self.newFileAction = QAction("New File", self)
-    self.fileMenu.addAction(self.newFileAction)
-    self.newFileAction.triggered.connect(self.newFile)
-    self.saveFileAction = QAction("Save File", self)
-    self.fileMenu.addAction(self.saveFileAction)
-    self.saveFileAction.triggered.connect(self.saveFile)
-    self.closeFileAction = QAction("Close File", self)
-    self.fileMenu.addAction(self.closeFileAction)
-    self.closeFileAction.triggered.connect(self.closeFile)
-
-    cascadeAction = QAction("Cascade windows", self)
-    cascadeAction.triggered.connect(self.mdiArea.cascadeSubWindows)
-    self.viewMenu.addAction(cascadeAction)
-    tileAction = QAction('Tile windows', self)
-    tileAction.triggered.connect(self.mdiArea.tileSubWindows)
-    self.viewMenu.addAction(tileAction)
-
-    # Load settings:
-    self.settings = QSettings('windelsoft', 'lcfoside')
-    self.loadSettings()
-
-  def addComponent(self, name, widget):
-     dw = QDockWidget(name)
-     dw.setWidget(widget)
-     dw.setObjectName(name)
-     self.addDockWidget(Qt.RightDockWidgetArea, dw)
-     self.viewMenu.addAction(dw.toggleViewAction())
-
-  # File handling:
-  def newFile(self):
-     ce = CodeEdit()
-     w = self.mdiArea.addSubWindow(ce)
-     ce.show()
-
-  def saveFile(self):
-     ac = self.activeMdiChild()
-     if ac:
-        ac.saveFile()
-
-  def saveAll(self):
-     pass
-
-  def openFile(self):
-     # TODO
-     pass
-
-  def closeFile(self):
-     ac = self.activeMdiChild()
-     if ac:
-        self.mdiArea.removeSubWindow(ac)
-  
-  def loadFile(self, filename):
-     # Find existing mdi widget:
-     wid = self.findMdiChild(filename)
-     if wid:
-        self.mdiArea.setActiveSubWindow(wid.parent())
-        return wid
-
-     # Create a new one:
-     ce = CodeEdit()
-     source = self.project.loadProjectFile(filename)
-     ce.setSource(source)
-     self.mdiArea.addSubWindow(ce)
-     ce.show()
-     return ce
-
-  # MDI:
-  def activeMdiChild(self):
-     aw = self.mdiArea.activeSubWindow()
-     if aw:
-        return aw.widget()
-     else:
-        return None
-
-  def findMdiChild(self, filename):
-     for window in self.mdiArea.subWindowList():
-        wid = window.widget()
-        if wid.filename == filename:
-           return wid
-     return None
-  
-  def allChildren(self):
-     c = []
-     for window in self.mdiArea.subWindowList():
-        wid = window.widget()
-        c.append(wid)
-     return c
-
-  # 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('openedproject'):
-        projectfile = self.settings.value('openedproject')
-        self.loadProject(projectfile)
-  
-  def closeEvent(self, ev):
-     self.settings.setValue('mainwindowstate', self.saveState())
-     self.settings.setValue('mainwindowgeometry', self.saveGeometry())
-     if self.project:
-        self.settings.setValue('openedproject', self.project.filename)
-        # TODO: ask for save of opened files
-     ev.accept()
-
-  # Error handling:
-  def nodeSelected(self, node):
-      ce = self.activeMdiChild()
-      if not ce:
-         return
-      if node.location:
-         row, col = node.location
-         ce.highlightErrorLocation( row, col )
-      else:
-         ce.clearErrors()
-
-  def errorSelected(self, err):
-     row, col, msg = err
-     ce = self.activeMdiChild()
-     if not ce:
-        return
-     ce.highlightErrorLocation(row, col)
-
-  # Project loading:
-  def loadProject(self, filename):
-     self.project = Project(filename)
-     self.projectview.setProject(self.project)
-
-  def openProject(self):
-     filename = QFileDialog.getOpenFileName(self, \
-       "Choose project file", "", "lcfos Project files (*.lcp)")
-     if filename:
-        self.loadProject(filename)
-
-  # Build recepy:
-  def buildFile(self):
-     """ Build project """
-     self.saveAll()
-     self.buildOutput.clear()
-     self.buildOutput.append(str(self.compiler))
-     mods = self.compiler.compileProject(self.project)
-
-     self.builderrors.setErrorList(self.compiler.errorlist)
-     self.astViewer.setAst(mods[0])
-     for err in self.compiler.errorlist:
-        self.buildOutput.append(str(err))
-     self.buildOutput.append("Done!")
-
Binary file python/apps/lcfos.png has changed
--- a/python/apps/runbuild.py	Sun Oct 07 17:04:10 2012 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,16 +0,0 @@
-import sys
-
-# Compiler imports:
-from compiler.compiler import Compiler
-from project import Project
-
-if __name__ == '__main__':
-   if len(sys.argv) < 2:
-      print('Use {0} projectfile'.format(sys.argv[0]))
-      sys.exit(-1)
-   filename = sys.argv[1]
-   project = Project()
-   project.load(filename)
-   pc = Compiler()
-   pc.compileProject(project)
-
--- a/python/apps/runide.py	Sun Oct 07 17:04:10 2012 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,14 +0,0 @@
-import sys
-from PyQt4.QtGui import QApplication
-
-# Compiler imports:
-from compiler.compiler import Compiler
-from ide.ide import Ide
-
-if __name__ == '__main__':
-   app = QApplication(sys.argv)
-   ide = Ide()
-   ide.compiler = Compiler()
-   ide.show()
-   app.exec_()
-
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/python/libs/widgets/astviewer.py	Sun Oct 07 17:13:47 2012 +0200
@@ -0,0 +1,36 @@
+from PyQt4.QtCore import *
+from PyQt4.QtGui import *
+
+def astToNamedElement(astNode, parentNode):
+   """ Helper to convert and AST tree to NamedElement tree: """
+   item = QStandardItem(str(astNode))
+   item.setData(astNode)
+   parentNode.appendRow(item)
+   for c in astNode.getChildren():
+      astToNamedElement(c, item)
+
+# The actual widget:
+class AstViewer(QTreeView):
+   sigNodeSelected = pyqtSignal(object)
+   def __init__(self, parent=None):
+      super(AstViewer, self).__init__(parent)
+      self.setHeaderHidden(True)
+      self.clicked.connect(self.selectHandler)
+
+   def setAst(self, ast):
+      """ Create a new model and add all ast elements to it """
+      model = QStandardItemModel()
+      if ast:
+         astToNamedElement(ast, model.invisibleRootItem())
+      self.setModel( model )
+      self.expandAll()
+
+   def selectHandler(self, index):
+      if not index.isValid():
+         return
+      model = self.model()
+      item = model.itemFromIndex(index)
+      node = item.data()
+      self.sigNodeSelected.emit(node)
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/python/libs/widgets/codeeditor.py	Sun Oct 07 17:13:47 2012 +0200
@@ -0,0 +1,140 @@
+from PyQt4.QtCore import *
+from PyQt4.QtGui import *
+import compiler.lexer
+import os.path
+
+class MySyntaxHighlighter(QSyntaxHighlighter):
+   def __init__(self, parent=None):
+      super(MySyntaxHighlighter, self).__init__(parent)
+      # Syntax highlighting:
+      self.rules = []
+      fmt = QTextCharFormat()
+      fmt.setForeground(Qt.darkBlue)
+      fmt.setFontWeight(QFont.Bold)
+      for kw in compiler.lexer.keywords:
+         pattern = '\\b'+kw+'\\b'
+         self.rules.append( (pattern, fmt) )
+
+      # Comments:
+      fmt = QTextCharFormat()
+      fmt.setForeground(Qt.gray)
+      fmt.setFontItalic(True)
+      pattern = '\{.*\}'
+      self.rules.append( (pattern, fmt) )
+
+      # Procedure:
+      fmt = QTextCharFormat()
+      fmt.setForeground(Qt.blue)
+      fmt.setFontItalic(True)
+      #pattern = '(?<=procedure )[A-Za-z]'
+      # TODO lookbehind does not work, think something else
+      #self.rules.append( (pattern, fmt) )
+
+   def highlightBlock(self, text):
+      for pattern, fmt in self.rules:
+         expression = QRegExp(pattern)
+         index = expression.indexIn(text)
+         while index >= 0:
+            length = expression.matchedLength()
+            self.setFormat(index, length, fmt)
+            index = expression.indexIn(text, index + length)
+
+class LineNumberArea(QWidget):
+   def __init__(self, codeedit):
+      super(LineNumberArea, self).__init__(codeedit)
+      self.codeedit = codeedit
+      # TODO: display error in this: self.setToolTip('hello world')
+   def sizeHint(self):
+      return QSize(self.codeedit.lineNumberAreaWidth(), 0)
+   def paintEvent(self, ev):
+      self.codeedit.lineNumberAreaPaintEvent(ev)
+
+class CodeEdit(QPlainTextEdit):
+   def __init__(self, parent=None):
+      super(CodeEdit, self).__init__(parent)
+      # members:
+      self.isUntitled = True
+      self.filename = None
+      self.setFont(QFont('Courier'))
+      self.lineNumberArea = LineNumberArea(self)
+
+      self.blockCountChanged.connect(self.updateLineNumberAreaWidth)
+      self.updateRequest.connect(self.updateLineNumberArea)
+
+      # Syntax highlighter:
+      self.highlighter = MySyntaxHighlighter(self.document())
+
+   def setFileName(self, filename):
+      self.filename = filename
+      self.isUntitled = False
+      self.setWindowTitle(filename)
+   def setSource(self, source):
+      self.setPlainText(source)
+
+   def save(self):
+      pass
+   def saveAs(self):
+      pass
+
+   def saveFile(self):
+      if self.isUntitled:
+         self.saveAs()
+      else:
+         source = str(self.toPlainText())
+         f = open(self.filename, 'w')
+         f.write(source)
+         f.close()
+
+   def highlightErrorLocation(self, row, col):
+      tc = QTextCursor(self.document())
+      tc.clearSelection()
+      tc.movePosition(tc.Down, tc.MoveAnchor, row - 1)
+      tc.movePosition(tc.Right, tc.MoveAnchor, col - 1)
+      tc.movePosition(tc.NextCharacter, tc.KeepAnchor) # Select 1 character
+      selection = QTextEdit.ExtraSelection()
+      lineColor = QColor(Qt.red).lighter(160)
+      selection.format.setBackground(lineColor)
+      #selection.format.setProperty(QTextFormat.FullWidthSelection, True)
+      selection.cursor = tc
+      self.setExtraSelections( [ selection ] )
+   def clearErrors(self):
+      self.setExtraSelections( [  ] )
+
+   def lineNumberAreaWidth(self):
+      digits = 1
+      mx = max(1, self.blockCount())
+      while mx >= 10:
+         mx = mx / 10
+         digits += 1
+      space = 3 + self.fontMetrics().width('8') * digits
+      return space
+   def lineNumberAreaPaintEvent(self, ev):
+      painter = QPainter(self.lineNumberArea)
+      painter.fillRect(ev.rect(), Qt.lightGray)
+      block = self.firstVisibleBlock()
+      blockNumber = block.blockNumber()
+      top = self.blockBoundingGeometry(block).translated(self.contentOffset()).top()
+      bottom = top + self.blockBoundingRect(block).height()
+      while block.isValid() and top <= ev.rect().bottom():
+         if block.isVisible() and bottom >= ev.rect().top():
+            num = str(blockNumber + 1)
+            painter.setPen(Qt.black)
+            painter.drawText(0, top, self.lineNumberArea.width(), self.fontMetrics().height(), Qt.AlignRight, num)
+         block = block.next()
+         top = bottom
+         bottom = top + self.blockBoundingRect(block).height()
+         blockNumber += 1
+   def resizeEvent(self, ev):
+      super(CodeEdit, self).resizeEvent(ev)
+      cr = self.contentsRect()
+      self.lineNumberArea.setGeometry(QRect(cr.left(), cr.top(), self.lineNumberAreaWidth(), cr.height() ))
+   def updateLineNumberAreaWidth(self, newBlockCount):
+      self.setViewportMargins(self.lineNumberAreaWidth(), 0, 0, 0)
+   def updateLineNumberArea(self, rect, dy):
+      if dy > 0:
+         self.lineNumberArea.scroll(0, dy)
+      else:
+         self.lineNumberArea.update(0, rect.y(), self.lineNumberArea.width(), rect.height())
+      if rect.contains(self.viewport().rect()):
+         self.updateLineNumberAreaWidth(0)
+