view 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 source

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