Subversion Repositories Scribus

Rev

Rev 17560 | Blame | Compare with Previous | Last modification | View Log | RSS feed

# -*- coding: utf-8 -*-
import re
import sys
import os

# I put the rope package into a ZIP-file to save space
# and to keep everything clear
path = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, os.path.join(path, "rope.zip"))


from rope.base.project import get_no_project
from rope.contrib.codeassist import code_assist

from PyQt4.QtCore import QCoreApplication, QLine, Qt, SIGNAL
from PyQt4.QtGui import (QTextCursor, QBrush, QFont, QPainter, QVBoxLayout,
                         QApplication, QKeyEvent, QTextBlockUserData, QPen, QPlainTextEdit,
                         QHBoxLayout, QPalette, QColor, QFrame, QWidget, QMessageBox)

from indenter import PythonCodeIndenter
from assist import AutoComplete, CallTip

from highlighter import PythonHighlighter,  QtScriptHighlighter









class EditorBlockData(QTextBlockUserData):
   
    def __init__(self):
        QTextBlockUserData.__init__(self)






class RopeEditorWrapper(object):


    def __init__(self, editview):
        self.editview = editview


    def length(self):
        return self.editview.length()


    def line_editor(self):
        return self


    def _get_block(self, line_no=None):
        cursor = self.editview.textCursor()
        row = cursor.blockNumber()
        if line_no is None:
            line_no = row
        block = cursor.block()
        while row > line_no:
            block = block.previous()
            row -= 1
        while row < line_no:
            block = block.next()
            row += 1
        return block


    def get_line(self, line_no=None):
        return unicode(self._get_block(line_no).text())


    def indent_line(self, line_no, indent_length):
        block = self._get_block(line_no)
        cursor = QTextCursor(block)
        cursor.joinPreviousEditBlock()
        cursor.movePosition(QTextCursor.StartOfBlock, QTextCursor.MoveAnchor)
        if indent_length <  0:
            for i in range(-indent_length):
                cursor.deleteChar()
        else:
            cursor.insertText(" " * indent_length)
        if indent_length:
            cursor.movePosition(
                QTextCursor.StartOfBlock, QTextCursor.MoveAnchor)
            line = unicode(cursor.block().text())
            if len(line) and line[0] == " ":
                cursor.movePosition(
                    QTextCursor.NextWord, QTextCursor.MoveAnchor)
            self.editview.setTextCursor(cursor)
        cursor.endEditBlock()




class EditorView(QPlainTextEdit):


    def __init__(self, parent=None, text=None,
                 EditorHighlighterClass=PythonHighlighter,
                 indenter=PythonCodeIndenter):
        QPlainTextEdit.__init__(self, parent)
        self.setFrameStyle(QFrame.NoFrame)
        self.setTabStopWidth(4)
        self.setLineWrapMode(QPlainTextEdit.NoWrap)
        font = QFont()
        font.setFamily("lucidasanstypewriter")
        font.setFixedPitch(True)
        font.setPointSize(10)
        self.setFont(font)
        self.highlighter = EditorHighlighterClass(self)
        if text:
            self.setPlainText(text)
        self.frame_style = self.frameStyle()
        self.draw_line = True
        self.print_width = self.fontMetrics().width("x"*78)
        self.line_pen = QPen(QColor("lightgrey"))
        self.last_row = self.last_col = -1
        self.last_block = None
        self.highlight_line = True
        self.highlight_color = self.palette().highlight().color().light(175)
        self.highlight_brush = QBrush(QColor(self.highlight_color))
        self.connect(self, SIGNAL("cursorPositionChanged()"),
                        self.onCursorPositionChanged)
        self.indenter = indenter(RopeEditorWrapper(self))
        # True if you want to catch Emacs keys in actions
        self.disable_shortcuts = False

        self.prj = get_no_project()
        self.prj.root = None
        self.calltip = CallTip(self)
        self.autocomplete = AutoComplete(self)


    def closeEvent(self, event):
        self.calltip.close()
        self.autocomplete.close()


    def isModified(self):
        return self.document().isModified()


    def setModified(self, flag):
        self.document().setModified(flag)


    def length(self):
        return self.document().blockCount()


    def goto(self, line_no):
        cursor = self.textCursor()
        block = cursor.block()
        row = cursor.blockNumber()
        while row > line_no:
            block = block.previous()
            row -= 1
        while row < line_no:
            block = block.next()
            row += 1
        cursor = QTextCursor(block)
        self.setTextCursor(cursor)


    def move_start_of_doc(self):
        cursor = self.textCursor()
        cursor.setPosition(0)
        self.setTextCursor(cursor)


    def move_end_of_doc(self):
        cursor = self.textCursor()
        block = cursor.block()
        while block.isValid():
            last_block = block
            block = block.next()
        cursor.setPosition(last_block.position())
        cursor.movePosition(
                QTextCursor.EndOfBlock, QTextCursor.MoveAnchor)
        self.setTextCursor(cursor)


    def move_start_of_row(self):
        cursor = self.textCursor()
        cursor.movePosition(
                QTextCursor.StartOfBlock, QTextCursor.MoveAnchor)
        self.setTextCursor(cursor)


    def move_end_of_row(self):
        cursor = self.textCursor()
        cursor.movePosition(
                QTextCursor.EndOfBlock, QTextCursor.MoveAnchor)
        self.setTextCursor(cursor)


    def highline(self, cursor):
        self.viewport().update()


    def onCursorPositionChanged(self):
        cursor = self.textCursor()
        row, col = cursor.blockNumber(), cursor.columnNumber()
        if self.last_row != row:
            self.last_row = row
            if self.highlight_line:
                self.highline(cursor)
        if col != self.last_col:
            self.last_col = col
        self.emit(SIGNAL("cursorPositionChanged(int,int)"), row, col)


    def _create_line(self):
        x = self.print_width
        self.line = QLine(x, 0, x, self.height())

   
    def resizeEvent(self, event):
        self._create_line()
        QPlainTextEdit.resizeEvent(self, event)


    def paintEvent(self, event):
        painter = QPainter(self.viewport())
        if self.highlight_line:
            r = self.cursorRect()
            r.setX(0)
            r.setWidth(self.viewport().width())
            painter.fillRect(r, self.highlight_brush)
        if self.draw_line:
            painter.setPen(self.line_pen)
            painter.drawLine(self.line)
        painter.end()
        QPlainTextEdit.paintEvent(self, event)


    def setDocument(self, document):
        QPlainTextEdit.setDocument(self, document)
        self.highlighter.setDocument(document)


    def indent(self):
        self.indenter.correct_indentation(self.textCursor().blockNumber())


    def tab_pressed(self):
        self.indent()


    def dedent(self):
        self.indenter.deindent(self.textCursor().blockNumber())


    def backtab_pressed(self):
        self.dedent()
        return True


    def backspace_pressed(self):
        cursor = self.textCursor()
        text = unicode(cursor.block().text())
        col = cursor.columnNumber()
        if col > 0 and text[:col].strip() == "":
            self.indenter.deindent(self.textCursor().blockNumber())
            return True


    def autocomplete_pressed(self):
        try:
            items = code_assist(self.prj,
                                unicode(self.toPlainText()),
                                self.textCursor().position())
        except Exception,  e:
            items = []
        if items:
            self.autocomplete.setItems(items)
            self.autocomplete.show()
       

    def after_return_pressed(self):
        self.indenter.entering_new_line(self.textCursor().blockNumber())


    def keyPressEvent(self, event):
        if self.autocomplete.active:
            if self.autocomplete.keyPressEvent(event):
                return
        elif self.calltip.active:
            if self.calltip.keyPressEvent(event):
                return

        m = event.modifiers()
        k = event.key()
        t = event.text()
        # Disable some shortcuts
        if self.disable_shortcuts and \
                m & Qt.ControlModifier and k in [Qt.Key_A, Qt.Key_R,
                                                 Qt.Key_C, Qt.Key_K,
                                                 Qt.Key_X, Qt.Key_V,
                                                 Qt.Key_Y, Qt.Key_Z]:
            new_ev = QKeyEvent(event.type(), k, m, t)
            event.ignore()
            QCoreApplication.postEvent(self.parent(), new_ev)
            return
        elif k == Qt.Key_Tab:
            if self.tab_pressed():
                return
        elif k == Qt.Key_Backtab:
            if self.backtab_pressed():
                return
        elif k == Qt.Key_Backspace:
            if self.backspace_pressed():
                return
        elif k == Qt.Key_Period or \
              (k == Qt.Key_Space and event.modifiers() == Qt.ControlModifier):
            QPlainTextEdit.keyPressEvent(self, event)
            self.autocomplete_pressed()
            return
        elif k in [Qt.Key_ParenLeft, Qt.Key_BraceLeft, Qt.Key_BracketLeft]:
            QPlainTextEdit.keyPressEvent(self, event)
            self.paren_opened(k)
            return
        QPlainTextEdit.keyPressEvent(self, event)
        if k == Qt.Key_Return or k == Qt.Key_Enter:
            self.after_return_pressed()



    def paren_opened(self, key):
        close_char = {
            Qt.Key_ParenLeft: ")",
            Qt.Key_BraceLeft:" }",
            Qt.Key_BracketLeft:"]"
            }
        cursor = self.textCursor()
        cursor.insertText(close_char[key])
        cursor.setPosition(cursor.position()-1)
        self.setTextCursor(cursor)



class EditorSidebar(QWidget):


    def __init__(self, editor):
        QWidget.__init__(self, editor)
        self.editor = editor
        self.view = editor.view
        self.doc = editor.view.document
        self.fm = self.fontMetrics()
        self.show_line_numbers = True

        self.setAutoFillBackground(True)
        #bg = editor.view.palette().base().color()
        #pal = QPalette()
        #pal.setColor(self.backgroundRole(), bg)
        #self.setPalette(pal)
        self.setBackgroundRole(QPalette.Base)

        self.connect(self.doc().documentLayout(),
                     SIGNAL("update(const QRectF &)"),
                     self.update)
        self.connect(self.view.verticalScrollBar(),
                     SIGNAL("valueChanged(int)"),
                     self.update)
        self.first_row = self.last_row = self.rows = 0
        width = 10
        if self.show_line_numbers:
            width += self.fm.width("00000")
        self.setFixedWidth(width)



    def paintEvent(self, event):
        QWidget.paintEvent(self, event)
        p = QPainter(self)
        view = self.view
        first = view.firstVisibleBlock()
        first_row = first.blockNumber()
        block = first
        row = first_row
        y = view.contentOffset().y()
        pageBottom = max(
          view.height(),
          view.verticalScrollBar().value() + view.viewport().height())
        fm = self.fm
        w  = self.width() - 8
        while block.isValid():
            txt = str(row).rjust(5)
            y = view.blockBoundingGeometry(block).y()
            if y >= pageBottom:
                break
            x = w - fm.width(txt)
            p.drawText(x, y, txt)
            row += 1
            block = block.next()
        p.end()



class EditorWidget(QFrame):


    def __init__(self, parent=None, text=None,
                    EditorSidebarClass=EditorSidebar,
                    EditorViewClass=EditorView):
        QFrame.__init__(self, parent)
        self.view = EditorViewClass(self, text)
        self.sidebar = EditorSidebarClass(self)
        self.setFrameStyle(QFrame.StyledPanel | QFrame.Sunken)
        self.setLineWidth(2)
        self.vlayout = QVBoxLayout()
        self.vlayout.setSpacing(0)
        self.setLayout(self.vlayout)
        self.hlayout = QHBoxLayout()
        self.vlayout.addLayout(self.hlayout)
        self.hlayout.addWidget(self.sidebar)
        self.hlayout.addWidget(self.view)
        self.vlayout.setContentsMargins(2, 2, 2, 2)


    def setPlainText(self, text):
        self.view.document().setPlainText(text)
        self.view.setModified(False)

    def isModified(self):
        return self.view.document().isModified()

    def toPlainText(self):
        return unicode(self.view.document().toPlainText())

    def setModified(self, flag):
        self.view.document().setModified(flag)

class PythonEditorWidget(EditorWidget):
    pass

class QtScriptEditorWidget(QPlainTextEdit):
   
    def __init__(self,  parent):
        QPlainTextEdit.__init__(self,  parent)
        self.highlighter = QtScriptHighlighter(self)

class SaveDialog(QMessageBox):

    def __init__(self, msg):
        QMessageBox.__init__(self)
        self.setWindowTitle("Save")
        self.setText(msg)
        self.setStandardButtons(QMessageBox.Save |QMessageBox.Discard | QMessageBox.Cancel)
        self.setDefaultButton(QMessageBox.Save)


if __name__ == "__main__":
    if __file__ == "<stdin>": __file__ = "./widget.py"
    import sys
    app = QApplication(sys.argv)
    src = open(__file__).read()
    edit = EditorWidget(text=src)
    edit.resize(640,480)
    edit.show()
    app.exec_()