diff options
| -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)) |
