diff python/ide.py @ 95:4a37d6992bd3

movage
author windel
date Mon, 24 Dec 2012 13:24:59 +0100
parents python/apps/ide.py@60cc36ef5a50
children 3f772feb12ef
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/python/ide.py	Mon Dec 24 13:24:59 2012 +0100
@@ -0,0 +1,299 @@
+import sys, os, base64
+if sys.version_info.major != 3:
+   print("Needs to be run in python version 3.x")
+   sys.exit(1)
+
+from PyQt4.QtCore import *
+from PyQt4.QtGui import *
+
+# Compiler imports:
+sys.path.insert(0, os.path.join('..','libs'))
+from project import Project
+from compiler import Compiler
+from widgets import CodeEdit, AstViewer
+
+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 el in self.project.elements:
+         fitem = QStandardItem(el)
+         pitem.appendRow(fitem)
+         fitem.setEditable(False)
+         fitem.setData(el)
+      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')
+    self.compiler = Compiler()
+    icon = QPixmap()
+    icon.loadFromData(lcfospng)
+    self.setWindowIcon(QIcon(icon))
+
+    # Create menus:
+    mb = self.menuBar()
+    self.projectMenu = mb.addMenu('Project')
+    self.viewMenu = mb.addMenu('View')
+    self.helpMenu = mb.addMenu('Help')
+
+    # Create mdi area:
+    self.mdiArea = QMdiArea()
+    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(self.nodeSelected)
+    self.builderrors = addComponent('Build errors', BuildErrors())
+    self.builderrors.sigErrorSelected.connect(self.errorSelected)
+    self.projectview = addComponent('Project explorer', ProjectView())
+    self.projectview.sigLoadFile.connect(self.loadFile)
+
+    # About dialog:
+    self.aboutDialog = AboutDialog()
+    self.aboutDialog.setWindowIcon(QIcon(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.projectMenu, self.newProject)
+    addMenuEntry("Open", self.projectMenu, self.openProject)
+    addMenuEntry("Save", self.projectMenu, self.saveProject)
+    addMenuEntry("Close", self.projectMenu, self.closeProject)
+    addMenuEntry("Build", self.projectMenu, self.buildProject, shortcut=QKeySequence('F7'))
+
+    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)
+
+    # Load settings:
+    self.settings = QSettings('windelsoft', 'lcfoside')
+    self.loadSettings()
+
+  # File handling:
+  def newProject(self):
+     filename = QFileDialog.getSaveFileName(self, \
+       "Select new projectfile", "", "lcfos Project files (*.lcp2)")
+     if filename:
+        self.project = Project()
+        self.project.filename = filename
+        self.project.save()
+
+  def saveProject(self):
+     self.project.save()
+
+  def closeProject(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
+
+  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 (*.lcp2)")
+     if filename:
+        self.loadProject(filename)
+
+  # 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:
+
+  # Build recepy:
+  def buildProject(self):
+     """ Build project """
+     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.show()
+   app.exec_()
+