diff options
Diffstat (limited to 'Lib')
| -rw-r--r-- | Lib/defconQt/__main__.py | 5 | ||||
| -rw-r--r-- | Lib/defconQt/baseCodeEditor.py | 159 | ||||
| -rw-r--r-- | Lib/defconQt/featureTextEditor.py | 230 | ||||
| -rw-r--r-- | Lib/defconQt/fontView.py | 31 | ||||
| -rw-r--r-- | Lib/defconQt/scriptingWindow.py | 94 | 
5 files changed, 337 insertions, 182 deletions
diff --git a/Lib/defconQt/__main__.py b/Lib/defconQt/__main__.py index ffcef50..b656bef 100644 --- a/Lib/defconQt/__main__.py +++ b/Lib/defconQt/__main__.py @@ -1,10 +1,9 @@  from defconQt.objects.defcon import TFont -from defconQt.fontView import MainWindow +from defconQt.fontView import Application, MainWindow  import sys  import os  from PyQt5.QtGui import QIcon -from PyQt5.QtWidgets import QApplication  if len(sys.argv) < 2:      share_dir = os.path.join(os.path.dirname(__file__), '..', '..', 'share') @@ -20,7 +19,7 @@ else:  from defconQt import representationFactories  representationFactories.registerAllFactories()  #with PyCallGraph(output=GraphvizOutput()): -app = QApplication(sys.argv) +app = Application(sys.argv)  # TODO: http://stackoverflow.com/a/21330349/2037879  app.setWindowIcon(QIcon("defconQt/resources/icon.png"))  window = MainWindow(TFont(os.path.abspath(ufoFile))) diff --git a/Lib/defconQt/baseCodeEditor.py b/Lib/defconQt/baseCodeEditor.py new file mode 100644 index 0000000..cab9ce9 --- /dev/null +++ b/Lib/defconQt/baseCodeEditor.py @@ -0,0 +1,159 @@ +from PyQt5.QtCore import QRegularExpression, Qt +from PyQt5.QtGui import QColor, QFont, QPainter, QSyntaxHighlighter, QTextCursor +from PyQt5.QtWidgets import QPlainTextEdit, QWidget + +# maybe add closeEvent + +class LineNumberArea(QWidget): +    def __init__(self, editor): +        super(LineNumberArea, self).__init__(editor) + +    def sizeHint(self): +        return QSize(self.parent().lineNumberAreaWidth(), 0) + +    def paintEvent(self, event): +        self.parent().lineNumberAreaPaintEvent(event) + +class CodeEditor(QPlainTextEdit): +    def __init__(self, text=None, parent=None): +        super(CodeEditor, self).__init__(parent) +        # https://gist.github.com/murphyrandle/2921575 +        font = QFont('Roboto Mono', 10) +        font.setFixedPitch(True) +        self.setFont(font) + +        self.indent = "    " +        self.openBlockDelimiter = None +        self.lineNumbers = LineNumberArea(self) +        self.setPlainText(text) +        # kick-in geometry update before arming signals bc blockCountChanged +        # won't initially trigger if text is None or one-liner. +        self.updateLineNumberAreaWidth() +        self.blockCountChanged.connect(self.updateLineNumberAreaWidth) +        self.updateRequest.connect(self.updateLineNumberArea) + +    def lineNumberAreaPaintEvent(self, event): +        painter = QPainter(self.lineNumbers) +        painter.fillRect(event.rect(), QColor(230, 230, 230)) +        d = event.rect().topRight() +        a = event.rect().bottomRight() +        painter.setPen(Qt.darkGray) +        painter.drawLine(d.x(), d.y(), a.x(), a.y()) +        painter.setPen(QColor(100, 100, 100)) +        painter.setFont(self.font()) + +        block = self.firstVisibleBlock() +        blockNumber = block.blockNumber() +        top = int(self.blockBoundingGeometry(block).translated(self.contentOffset()).top()) +        bottom = top + int(self.blockBoundingRect(block).height()) + +        while block.isValid() and top <= event.rect().bottom(): +            if block.isVisible() and bottom >= event.rect().top(): +                number = str(blockNumber + 1) +                painter.drawText(4, top, self.lineNumbers.width() - 8, +                    self.fontMetrics().height(), Qt.AlignRight, number) +            block = block.next() +            top = bottom +            bottom = top + int(self.blockBoundingRect(block).height()) +            blockNumber += 1 + +    def lineNumberAreaWidth(self): +        digits = 1 +        top = max(1, self.blockCount()) +        while top >= 10: +            top /= 10 +            digits += 1 +        # Avoid too frequent geometry changes +        if digits < 3: digits = 3 +        return 10 + self.fontMetrics().width('9') * digits + +    def updateLineNumberArea(self, rect, dy): +        if dy: +            self.lineNumbers.scroll(0, dy) +        else: +            self.lineNumbers.update(0, rect.y(), self.lineNumbers.width(), +                rect.height()) + +        if rect.contains(self.viewport().rect()): +            self.updateLineNumberAreaWidth(0) + +    def updateLineNumberAreaWidth(self, newBlockCount=None): +        self.setViewportMargins(self.lineNumberAreaWidth(), 0, 0, 0) + +    def resizeEvent(self, event): +        super(CodeEditor, self).resizeEvent(event) +        cr = self.contentsRect() +        self.lineNumbers.setGeometry(cr.left(), cr.top(), +            self.lineNumberAreaWidth(), cr.height()) + +    def findLineIndentLevel(self, cursor): +        indent = 0 +        cursor.select(QTextCursor.LineUnderCursor) +        lineLength = len(cursor.selectedText()) // len(self.indent) +        cursor.movePosition(QTextCursor.StartOfLine) +        while lineLength > 0: +            cursor.movePosition(QTextCursor.NextCharacter, +                QTextCursor.KeepAnchor, len(self.indent)) +            if cursor.selectedText() == self.indent: +                indent += 1 +            else: break +            # Now move the anchor back to the position() +            #cursor.movePosition(QTextCursor.NoMove) # shouldn't NoMove work here? +            cursor.setPosition(cursor.position()) +            lineLength -= 1 +        cursor.movePosition(QTextCursor.EndOfLine) +        return indent + +    def keyPressEvent(self, event): +        key = event.key() +        if key == Qt.Key_Return: +            cursor = self.textCursor() +            indentLvl = self.findLineIndentLevel(cursor) +            if self.openBlockDelimiter is not None: +                cursor.movePosition(QTextCursor.PreviousCharacter, QTextCursor.KeepAnchor) +                if cursor.selectedText() == self.openBlockDelimiter: +                    indentLvl += 1 +            super(CodeEditor, self).keyPressEvent(event) +            newLineSpace = "".join(self.indent for _ in range(indentLvl)) +            cursor = self.textCursor() +            cursor.insertText(newLineSpace) +        elif key == Qt.Key_Tab: +            cursor = self.textCursor() +            cursor.insertText(self.indent) +        elif key == Qt.Key_Backspace: +            cursor = self.textCursor() +            cursor.movePosition(QTextCursor.PreviousCharacter, QTextCursor.KeepAnchor, +                  len(self.indent)) +            if cursor.selectedText() == self.indent: +                cursor.removeSelectedText() +            else: +                super(CodeEditor, self).keyPressEvent(event) +        else: +            super(CodeEditor, self).keyPressEvent(event) + +    def wheelEvent(self, event): +        if event.modifiers() & Qt.ControlModifier: +            font = self.font() +            newPointSize = font.pointSize() + event.angleDelta().y() / 120.0 +            event.accept() +            if newPointSize < 6: return +            font.setPointSize(newPointSize) +            self.setFont(font) +        else: +            super(CodeEditor, self).wheelEvent(event) + +class CodeHighlighter(QSyntaxHighlighter): +    def __init__(self, parent=None): +        super(CodeHighlighter, self).__init__(parent) +        self.highlightingRules = [] + +    def highlightBlock(self, text): +        for pattern, fmt in self.highlightingRules: +            regex = QRegularExpression(pattern) +            i = regex.globalMatch(text) +            while i.hasNext(): +                match = i.next() +                start = match.capturedStart() +                length = match.capturedLength() +                self.setFormat(start, length, fmt) +        self.setCurrentBlockState(0) diff --git a/Lib/defconQt/featureTextEditor.py b/Lib/defconQt/featureTextEditor.py index 5ee9b02..5c5de76 100644 --- a/Lib/defconQt/featureTextEditor.py +++ b/Lib/defconQt/featureTextEditor.py @@ -1,7 +1,9 @@ -from PyQt5.QtCore import QFile, QRegExp, Qt -from PyQt5.QtGui import QColor, QFont, QKeySequence, QPainter, QSyntaxHighlighter, QTextCharFormat, QTextCursor +from defconQt.baseCodeEditor import CodeEditor, CodeHighlighter +from PyQt5.QtCore import QFile, Qt +from PyQt5.QtGui import (QColor, QFont, QKeySequence, QPainter, QTextCharFormat, +    QTextCursor)  from PyQt5.QtWidgets import (QApplication, QFileDialog, QMainWindow, QMenu, -        QMessageBox, QPlainTextEdit, QWidget) +    QMessageBox, QPlainTextEdit, QWidget)  # TODO: implement search and replace  class MainEditWindow(QMainWindow): @@ -9,14 +11,20 @@ class MainEditWindow(QMainWindow):          super(MainEditWindow, self).__init__(parent)          self.font = font -        self.setupFileMenu() -        self.editor = TextEditor(self.font.features.text, self) +        self.editor = FeatureTextEditor(self.font.features.text, self)          self.resize(600, 500) +        fileMenu = QMenu("&File", self) +        fileMenu.addAction("&Save...", self.save, QKeySequence.Save) +        fileMenu.addSeparator() +        fileMenu.addAction("Reload from UFO", self.reload) +        fileMenu.addAction("E&xit", self.close, QKeySequence.Quit) +        self.menuBar().addMenu(fileMenu) +          self.setCentralWidget(self.editor)          self.setWindowTitle("Font features", self.font)          # now arm `undoAvailable` to `setWindowModified` -        self.editor.setFileChangedCallback(self.setWindowModified) +        self.editor.undoAvailable.connect(self.setWindowModified)      def setWindowTitle(self, title, font):          if font is not None: puts = "[*]%s – %s %s" % (title, self.font.info.familyName, self.font.info.styleName) @@ -45,126 +53,15 @@ class MainEditWindow(QMainWindow):      def save(self):          self.editor.write(self.font.features) -    def setupFileMenu(self): -        fileMenu = QMenu("&File", self) -        self.menuBar().addMenu(fileMenu) - -        fileMenu.addAction("&Save...", self.save, QKeySequence.Save) -        fileMenu.addSeparator() -        fileMenu.addAction("Reload from UFO", self.reload) -        fileMenu.addAction("E&xit", self.close, QKeySequence.Quit) - -class LineNumberArea(QWidget): -    def __init__(self, editor): -        super(LineNumberArea, self).__init__(editor) - -    def sizeHint(self): -        return QSize(self.parent().lineNumberAreaWidth(), 0) - -    def paintEvent(self, event): -        self.parent().lineNumberAreaPaintEvent(event) - -class TextEditor(QPlainTextEdit): +class FeatureTextEditor(CodeEditor):      def __init__(self, text=None, parent=None): -        super(TextEditor, self).__init__(parent) -        # https://gist.github.com/murphyrandle/2921575 -        font = QFont('Roboto Mono', 10) -        font.setFixedPitch(True) -        self.setFont(font) - -        self._indent = "    " -        self.highlighter = Highlighter(self.document()) -        self.lineNumbers = LineNumberArea(self) -        self.setPlainText(text) -        # kick-in geometry update before arming signals bc blockCountChanged -        # won't initially trigger if text is None or one-liner. -        self.updateLineNumberAreaWidth() -        self.blockCountChanged.connect(self.updateLineNumberAreaWidth) -        self.updateRequest.connect(self.updateLineNumberArea) - -    def setFontParams(self, family='Roboto Mono', ptSize=10, isMono=True): -        font = QFont(family, ptSize) -        font.setFixedPitch(isMono) -        self.setFont(font) - -    # TODO: add way to unset callback? -    def setFileChangedCallback(self, fileChangedCallback): -        self.undoAvailable.connect(fileChangedCallback) +        super(FeatureTextEditor, self).__init__(text, parent) +        self.openBlockDelimiter = '{' +        self.highlighter = FeatureTextHighlighter(self.document())      def write(self, features):          features.text = self.toPlainText() -    def lineNumberAreaPaintEvent(self, event): -        painter = QPainter(self.lineNumbers) -        painter.fillRect(event.rect(), QColor(230, 230, 230)) -        d = event.rect().topRight() -        a = event.rect().bottomRight() -        painter.setPen(Qt.darkGray) -        painter.drawLine(d.x(), d.y(), a.x(), a.y()) -        painter.setPen(QColor(100, 100, 100)) -        painter.setFont(self.font()) - -        block = self.firstVisibleBlock() -        blockNumber = block.blockNumber(); -        top = int(self.blockBoundingGeometry(block).translated(self.contentOffset()).top()) -        bottom = top + int(self.blockBoundingRect(block).height()) - -        while block.isValid() and top <= event.rect().bottom(): -            if block.isVisible() and bottom >= event.rect().top(): -                number = str(blockNumber + 1) -                painter.drawText(4, top, self.lineNumbers.width() - 8, -                    self.fontMetrics().height(), -                    Qt.AlignRight, number) -            block = block.next() -            top = bottom -            bottom = top + int(self.blockBoundingRect(block).height()) -            blockNumber += 1 - -    def lineNumberAreaWidth(self): -        digits = 1 -        top = max(1, self.blockCount()) -        while top >= 10: -            top /= 10 -            digits += 1 -        # Avoid too frequent geometry changes -        if digits < 3: digits = 3 -        return 10 + self.fontMetrics().width('9') * digits - -    def updateLineNumberArea(self, rect, dy): -        if dy: -            self.lineNumbers.scroll(0, dy); -        else: -            self.lineNumbers.update(0, rect.y(), -                self.lineNumbers.width(), rect.height()) - -        if rect.contains(self.viewport().rect()): -            self.updateLineNumberAreaWidth(0) - -    def updateLineNumberAreaWidth(self, newBlockCount=None): -        self.setViewportMargins(self.lineNumberAreaWidth(), 0, 0, 0) - -    def resizeEvent(self, event): -        super(TextEditor, self).resizeEvent(event) -        cr = self.contentsRect() -        self.lineNumbers.setGeometry(cr.left(), cr.top(), self.lineNumberAreaWidth(), cr.height()) - -    def findLineIndentLevel(self, cursor): -        indent = 0 -        cursor.select(QTextCursor.LineUnderCursor) -        lineLength = len(cursor.selectedText()) // len(self._indent) -        cursor.movePosition(QTextCursor.StartOfLine) -        while lineLength > 0: -            cursor.movePosition(QTextCursor.NextCharacter, QTextCursor.KeepAnchor, len(self._indent)) -            if cursor.selectedText() == self._indent: -                indent += 1 -            else: break -            # Now move the anchor back to the position() -            #cursor.movePosition(QTextCursor.NoMove) # shouldn't NoMove work here? -            cursor.setPosition(cursor.position()) -            lineLength -= 1 -        cursor.movePosition(QTextCursor.EndOfLine) -        return indent -      def keyPressEvent(self, event):          key = event.key()          if key == Qt.Key_Return: @@ -173,7 +70,7 @@ class TextEditor(QPlainTextEdit):              newBlock = False              cursor.movePosition(QTextCursor.PreviousCharacter, QTextCursor.KeepAnchor) -            if cursor.selectedText() == '{': +            if cursor.selectedText() == self.openBlockDelimiter:                  # We don't add a closing tag if there is text right below with the same                  # indentation level because in that case the user might just be looking                  # to add a new line @@ -191,7 +88,7 @@ class TextEditor(QPlainTextEdit):              if newBlock:                  txt = cursor.selectedText().lstrip(" ").split(" ")                  if len(txt) > 1: -                    if len(txt) < 3 and txt[-1][-1] == '{': +                    if len(txt) < 3 and txt[-1][-1] == self.openBlockDelimiter:                          feature = txt[-1][:-1]                      else:                          feature = txt[1] @@ -199,74 +96,49 @@ class TextEditor(QPlainTextEdit):                      feature = None              cursor.movePosition(QTextCursor.EndOfLine) -            super(TextEditor, self).keyPressEvent(event) -            newLineSpace = "".join(self._indent for _ in range(indentLvl)) +            cursor.insertText("\n") +            newLineSpace = "".join(self.indent for _ in range(indentLvl))              cursor.insertText(newLineSpace)              if newBlock: -                super(TextEditor, self).keyPressEvent(event) -                newLineSpace = "".join((newLineSpace[:-len(self._indent)], "} ", feature, ";")) +                cursor.insertText("\n") +                newLineSpace = "".join((newLineSpace[:-len(self.indent)], "} ", feature, ";"))                  cursor.insertText(newLineSpace)                  cursor.movePosition(QTextCursor.Up)                  cursor.movePosition(QTextCursor.EndOfLine)                  self.setTextCursor(cursor) -        elif key == Qt.Key_Tab: -            cursor = self.textCursor() -            cursor.insertText(self._indent) -        elif key == Qt.Key_Backspace: -            cursor = self.textCursor() -            cursor.movePosition(QTextCursor.PreviousCharacter, QTextCursor.KeepAnchor, -                  len(self._indent)) -            if cursor.selectedText() == self._indent: -                cursor.removeSelectedText() -            else: -                super(TextEditor, self).keyPressEvent(event)          else: -            super(TextEditor, self).keyPressEvent(event) - -keywordPatterns = ["\\bAscender\\b", "\\bAttach\\b", "\\bCapHeight\\b", "\\bCaretOffset\\b", "\\bCodePageRange\\b", -    "\\bDescender\\b", "\\bFontRevision\\b", "\\bGlyphClassDef\\b", "\\bHorizAxis.BaseScriptList\\b", -    "\\bHorizAxis.BaseTagList\\b", "\\bHorizAxis.MinMax\\b", "\\bIgnoreBaseGlyphs\\b", "\\bIgnoreLigatures\\b", -    "\\bIgnoreMarks\\b", "\\bLigatureCaretByDev\\b", "\\bLigatureCaretByIndex\\b", "\\bLigatureCaretByPos\\b", -    "\\bLineGap\\b", "\\bMarkAttachClass\\b", "\\bMarkAttachmentType\\b", "\\bNULL\\b", "\\bPanose\\b", "\\bRightToLeft\\b", -    "\\bTypoAscender\\b", "\\bTypoDescender\\b", "\\bTypoLineGap\\b", "\\bUnicodeRange\\b", "\\bUseMarkFilteringSet\\b", -    "\\bVendor\\b", "\\bVertAdvanceY\\b", "\\bVertAxis.BaseScriptList\\b", "\\bVertAxis.BaseTagList\\b", -    "\\bVertAxis.MinMax\\b", "\\bVertOriginY\\b", "\\bVertTypoAscender\\b", "\\bVertTypoDescender\\b", -    "\\bVertTypoLineGap\\b", "\\bXHeight\\b", "\\banchorDef\\b", "\\banchor\\b", "\\banonymous\\b", "\\banon\\b", -    "\\bby\\b", "\\bcontour\\b", "\\bcursive\\b", "\\bdevice\\b", "\\benumerate\\b", "\\benum\\b", "\\bexclude_dflt\\b", -    "\\bfeatureNames\\b", "\\bfeature\\b", "\\bfrom\\b", "\\bignore\\b", "\\binclude_dflt\\b", "\\binclude\\b", -    "\\blanguagesystem\\b", "\\blanguage\\b", "\\blookupflag\\b", "\\blookup\\b", "\\bmarkClass\\b", "\\bmark\\b", -    "\\bnameid\\b", "\\bname\\b", "\\bparameters\\b", "\\bposition\\b", "\\bpos\\b", "\\brequired\\b", "\\breversesub\\b", -    "\\brsub\\b", "\\bscript\\b", "\\bsizemenuname\\b", "\\bsubstitute\\b", "\\bsubtable\\b", "\\bsub\\b", "\\btable\\b", -    "\\buseExtension\\b", "\\bvalueRecordDef\\b", "\\bwinAscent\\b", "\\bwinDescent\\b"] - -class Highlighter(QSyntaxHighlighter): +            super(FeatureTextEditor, self).keyPressEvent(event) + +keywordPatterns = ["Ascender", "Attach", "CapHeight", "CaretOffset", "CodePageRange", +    "Descender", "FontRevision", "GlyphClassDef", "HorizAxis.BaseScriptList", +    "HorizAxis.BaseTagList", "HorizAxis.MinMax", "IgnoreBaseGlyphs", "IgnoreLigatures", +    "IgnoreMarks", "LigatureCaretByDev", "LigatureCaretByIndex", "LigatureCaretByPos", +    "LineGap", "MarkAttachClass", "MarkAttachmentType", "NULL", "Panose", "RightToLeft", +    "TypoAscender", "TypoDescender", "TypoLineGap", "UnicodeRange", "UseMarkFilteringSet", +    "Vendor", "VertAdvanceY", "VertAxis.BaseScriptList", "VertAxis.BaseTagList", +    "VertAxis.MinMax", "VertOriginY", "VertTypoAscender", "VertTypoDescender", +    "VertTypoLineGap", "XHeight", "anchorDef", "anchor", "anonymous", "anon", +    "by", "contour", "cursive", "device", "enumerate", "enum", "exclude_dflt", +    "featureNames", "feature", "from", "ignore", "include_dflt", "include", +    "languagesystem", "language", "lookupflag", "lookup", "markClass", "mark", +    "nameid", "name", "parameters", "position", "pos", "required", "reversesub", +    "rsub", "script", "sizemenuname", "substitute", "subtable", "sub", "table", +    "useExtension", "valueRecordDef", "winAscent", "winDescent"] + +class FeatureTextHighlighter(CodeHighlighter):      def __init__(self, parent=None): -        super(Highlighter, self).__init__(parent) +        super(FeatureTextHighlighter, self).__init__(parent)          keywordFormat = QTextCharFormat() -        keywordFormat.setForeground(QColor(30, 150, 220)) +        keywordFormat.setForeground(QColor(34, 34, 34))          keywordFormat.setFontWeight(QFont.Bold) - -        self.highlightingRules = [(QRegExp("(%s)" % ("|".join(keywordPatterns))), keywordFormat)] +        self.highlightingRules.append(("\\b(%s)\\b" % ("|".join(keywordPatterns)), keywordFormat))          singleLineCommentFormat = QTextCharFormat()          singleLineCommentFormat.setForeground(Qt.darkGray) -        self.highlightingRules.append((QRegExp("#[^\n]*"), -                singleLineCommentFormat)) - -        classFormat = QTextCharFormat() -        classFormat.setFontWeight(QFont.Bold) -        classFormat.setForeground(QColor(200, 50, 150)) -        self.highlightingRules.append((QRegExp("@[A-Za-z0-9_.]+"), -                classFormat)) - -    def highlightBlock(self, text): -        for pattern, format in self.highlightingRules: -            expression = QRegExp(pattern) -            index = expression.indexIn(text) -            while index >= 0: -                length = expression.matchedLength() -                self.setFormat(index, length, format) -                index = expression.indexIn(text, index + length) +        self.highlightingRules.append(("#[^\n]*", singleLineCommentFormat)) -        self.setCurrentBlockState(0) +        groupFormat = QTextCharFormat() +        groupFormat.setFontWeight(QFont.Bold) +        groupFormat.setForeground(QColor(96, 106, 161)) +        self.highlightingRules.append(("@[A-Za-z0-9_.]+", groupFormat)) diff --git a/Lib/defconQt/fontView.py b/Lib/defconQt/fontView.py index 0ec8910..9921cdb 100644 --- a/Lib/defconQt/fontView.py +++ b/Lib/defconQt/fontView.py @@ -3,6 +3,7 @@ from defconQt.fontInfo import TabDialog  from defconQt.glyphCollectionView import GlyphCollectionWidget  from defconQt.glyphView import MainGfxWindow  from defconQt.groupsView import GroupsWindow +from defconQt.scriptingWindow import MainScriptingWindow  from defconQt.objects.defcon import CharacterSet, TFont, TGlyph  from defcon import Component  from defconQt.spaceCenter import MainSpaceWindow @@ -45,6 +46,17 @@ latin1 = CharacterSet(  "breve","dotaccent","ring","ogonek","tilde","hungarumlaut","quoteleft",  "quoteright","minus"], "Latin-1") +class Application(QApplication): +    def allFonts(self): +        fonts = [] +        for window in QApplication.topLevelWidgets(): +            if isinstance(window, MainWindow): +                fonts.append(window._font) +                return fonts + +    def currentFont(self): +        return self.currentMainWindow._font +  # TODO: implement Frederik's Glyph Construction Builder  class AddGlyphDialog(QDialog):      def __init__(self, currentGlyphs=None, parent=None): @@ -311,6 +323,10 @@ class MainWindow(QMainWindow):          fontMenu.addAction("Sort…", self.sortCharacters)          menuBar.addMenu(fontMenu) +        pythonMenu = QMenu("&Python", self) +        pythonMenu.addAction("Scripting &window", self.scripting) +        menuBar.addMenu(pythonMenu) +          windowMenu = QMenu("&Windows", self)          windowMenu.addAction("&Space center", self.spaceCenter, "Ctrl+Y")          windowMenu.addAction("&Groups window", self.fontGroups, "Ctrl+G") @@ -566,6 +582,12 @@ class MainWindow(QMainWindow):          self.collectionWidget._sizeEvent(self.width(), val)          QToolTip.showText(QCursor.pos(), str(val), self) +    def event(self, event): +        if event.type() == QEvent.WindowActivate: +            app = QApplication.instance() +            app.currentMainWindow = self +        return super(MainWindow, self).event(event) +      def resizeEvent(self, event):          if self.isVisible(): self.collectionWidget._sizeEvent(event.size().width())          super(MainWindow, self).resizeEvent(event) @@ -619,6 +641,15 @@ class MainWindow(QMainWindow):          else:              self.fontGroupsWindow.raise_() +    def scripting(self): +        # TODO: see up here +        app = QApplication.instance() +        if not (hasattr(app, 'scriptingWindow') and app.scriptingWindow.isVisible()): +            app.scriptingWindow = MainScriptingWindow() +            app.scriptingWindow.show() +        else: +            app.scriptingWindow.raise_() +      def sortCharacters(self):          sortDescriptor, ok = SortDialog.getDescriptor(self, self.sortDescriptor)          if ok: diff --git a/Lib/defconQt/scriptingWindow.py b/Lib/defconQt/scriptingWindow.py new file mode 100644 index 0000000..2448d48 --- /dev/null +++ b/Lib/defconQt/scriptingWindow.py @@ -0,0 +1,94 @@ +from defconQt.baseCodeEditor import CodeEditor, CodeHighlighter +from keyword import kwlist +import traceback +from PyQt5.QtCore import Qt +from PyQt5.QtGui import QColor, QFont, QKeySequence, QTextCharFormat, QTextCursor +from PyQt5.QtWidgets import QApplication, QMainWindow, QMenu, QPlainTextEdit + +class MainScriptingWindow(QMainWindow): +    def __init__(self): +        super(MainScriptingWindow, self).__init__() + +        self.editor = PythonEditor(parent=self) +        self.resize(600, 500) + +        fileMenu = QMenu("&File", self) +        fileMenu.addAction("&Run…", self.runScript, "Ctrl+R") +        fileMenu.addSeparator() +        fileMenu.addAction("E&xit", self.close, QKeySequence.Quit) +        self.menuBar().addMenu(fileMenu) + +        self.setCentralWidget(self.editor) +        self.setWindowTitle("[*]Untitled.py") +        # arm `undoAvailable` to `setWindowModified` +        self.editor.undoAvailable.connect(self.setWindowModified) + +    def runScript(self): +        app = QApplication.instance() +        script = self.editor.toPlainText() +        global_vars = { +            "__builtins__": __builtins__, +            "AllFonts": app.allFonts, +            "CurrentFont": app.currentFont, +        } +        try: +            code = compile(script, "<string>", "exec") +            exec(code, global_vars) +        except: +            print(traceback.format_exc()) + +class PythonEditor(CodeEditor): +    autocomplete = { +        Qt.Key_ParenLeft: "()", +        Qt.Key_BracketLeft: "[]", +        Qt.Key_BraceLeft: "{}", +        Qt.Key_Apostrophe: "''", +        Qt.Key_QuoteDbl: '""', +    } + +    def __init__(self, text=None, parent=None): +        super(PythonEditor, self).__init__(text, parent) +        self.openBlockDelimiter = ":" +        self.highlighter = PythonHighlighter(self.document()) + +    def keyPressEvent(self, event): +        key = event.key() +        if key in self.autocomplete.keys(): +            super(PythonEditor, self).keyPressEvent(event) +            cursor = self.textCursor() +            cursor.insertText(self.autocomplete[key][-1]) +            cursor.movePosition(QTextCursor.PreviousCharacter) +            self.setTextCursor(cursor) +            event.accept() +            return +        elif key == Qt.Key_Backspace: +            cursor = self.textCursor() +            ok = cursor.movePosition(QTextCursor.PreviousCharacter) +            if ok: +                ok = cursor.movePosition(QTextCursor.NextCharacter, QTextCursor.KeepAnchor, 2) +                if ok and cursor.selectedText() in self.autocomplete.values(): +                    cursor.removeSelectedText() +                    event.accept() +                    return +        super(PythonEditor, self).keyPressEvent(event) + +class PythonHighlighter(CodeHighlighter): +    def __init__(self, parent=None): +        super(PythonHighlighter, self).__init__(parent) + +        keywordFormat = QTextCharFormat() +        keywordFormat.setForeground(QColor(34, 34, 34)) +        keywordFormat.setFontWeight(QFont.Bold) +        self.highlightingRules.append(("\\b(%s)\\b" % ("|".join(kwlist)), keywordFormat)) + +        singleLineCommentFormat = QTextCharFormat() +        singleLineCommentFormat.setForeground(Qt.darkGray) +        self.highlightingRules.append(("#[^\n]*", singleLineCommentFormat)) + +        classOrFnNameFormat = QTextCharFormat() +        classOrFnNameFormat.setForeground(QColor(96, 106, 161)) +        self.highlightingRules.append(("(?<=\\bclass\\s|def\\s\\b)\\s*(\\w+)", classOrFnNameFormat)) + +        quotationFormat = QTextCharFormat() +        quotationFormat.setForeground(QColor(223, 17, 68)) +        self.highlightingRules.append(("'.*'|[\"]{1,3}.*[\"]{1,3}", quotationFormat))  | 
