From f7fe2201b80d52eff8358e0e92e8d75b06372fe0 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 30 Jan 2008 22:56:48 +0000 Subject: [PATCH] Syntax highlighting in advanced profile edit view --- src/libprs500/gui2/dialogs/user_profiles.py | 4 + src/libprs500/gui2/widgets.py | 250 +++++++++++++++++++- 2 files changed, 251 insertions(+), 3 deletions(-) diff --git a/src/libprs500/gui2/dialogs/user_profiles.py b/src/libprs500/gui2/dialogs/user_profiles.py index 4a6eeedf6e..c8a5610940 100644 --- a/src/libprs500/gui2/dialogs/user_profiles.py +++ b/src/libprs500/gui2/dialogs/user_profiles.py @@ -20,6 +20,7 @@ from PyQt4.QtGui import QDialog, QMessageBox from libprs500.ebooks.lrf.web.profiles import FullContentProfile, create_class from libprs500.gui2.dialogs.user_profiles_ui import Ui_Dialog from libprs500.gui2 import qstring_to_unicode, error_dialog, question_dialog +from libprs500.gui2.widgets import PythonHighlighter class UserProfiles(QDialog, Ui_Dialog): @@ -58,6 +59,7 @@ class UserProfiles(QDialog, Ui_Dialog): self.toggle_mode_button.setText('Switch to Advanced mode') else: self.source_code.setPlainText(src) + self.highlighter = PythonHighlighter(self.source_code.document()) self.stacks.setCurrentIndex(1) self.toggle_mode_button.setText('Switch to Basic mode') @@ -71,6 +73,7 @@ class UserProfiles(QDialog, Ui_Dialog): if not qstring_to_unicode(self.source_code.toPlainText()).strip(): src = self.options_to_profile()[0] self.source_code.setPlainText(src.replace('BasicUserProfile', 'AdvancedUserProfile')) + self.highlighter = PythonHighlighter(self.source_code.document()) def add_feed(self, *args): @@ -121,6 +124,7 @@ class %(classname)s(%(base_class)s): def populate_source_code(self): src = self.options_to_profile().replace('BasicUserProfile', 'AdvancedUserProfile') self.source_code.setPlainText(src) + self.highlighter = PythonHighlighter(self.source_code.document()) def add_profile(self, clicked): if self.stacks.currentIndex() == 0: diff --git a/src/libprs500/gui2/widgets.py b/src/libprs500/gui2/widgets.py index a75b5279d8..8652a2cf65 100644 --- a/src/libprs500/gui2/widgets.py +++ b/src/libprs500/gui2/widgets.py @@ -16,10 +16,13 @@ ''' Miscellanous widgets used in the GUI ''' -from PyQt4.QtGui import QListView, QIcon, QFont, QLabel, QListWidget, QListWidgetItem -from PyQt4.QtCore import QAbstractListModel, QVariant, Qt, QSize, SIGNAL, QObject +from PyQt4.QtGui import QListView, QIcon, QFont, QLabel, QListWidget, \ + QListWidgetItem, QTextCharFormat, QApplication, \ + QSyntaxHighlighter, QCursor, QColor +from PyQt4.QtCore import QAbstractListModel, QVariant, Qt, QSize, SIGNAL, \ + QObject, QRegExp, QSettings -from libprs500.gui2.jobs import ConversionJob, DetailView +from libprs500.gui2.jobs import DetailView from libprs500.gui2 import human_readable, NONE, TableView from libprs500 import fit_image, get_font_families @@ -176,3 +179,244 @@ class BasicList(QListWidget): def items(self): for i in range(self.count()): yield self.item(i) + + + +class PythonHighlighter(QSyntaxHighlighter): + + Rules = [] + Formats = {} + Config = {} + + KEYWORDS = ["and", "as", "assert", "break", "class", "continue", "def", + "del", "elif", "else", "except", "exec", "finally", "for", "from", + "global", "if", "import", "in", "is", "lambda", "not", "or", + "pass", "print", "raise", "return", "try", "while", "with", + "yield"] + + BUILTINS = ["abs", "all", "any", "basestring", "bool", "callable", "chr", + "classmethod", "cmp", "compile", "complex", "delattr", "dict", + "dir", "divmod", "enumerate", "eval", "execfile", "exit", "file", + "filter", "float", "frozenset", "getattr", "globals", "hasattr", + "hex", "id", "int", "isinstance", "issubclass", "iter", "len", + "list", "locals", "long", "map", "max", "min", "object", "oct", + "open", "ord", "pow", "property", "range", "reduce", "repr", + "reversed", "round", "set", "setattr", "slice", "sorted", + "staticmethod", "str", "sum", "super", "tuple", "type", "unichr", + "unicode", "vars", "xrange", "zip"] + + CONSTANTS = ["False", "True", "None", "NotImplemented", "Ellipsis"] + + + def __init__(self, parent=None): + super(PythonHighlighter, self).__init__(parent) + if not self.Config: + self.loadConfig() + + + self.initializeFormats() + + PythonHighlighter.Rules.append((QRegExp( + "|".join([r"\b%s\b" % keyword for keyword in self.KEYWORDS])), + "keyword")) + PythonHighlighter.Rules.append((QRegExp( + "|".join([r"\b%s\b" % builtin for builtin in self.BUILTINS])), + "builtin")) + PythonHighlighter.Rules.append((QRegExp( + "|".join([r"\b%s\b" % constant \ + for constant in self.CONSTANTS])), "constant")) + PythonHighlighter.Rules.append((QRegExp( + r"\b[+-]?[0-9]+[lL]?\b" + r"|\b[+-]?0[xX][0-9A-Fa-f]+[lL]?\b" + r"|\b[+-]?[0-9]+(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?\b"), + "number")) + PythonHighlighter.Rules.append((QRegExp( + r"\bPyQt4\b|\bQt?[A-Z][a-z]\w+\b"), "pyqt")) + PythonHighlighter.Rules.append((QRegExp(r"\b@\w+\b"), "decorator")) + stringRe = QRegExp(r"""(?:'[^']*'|"[^"]*")""") + stringRe.setMinimal(True) + PythonHighlighter.Rules.append((stringRe, "string")) + self.stringRe = QRegExp(r"""(:?"["]".*"["]"|'''.*''')""") + self.stringRe.setMinimal(True) + PythonHighlighter.Rules.append((self.stringRe, "string")) + self.tripleSingleRe = QRegExp(r"""'''(?!")""") + self.tripleDoubleRe = QRegExp(r'''"""(?!')''') + + @classmethod + def loadConfig(cls): + Config = cls.Config + def setDefaultString(name, default): + value = settings.value(name).toString() + if value.isEmpty(): + value = default + Config[name] = value + + settings = QSettings() + for name in ("window", "shell"): + Config["%swidth" % name] = settings.value("%swidth" % name, + QVariant(QApplication.desktop() \ + .availableGeometry().width() / 2)).toInt()[0] + Config["%sheight" % name] = settings.value("%sheight" % name, + QVariant(QApplication.desktop() \ + .availableGeometry().height() / 2)).toInt()[0] + Config["%sy" % name] = settings.value("%sy" % name, + QVariant(0)).toInt()[0] + Config["toolbars"] = settings.value("toolbars").toByteArray() + Config["splitter"] = settings.value("splitter").toByteArray() + Config["shellx"] = settings.value("shellx", QVariant(0)).toInt()[0] + Config["windowx"] = settings.value("windowx", QVariant(QApplication \ + .desktop().availableGeometry().width() / 2)).toInt()[0] + Config["remembergeometry"] = settings.value("remembergeometry", + QVariant(True)).toBool() + Config["startwithshell"] = settings.value("startwithshell", + QVariant(True)).toBool() + Config["showwindowinfo"] = settings.value("showwindowinfo", + QVariant(True)).toBool() + setDefaultString("shellstartup", """\ + from __future__ import division + import codecs + import sys + sys.stdin = codecs.getreader("UTF8")(sys.stdin) + sys.stdout = codecs.getwriter("UTF8")(sys.stdout)""") + setDefaultString("newfile", """\ + #!/usr/bin/env python + + from __future__ import division + + import sys + """) + Config["backupsuffix"] = settings.value("backupsuffix", + QVariant(".bak")).toString() + setDefaultString("beforeinput", "#>>>") + setDefaultString("beforeoutput", "#---") + Config["cwd"] = settings.value("cwd", QVariant(".")).toString() + Config["tooltipsize"] = settings.value("tooltipsize", + QVariant(150)).toInt()[0] + Config["maxlinestoscan"] = settings.value("maxlinestoscan", + QVariant(5000)).toInt()[0] + Config["pythondocpath"] = settings.value("pythondocpath", + QVariant("http://docs.python.org")).toString() + Config["autohidefinddialog"] = settings.value("autohidefinddialog", + QVariant(True)).toBool() + Config["findcasesensitive"] = settings.value("findcasesensitive", + QVariant(False)).toBool() + Config["findwholewords"] = settings.value("findwholewords", + QVariant(False)).toBool() + Config["tabwidth"] = settings.value("tabwidth", + QVariant(4)).toInt()[0] + Config["fontfamily"] = settings.value("fontfamily", + QVariant("Bitstream Vera Sans Mono")).toString() + Config["fontsize"] = settings.value("fontsize", + QVariant(10)).toInt()[0] + for name, color, bold, italic in ( + ("normal", "#000000", False, False), + ("keyword", "#000080", True, False), + ("builtin", "#0000A0", False, False), + ("constant", "#0000C0", False, False), + ("decorator", "#0000E0", False, False), + ("comment", "#007F00", False, True), + ("string", "#808000", False, False), + ("number", "#924900", False, False), + ("error", "#FF0000", False, False), + ("pyqt", "#50621A", False, False)): + Config["%sfontcolor" % name] = settings.value( + "%sfontcolor" % name, QVariant(color)).toString() + Config["%sfontbold" % name] = settings.value( + "%sfontbold" % name, QVariant(bold)).toBool() + Config["%sfontitalic" % name] = settings.value( + "%sfontitalic" % name, QVariant(italic)).toBool() + + + @classmethod + def initializeFormats(cls): + Config = cls.Config + baseFormat = QTextCharFormat() + baseFormat.setFontFamily(Config["fontfamily"]) + baseFormat.setFontPointSize(Config["fontsize"]) + for name in ("normal", "keyword", "builtin", "constant", + "decorator", "comment", "string", "number", "error", + "pyqt"): + format = QTextCharFormat(baseFormat) + format.setForeground(QColor(Config["%sfontcolor" % name])) + if Config["%sfontbold" % name]: + format.setFontWeight(QFont.Bold) + format.setFontItalic(Config["%sfontitalic" % name]) + PythonHighlighter.Formats[name] = format + + + def highlightBlock(self, text): + NORMAL, TRIPLESINGLE, TRIPLEDOUBLE, ERROR = range(4) + + textLength = text.length() + prevState = self.previousBlockState() + + self.setFormat(0, textLength, + PythonHighlighter.Formats["normal"]) + + if text.startsWith("Traceback") or text.startsWith("Error: "): + self.setCurrentBlockState(ERROR) + self.setFormat(0, textLength, + PythonHighlighter.Formats["error"]) + return + if prevState == ERROR and \ + not (text.startsWith('>>>') or text.startsWith("#")): + self.setCurrentBlockState(ERROR) + self.setFormat(0, textLength, + PythonHighlighter.Formats["error"]) + return + + for regex, format in PythonHighlighter.Rules: + i = text.indexOf(regex) + while i >= 0: + length = regex.matchedLength() + self.setFormat(i, length, + PythonHighlighter.Formats[format]) + i = text.indexOf(regex, i + length) + + # Slow but good quality highlighting for comments. For more + # speed, comment this out and add the following to __init__: + # PythonHighlighter.Rules.append((QRegExp(r"#.*"), "comment")) + if text.isEmpty(): + pass + elif text[0] == "#": + self.setFormat(0, text.length(), + PythonHighlighter.Formats["comment"]) + else: + stack = [] + for i, c in enumerate(text): + if c in ('"', "'"): + if stack and stack[-1] == c: + stack.pop() + else: + stack.append(c) + elif c == "#" and len(stack) == 0: + self.setFormat(i, text.length(), + PythonHighlighter.Formats["comment"]) + break + + self.setCurrentBlockState(NORMAL) + + if text.indexOf(self.stringRe) != -1: + return + # This is fooled by triple quotes inside single quoted strings + for i, state in ((text.indexOf(self.tripleSingleRe), + TRIPLESINGLE), + (text.indexOf(self.tripleDoubleRe), + TRIPLEDOUBLE)): + if self.previousBlockState() == state: + if i == -1: + i = text.length() + self.setCurrentBlockState(state) + self.setFormat(0, i + 3, + PythonHighlighter.Formats["string"]) + elif i > -1: + self.setCurrentBlockState(state) + self.setFormat(i, text.length(), + PythonHighlighter.Formats["string"]) + + + def rehighlight(self): + QApplication.setOverrideCursor(QCursor(Qt.WaitCursor)) + QSyntaxHighlighter.rehighlight(self) + QApplication.restoreOverrideCursor() +