#!/usr/bin/env python from __future__ import division from __future__ import with_statement import base64 import os import re import sys from PyQt4.QtCore import * from PyQt4.QtGui import * __version__ = "0.9.0" __license__ = """\ Copyright (c) 2007 Mark Summerfield. All rights reserved. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but without any warranty; without even the implied warranty of merchantability or fitness for a particular purpose. See the GNU General Public License for more details.""" MAC = "qt_mac_set_native_menubar" in dir() 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"] MODULES = dict([(key, False) for key in ("array", "bisect", "bsddb", "bz2", "codecs", "collections", "copy", "datetime", "decimal", "functools", "getopt", "gzip", "heapq", "itertools", "math", "optparse", "os", "os.path", "pickle", "cPickle", "random", "re", "string", "StringIO", "cStringIO", "struct", "subprocess", "sys", "tarfile", "time", "unicodedata", "weakref", "zipfile", "QAxContainer", "QtAssistant", "QtCore", "QtDesigner", "QtGui", "QtNetwork", "QtOpenGL", "QtSql", "QtSvg", "QtTest", "QtXml")]) TIMEOUT = 5000 ICONS = {} PIXMAPS = {} Config = {} CAT = {} # Completions And Tooltips MIN_COMPLETION_LEN = 3 MAX_TOOLTIP_LEN = 1000 FROM_IMPORT_RE = re.compile(r"from\s+([\w.]+)\s+import\s+(.*)") WORDS = set() WORD_RE = re.compile(r"[\W+.]") MIN_WORD_LEN = 3 MAX_WORD_LEN = 64 CATABLE_LINE_RE = QRegExp(r"\b(?:import|def|class)\s+") CLASS_OR_DEF_RE = re.compile(r"(class|def) ([^\W(:]+)[:(]") def trimQString(qstr, trimText): while qstr.startsWith(trimText): qstr = qstr.mid(len(trimText)) while qstr.endsWith(trimText): qstr.chop(len(trimText)) return qstr def loadConfig(): 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["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() def saveConfig(): settings = QSettings() for key, value in Config.items(): settings.setValue(key, QVariant(value)) def checkForCAT(line): # We can't use CAT to keep track of which modules we have processed. # This is because if the user does "import os", one of the names we # will get is "os.path", which then goes into CAT; so if later the # user did "import os.path", the name "os.path" will already be in # CAT (from the "import os") and wouldn't be looked for. So we keep # a separate dictionary of modules that we've done in MODULES. line = line.strip() if line.startswith("import "): line = line[len("import "):] for mname in [mname.strip() for mname in line.split(",")]: if not MODULES.get(mname, False): # We have not done this one _updateCAT(mname) else: match = FROM_IMPORT_RE.match(line) if match: if match.group(1) == "__future__" or not match.group(2): return for name in [x for x in \ match.group(2).replace(",", " ").split()]: mname = "%s.%s" % (match.group(1), name) if not MODULES.get(mname, False): # We have not done this one _updateCAT(mname) i = mname.rfind(".") while i > -1: mname = mname[:i] if not MODULES.get(mname, False): # We have not done this one _updateCAT(mname) i = mname.rfind(".") else: for word in WORD_RE.split(line): if not word or \ len(word) < MIN_WORD_LEN or \ len(word) > MAX_WORD_LEN or \ word in CAT or word in WORDS: continue WORDS.add(word) def _updateCAT(mname): try: MODULES[mname] = True CAT.setdefault(mname, {}) WORDS.discard(mname) module = __import__(mname) components = [] if "." in mname: for component in mname.split(".")[1:]: module = getattr(module, component) components.append(component) for aname in dir(module): if aname.startswith("_"): continue fullname = "%s.%s" % (mname, aname) CAT.setdefault(fullname, "") WORDS.discard(fullname) CAT.setdefault(aname, "") WORDS.discard(aname) for component in components: CAT.setdefault("%s.%s" % (component, aname), "") WORDS.discard("%s.%s" % (component, aname)) # PyQt4 classes don't have docstrings if not mname.startswith(("Qt", "PyQt4")): try: tip = eval("sys.modules['%s'].%s.__doc__" % ( mname, aname)) if tip is not None: tip = tip[:MAX_TOOLTIP_LEN].strip() \ .replace("\n\n", "

") CAT[fullname] = tip if CAT[aname] == "": # First one (i.e., builtin) CAT[aname] = tip # wins for component in components: # Last one wins CAT["%s.%s" % (component, aname)] = tip except: pass try: for bname in eval("dir(sys.modules['%s'].%s)" % ( mname, aname)): if bname.startswith("_"): continue cname = "%s.%s" % (fullname, bname) CAT.setdefault(cname, "") WORDS.discard(cname) # PyQt4 classes don't have docstrings if not mname.startswith(("Qt", "PyQt4")): tip = None try: tip = eval( "sys.modules['%s'].%s.%s.__doc__" % ( mname, aname, bname)) except: pass if tip is None: try: tip = eval( "sys.modules['%s'].%s.__doc__" % ( mname, aname)) except: pass if tip is not None: tip = tip[:MAX_TOOLTIP_LEN].strip() \ .replace("\n\n", "

") CAT[cname] = tip except: pass except: pass class HelpForm(QDialog): def __init__(self, parent=None): super(HelpForm, self).__init__(parent) self.setAttribute(Qt.WA_GroupLeader) self.setAttribute(Qt.WA_DeleteOnClose) browser = QTextBrowser() browser.setOpenExternalLinks(True) browser.setHtml( # TODO Improve u"""

This online help provides a brief introduction to Sandbox's completion and Find and Replace dialog, followed by some useful online links, and finishing with a summary of its key bindings. It is well worth pulling down the menus and seeing what options are available since they are almost all self-explanatory.

Word completion is achieved using Ctrl+P or by pressing Tab. A tooltip will appear when you enter (, if one is available. Completion and tooltips are available for Python's built-ins, and also for any module that gets imported, whether as part of the start-up (see the File\N{RIGHTWARDS ARROW}Configure dialog's At Startup tab), or that are imported in the Shell, or that are mentioned in import statements in any files that are opened. (This makes loading a file with lots of imports, or writing an import command in the shell, take a tiny bit longer than it would otherwise do.) The completion also tries to include your own identifiers. Also, classes, methods, and functions are added to the Goto menu. The number of lines that are scanned for completions, tooltips, and for the Goto menu is specified in the File\N{RIGHTWARDS ARROW}Configure dialog's General tab's Max Lines to Scan spinbox.

When you click Find or Replace in the Find and Replace dialog, the action will take place and the dialog will close. You can then do further finds and replaces using Edit\N{RIGHTWARDS ARROW}Find Again or Edit\N{RIGHTWARDS ARROW}Replace Again (or using the keyboard shortcuts shown in the menu). If you prefer to have the Find and Replace dialog visible and use its buttons instead, go to the File\N{RIGHTWARDS ARROW}Configure dialog's General tab, and uncheck Automatically Hide the Find and Replace dialog.

Some useful online documentation links:

Key bindings:

Hold down Shift when pressing movement keys to select the text moved over.
Press Esc to close this window. """ % (Config["tabwidth"], Config["tabwidth"])) layout = QVBoxLayout() layout.setMargin(0) layout.addWidget(browser) self.setLayout(layout) self.resize(500, 500) QShortcut(QKeySequence("Escape"), self, self.close) self.setWindowTitle("Sandbox - Help") class FindForm(QDialog): def __init__(self, parent=None): super(FindForm, self).__init__(parent) self.findEdit = QComboBox() self.findEdit.setDuplicatesEnabled(False) self.findEdit.setEditable(True) findLabel = QLabel("Find wha&t:") findLabel.setBuddy(self.findEdit) self.replaceEdit = QComboBox() self.replaceEdit.setDuplicatesEnabled(False) self.replaceEdit.setEditable(True) replaceLabel = QLabel("Replace w&ith:") replaceLabel.setBuddy(self.replaceEdit) self.caseSensitiveCheckBox = QCheckBox("&Case sensitive") self.caseSensitiveCheckBox.setChecked(Config["findcasesensitive"]) self.wholeWordsCheckBox = QCheckBox("&Whole words only") self.wholeWordsCheckBox.setChecked(Config["findwholewords"]) buttonBox = QDialogButtonBox() self.findButton = buttonBox.addButton("&Find", QDialogButtonBox.ActionRole) self.replaceButton = buttonBox.addButton("&Replace", QDialogButtonBox.ActionRole) buttonBox.addButton(QDialogButtonBox.Close) if not MAC: self.caseSensitiveCheckBox.setFocusPolicy(Qt.NoFocus) self.wholeWordsCheckBox.setFocusPolicy(Qt.NoFocus) buttonBox.setFocusPolicy(Qt.NoFocus) grid = QGridLayout() grid.addWidget(findLabel, 0, 0) grid.addWidget(self.findEdit, 0, 1) grid.addWidget(replaceLabel, 1, 0) grid.addWidget(self.replaceEdit, 1, 1) layout = QVBoxLayout() layout.addLayout(grid) hbox = QHBoxLayout() hbox.addWidget(self.caseSensitiveCheckBox) hbox.addWidget(self.wholeWordsCheckBox) hbox.addStretch() layout.addLayout(hbox) layout.addWidget(buttonBox) self.setLayout(layout) self.connect(self.findEdit, SIGNAL("editTextChanged(QString)"), self.updateUi) self.connect(self.findButton, SIGNAL("clicked()"), self.findClicked) self.connect(self.replaceButton, SIGNAL("clicked()"), self.replaceClicked) self.updateUi() self.setWindowTitle("Sandbox - Find and Replace") def findClicked(self): self.emit(SIGNAL("find"), self.findEdit.currentText(), self.caseSensitiveCheckBox.isChecked(), self.wholeWordsCheckBox.isChecked()) if Config["autohidefinddialog"]: self.hide() def replaceClicked(self): self.emit(SIGNAL("replace"), self.findEdit.currentText(), self.replaceEdit.currentText(), self.caseSensitiveCheckBox.isChecked(), self.wholeWordsCheckBox.isChecked()) if Config["autohidefinddialog"]: self.hide() def updateUi(self): enable = not self.findEdit.currentText().isEmpty() self.findButton.setEnabled(enable) self.replaceButton.setEnabled(enable) class PythonHighlighter(QSyntaxHighlighter): Rules = [] Formats = {} def __init__(self, parent=None): super(PythonHighlighter, self).__init__(parent) self.initializeFormats() PythonHighlighter.Rules.append((QRegExp( "|".join([r"\b%s\b" % keyword for keyword in KEYWORDS])), "keyword")) PythonHighlighter.Rules.append((QRegExp( "|".join([r"\b%s\b" % builtin for builtin in BUILTINS])), "builtin")) PythonHighlighter.Rules.append((QRegExp( "|".join([r"\b%s\b" % constant \ for constant in 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")) PythonHighlighter.Rules.append((QRegExp(r"#.*"), "comment")) 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'''"""(?!')''') @staticmethod def initializeFormats(): 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(sys.ps1) 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) 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() class TextView(QTextEdit): def __init__(self, parent=None): super(QTextEdit, self).__init__(parent) self.setAcceptRichText(False) font = QFont(Config["fontfamily"], Config["fontsize"]) self.setFont(font) self.textcharformat = QTextCharFormat() self.textcharformat.setFont(font) def currentWord(self, cursor=None): if cursor is None: cursor = self.textCursor() cursor.movePosition(QTextCursor.StartOfWord) cursor.movePosition(QTextCursor.EndOfWord, QTextCursor.KeepAnchor) return cursor.selectedText() def currentLine(self): cursor = self.textCursor() cursor.movePosition(QTextCursor.StartOfLine) cursor.movePosition(QTextCursor.EndOfLine, QTextCursor.KeepAnchor) return cursor.selectedText() def lastNonEmptyLine(self): cursor = self.textCursor() cursor.movePosition(QTextCursor.End) cursor.movePosition(QTextCursor.StartOfLine, QTextCursor.KeepAnchor) if cursor.selectedText().isEmpty(): cursor.movePosition(QTextCursor.PreviousCharacter, QTextCursor.KeepAnchor) cursor.movePosition(QTextCursor.StartOfLine, QTextCursor.KeepAnchor) text = cursor.selectedText() if not text.isEmpty(): while text.endsWith("\n"): text.chop(1) while text.endsWith(u"\u2029"): text.chop(1) return text def lastLine(self): cursor = self.textCursor() cursor.movePosition(QTextCursor.End) cursor.movePosition(QTextCursor.StartOfLine, QTextCursor.KeepAnchor) return cursor.selectedText() class TextEdit(TextView): COMPLETION_X_OFFSET = None def __init__(self, parent=None): super(TextEdit, self).__init__(parent) self.setLineWrapMode(QTextEdit.NoWrap) self.indent = 0 self.completionListWidget = None self.prefix = "" self.classes = set() self.defs = set() if TextEdit.COMPLETION_X_OFFSET is None: TextEdit.COMPLETION_X_OFFSET = \ QFontMetrics(self.font()).width("x") def checkForClassOrDef(self, line): match = CLASS_OR_DEF_RE.search(line) if match: if not match.group(2).startswith("__"): if match.group(1) == "class": self.classes.add(match.group(2)) else: self.defs.add(match.group(2)) def event(self, event): tabwidth = Config["tabwidth"] tooltipsize = Config["tooltipsize"] indent = " " * tabwidth if event.type() == QEvent.KeyPress: if event.key() == Qt.Key_Backspace: line = self.currentLine() if len(line) >= tabwidth and line.endsWith(indent): userCursor = self.textCursor() for _ in range(tabwidth): userCursor.deletePreviousChar() self.indent = max(0, self.indent - 1) return True elif event.key() == Qt.Key_Tab: if not self.tabChangesFocus(): cursor = self.textCursor() cursor.movePosition(QTextCursor.PreviousCharacter, QTextCursor.KeepAnchor) if cursor.selectedText().trimmed().isEmpty(): cursor = self.textCursor() cursor.insertText(indent) else: self.complete() return True # else leave for base class to handle elif event.key() == Qt.Key_ParenLeft: cursor = self.textCursor() cursor.movePosition(QTextCursor.StartOfLine, QTextCursor.KeepAnchor) name = unicode(cursor.selectedText().section(" ", -1)) tip = CAT.get(name) if tip is not None: rect = self.cursorRect() point = self.mapToGlobal(QPoint(rect.x(), rect.y())) if len(tip) > tooltipsize: tip = tip[:tooltipsize] + " ..." QToolTip.showText(point, tip) elif event.key() in (Qt.Key_Enter, Qt.Key_Return): userCursor = self.textCursor() cursor = QTextCursor(userCursor) cursor.movePosition(QTextCursor.End) if cursor.position() == userCursor.position() and \ self.lastLine().trimmed().isEmpty(): self.emit(SIGNAL("returnOnBlankLine")) insert = "\n" cursor = QTextCursor(userCursor) cursor.movePosition(QTextCursor.StartOfLine) cursor.movePosition(QTextCursor.EndOfLine, QTextCursor.KeepAnchor) line = cursor.selectedText() uline = unicode(line) checkForCAT(uline) self.checkForClassOrDef(uline) if line.endsWith(":") or line.endsWith("\\"): for c in line: if c == " ": insert += " " else: break insert += indent self.indent += 1 elif line.startsWith(indent): for c in line: if c == " ": insert += " " else: break userCursor.insertText(insert) return True elif event.key() in (Qt.Key_Up, Qt.Key_Down, Qt.Key_PageUp, Qt.Key_PageDown): cursor = self.textCursor() cursor.movePosition(QTextCursor.StartOfLine) cursor.movePosition(QTextCursor.EndOfLine, QTextCursor.KeepAnchor) line = cursor.selectedText() checkForCAT(unicode(line)) # Fall through to let the base class handle the movement return QTextEdit.event(self, event) def complete(self): def getWords(prefix): return [word for word in list(CAT.keys() + list(WORDS)) \ if len(word) >= MIN_COMPLETION_LEN and \ word.startswith(prefix)] cursor = self.textCursor() cursor.movePosition(QTextCursor.StartOfLine, QTextCursor.KeepAnchor) self.prefix = cursor.selectedText().section(" ", -1) if self.prefix.isEmpty(): return pos = self.prefix.length() - 1 while (self.prefix.at(pos).isLetterOrNumber() or \ self.prefix[pos] == ".") and pos >= 0: pos -= 1 if pos > 0: self.prefix = self.prefix.mid(pos + 1) if self.prefix.isEmpty(): return words = getWords(self.prefix) while not words and self.prefix.contains("."): self.prefix = self.prefix.mid(self.prefix.indexOf(".") + 1) words = getWords(self.prefix) if not words: return if len(words) > 15: length = len(self.prefix) words = [word for word in words if "." not in word[length:]] firsttime = False if self.completionListWidget is None: self.completionListWidget = QListWidget(self) self.completionListWidget.setWindowFlags(Qt.Popup) QShortcut(QKeySequence("Escape"), self.completionListWidget, self.completionListWidget.close) self.connect(self.completionListWidget, SIGNAL("itemActivated(QListWidgetItem*)"), self.userCompleted) firsttime = True self.completionListWidget.clear() self.completionListWidget.addItems(sorted(words)) self.completionListWidget.setCurrentItem( self.completionListWidget.item(len(words) // 2)) self.completionListWidget.scrollTo( self.completionListWidget.currentIndex(), QListWidget.PositionAtCenter) rect = self.cursorRect() point = self.mapToGlobal(QPoint(rect.x(), rect.y())) if not firsttime: screenRect = QApplication.desktop().availableGeometry(self) xDiff = screenRect.width() - (point.x() + \ self.completionListWidget.width()) if xDiff < 0: point.setX(point.x() + xDiff) yDiff = screenRect.height() - (point.y() + \ self.completionListWidget.height()) if yDiff < 0: point.setY(point.y() + yDiff) point.setX(point.x() + TextEdit.COMPLETION_X_OFFSET) self.completionListWidget.move(point) self.completionListWidget.show() def userCompleted(self): extra = self.completionListWidget.currentItem().text(). \ mid(len(self.prefix)) if not extra.isEmpty(): cursor = self.textCursor() cursor.insertText(extra) self.completionListWidget.close() def gotoLine(self): cursor = self.textCursor() lino, ok = QInputDialog.getInteger(self, "Sandbox - Goto Line", "Goto line:", cursor.blockNumber() + 1, 1, self.document().blockCount()) if ok: cursor.movePosition(QTextCursor.Start) cursor.movePosition(QTextCursor.Down, QTextCursor.MoveAnchor, lino - 1) self.setTextCursor(cursor) self.ensureCursorVisible() def gotoMatching(self): # move the cursor to the matching ()[]<>{} or do nothing OPEN = "([<{" CLOSE = ")]>}" cursor = self.textCursor() cursor.movePosition(QTextCursor.PreviousCharacter, QTextCursor.KeepAnchor) c = unicode(cursor.selectedText()) if c not in OPEN + CLOSE: cursor = self.textCursor() cursor.movePosition(QTextCursor.NextCharacter, QTextCursor.KeepAnchor) c = unicode(cursor.selectedText()) if c in OPEN + CLOSE: self.setTextCursor(cursor) else: return i = OPEN.find(c) if i > -1: movement = QTextCursor.NextCharacter stack = 0 target = CLOSE[i] else: i = CLOSE.find(c) if i == -1: return movement = QTextCursor.PreviousCharacter stack = -1 target = OPEN[i] cursor = self.textCursor() while not (movement == QTextCursor.NextCharacter and \ cursor.atEnd()) and \ not (movement == QTextCursor.PreviousCharacter and \ cursor.atStart()): cursor.clearSelection() cursor.movePosition(movement, QTextCursor.KeepAnchor) x = unicode(cursor.selectedText()) if not x: break if x == c: stack += 1 elif x == target: if stack == 0: cursor.clearSelection() if movement == QTextCursor.PreviousCharacter: cursor.movePosition(QTextCursor.NextCharacter) self.setTextCursor(cursor) break else: stack -= 1 def indentRegion(self): self._walkTheLines(True, " " * Config["tabwidth"]) def unindentRegion(self): self._walkTheLines(False, " " * Config["tabwidth"]) def commentRegion(self): self._walkTheLines(True, "# ") def uncommentRegion(self): self._walkTheLines(False, "# ") def _walkTheLines(self, insert, text): userCursor = self.textCursor() userCursor.beginEditBlock() start = userCursor.position() end = userCursor.anchor() if start > end: start, end = end, start block = self.document().findBlock(start) while block.isValid(): cursor = QTextCursor(block) cursor.movePosition(QTextCursor.StartOfBlock) if insert: cursor.insertText(text) else: cursor.movePosition(QTextCursor.NextCharacter, QTextCursor.KeepAnchor, len(text)) if cursor.selectedText() == text: cursor.removeSelectedText() block = block.next() if block.position() > end: break userCursor.endEditBlock() class ShellWidget(QSplitter): # Cannot offer a stop command since sending ^C or Del to the # interpreter does nothing. Alternative would be to use # code.InteractiveInterpreter but I prefer the real thing. def __init__(self, parent=None): super(ShellWidget, self).__init__(Qt.Vertical, parent) self.history = [] self.historyIndex = -1 self.output = TextView() self.output.setUndoRedoEnabled(False) self.output.setReadOnly(True) self.output.setTextInteractionFlags(Qt.TextSelectableByMouse| Qt.TextSelectableByKeyboard) self.connect(self.output, SIGNAL("textChanged()"), self.keepOutputVisible) self.outputHighlighter = PythonHighlighter(self.output.document()) self.input = TextEdit() self.connect(self.input, SIGNAL("returnOnBlankLine"), self.execute) self.inputHighlighter = PythonHighlighter(self.input.document()) self.addWidget(self.output) self.addWidget(self.input) QWidget.setTabOrder(self.input, self.output) self.process = None self.restartProcess() QShortcut(QKeySequence("F6"), self, self.changeFocus) self.setWindowTitle("Sandbox - Shell") def restartProcess(self): firsttime = False if self.process is not None: self.endProcess() self.output.append("%s RESTARTED %s\n" % ("#" * 20, "#" * 20)) else: firsttime = True self.useSeparators = False self.starting = True self.inTraceback = False self.inError = False self.process = QProcess(self) cwd = Config["cwd"] if not cwd.isEmpty() and cwd != ".": self.process.setWorkingDirectory(cwd) self.process.setProcessChannelMode(QProcess.MergedChannels) self.connect(self.process, SIGNAL("readyRead()"), self.readFromPython) self.process.start(sys.executable, ["-i"]) self.process.waitForStarted() startup = Config.get("shellstartup")[:] if startup is not None: for line in startup.split("\n"): QTimer.singleShot(0, lambda line=line: self.writeToPython(QString(line + "\n"))) QTimer.singleShot(0, lambda line=line: checkForCAT(unicode(line))) QTimer.singleShot(0, lambda firsttime=firsttime: self.setUseSeparators(firsttime=firsttime)) def setUseSeparators(self, on=True, firsttime=False): self.useSeparators = on if firsttime and Config["showwindowinfo"]: self.output.append("# This is the Python output window.") self.output.append("# Enter Python expressions and " "definitions in the input window below, ") self.output.append("# then press F5 to execute.") self.output.append(u"""\ # Use Tab or Ctrl+P for completion (which also works in editor windows), # and Ctrl+\u2191 and Ctrl+\u2193 for the command history.\n """) self.outputHighlighter.rehighlight() def endProcess(self): if self.process is not None and self.process.state(): self.process.write(QByteArray("\x04")) self.process.close() self.process.terminate() QThread.msleep(100) if self.process.state(): self.process.kill() self.process = None def changeFocus(self): if self.input.hasFocus(): self.output.setFocus() else: self.input.setFocus() def keepOutputVisible(self): self.output.moveCursor(QTextCursor.End) self.output.ensureCursorVisible() def writeSeparator(self, cursor, separator): if not self.useSeparators: return cursor.movePosition(QTextCursor.End) while True: cursor.movePosition(QTextCursor.PreviousCharacter, QTextCursor.KeepAnchor) if cursor.selectedText() == "\n": cursor.deleteChar() else: break cursor.insertText("\n%s\n" % separator, self.output.textcharformat) def readFromPython(self): beforeoutput = Config["beforeoutput"] beforeinput = Config["beforeinput"] cursor = self.output.textCursor() doneSeparator = False bytes = QByteArray() while self.process.bytesAvailable(): bytes += self.process.readAll() lines = QString.fromUtf8(bytes.data()).split("\n") for line in lines: if line.endsWith(sys.ps1): self.inTraceback = False self.inError = False line = trimQString(line, sys.ps1) line = trimQString(line, sys.ps2) if line.isEmpty(): continue if line.startsWith("Traceback"): self.inTraceback = True if line.startsWith(' File "'): self.inError = True if self.inError: line.replace("", "").prepend("Error: ") if not line.endsWith("\n"): line.append("\n") if not doneSeparator and \ not self.starting and \ not self.inTraceback: self.writeSeparator(cursor, beforeoutput) doneSeparator = True cursor = self.output.textCursor() cursor.movePosition(QTextCursor.End) cursor.insertText(line, self.output.textcharformat) if not self.inTraceback and \ self.output.lastNonEmptyLine() != beforeinput: # Gives cleaner output, but it is slow! self.output.setPlainText(self.output.toPlainText().replace( "%s\n%s\n" % (beforeinput, beforeoutput), "")) self.writeSeparator(cursor, beforeinput) def writeToPython(self, text): cursor = self.output.textCursor() cursor.movePosition(QTextCursor.End) cursor.insertText(text, self.output.textcharformat) text.append("\n\n") self.process.write(text.toUtf8()) self.process.waitForBytesWritten(-1) self.input.clear() def execute(self): self.starting = False text = self.input.toPlainText().trimmed() if text.isEmpty(): text = QString("") elif not text.contains("\n"): checkForCAT(unicode(text)) # TODO special case help() if text == "license()": text.prepend("license.MAXLINES = 9999;") else: self.historyIndex = len(self.history) self.history.append(text) text.append("\n") self.writeToPython(text) def undo(self): self.input.undo() def redo(self): self.input.redo() def copy(self): if self.input.hasFocus(): self.input.copy() else: self.output.copy() def cut(self): if self.input.hasFocus(): self.input.cut() else: self.output.cut() def paste(self): if self.input.hasFocus(): self.input.paste() else: self.output.paste() def selectAll(self): if self.input.hasFocus(): self.input.selectAll() else: self.output.selectAll() def complete(self): self.input.complete() def gotoMatching(self): self.input.gotoMatching() def clearOutput(self): self.output.clear() def showPrevious(self): if -1 < self.historyIndex < len(self.history): self.input.setText(self.history[self.historyIndex]) self.historyIndex = max(0, self.historyIndex - 1) self.input.setFocus() def showNext(self): if -1 < self.historyIndex < len(self.history): self.input.setText(self.history[self.historyIndex]) self.historyIndex = min(len(self.history) - 1, self.historyIndex + 1) self.input.setFocus() class EditWidget(TextEdit): def __init__(self, parent=None): super(EditWidget, self).__init__(parent) self.highlighter = PythonHighlighter(self.document()) self.findForm = None self.process = None self.results = None self.args = "" self.history = [0] self.historyIndex = 0 self.movingInHistory = False self.connect(self, SIGNAL("cursorPositionChanged()"), self._updateHistory) def _updateHistory(self): if not self.movingInHistory: lino = self.textCursor().blockNumber() if lino != self.history[-1]: self.historyIndex = len(self.history) self.history.append(lino) def gotoPrevious(self): self.movingInHistory = True self.historyIndex = max(0, self.historyIndex - 1) self._gotoHistory() self.movingInHistory = False def gotoNext(self): self.movingInHistory = True self.historyIndex = min(len(self.history) - 1, self.historyIndex + 1) self._gotoHistory() self.movingInHistory = False def _gotoHistory(self): cursor = self.textCursor() cursor.movePosition(QTextCursor.Start) cursor.movePosition(QTextCursor.Down, QTextCursor.MoveAnchor, self.history[self.historyIndex]) self.setTextCursor(cursor) self.ensureCursorVisible() def findText(self): if self.findForm is None: self.findForm = FindForm(self) self.connect(self.findForm, SIGNAL("find"), self.findClicked) self.connect(self.findForm, SIGNAL("replace"), self.replaceClicked) self.findForm.show() self.findForm.activateWindow() self.findForm.raise_() def findClicked(self, findText, caseSensitive, wholeWords): flags = QTextDocument.FindFlags() if caseSensitive: flags |= QTextDocument.FindCaseSensitively if wholeWords: flags |= QTextDocument.FindWholeWords cursor = self.document().find(findText, self.textCursor(), flags) if not cursor.isNull(): self.setTextCursor(cursor) else: self.parent().statusBar().showMessage( "'%s' not found" % findText, TIMEOUT) return cursor def findNext(self): if self.findForm is None or \ self.findForm.findEdit.currentText().isEmpty(): self.findText() else: self.findClicked(self.findForm.findEdit.currentText(), self.findForm.caseSensitiveCheckBox.isChecked(), self.findForm.wholeWordsCheckBox.isChecked()) def findWordAt(self): word = self.currentWord() if not word.isEmpty(): self.findClicked(word, True, True) def replaceClicked(self, findText, replaceText, caseSensitive, wholeWords): cursor = self.findClicked(self.findForm.findEdit.currentText(), self.findForm.caseSensitiveCheckBox.isChecked(), self.findForm.wholeWordsCheckBox.isChecked()) if cursor.isNull(): return cursor.beginEditBlock() start = min(cursor.anchor(), cursor.position()) cursor.removeSelectedText() cursor.insertText(replaceText) cursor.setPosition(start) cursor.setPosition(start + replaceText.length(), QTextCursor.KeepAnchor) cursor.endEditBlock() self.setTextCursor(cursor) def replaceNext(self): if self.findForm is None or \ self.findForm.findEdit.currentText().isEmpty(): self.findText() else: self.replaceClicked(self.findForm.findEdit.currentText(), self.findForm.replaceEdit.currentText(), self.findForm.caseSensitiveCheckBox.isChecked(), self.findForm.wholeWordsCheckBox.isChecked()) def setArgs(self): args, ok = QInputDialog.getText(self, "Sandbox - Set Arguments", "Command line arguments:", QLineEdit.Normal, self.args) self.args = "" if ok: self.args = unicode(args) def execute(self): filename = self.parent().filename if self.document().isModified() and \ QMessageBox.question(self, "Sandbox - Unsaved Changes", "Save unsaved changes in %s before executing?" % \ filename, QMessageBox.Yes|QMessageBox.No) == \ QMessageBox.Yes: self.parent().fileSave() if self.results is None: self.results = TextView() self.results.setUndoRedoEnabled(False) self.results.setReadOnly(True) self.results.setTextInteractionFlags(Qt.TextSelectableByMouse| Qt.TextSelectableByKeyboard) fm = QFontMetrics(self.font()) # TODO Make the default width & height configurable self.results.resize(fm.width("X") * 80, fm.height() * 25) self.connect(self.results, SIGNAL("textChanged()"), self.keepResultsVisible) QShortcut(QKeySequence("Escape"), self.results, self.results.close) else: self.results.clear() self.results.setWindowTitle("Sandbox - %s %s [Running]" % ( QFileInfo(filename).fileName(), " ".join(self.args.split()))) self.process = QProcess(self) cwd = Config["cwd"] if not cwd.isEmpty() and cwd != ".": self.process.setWorkingDirectory(cwd) self.process.setProcessChannelMode(QProcess.MergedChannels) self.connect(self.process, SIGNAL("readyRead()"), self.readResults) self.process.start(sys.executable, [filename] + self.args.split()) self.process.waitForStarted() def readResults(self): self.results.show() self.results.activateWindow() self.results.raise_() bytes = QByteArray() while self.process.bytesAvailable(): bytes += self.process.readAll() self.results.append(QString.fromUtf8(bytes.data())) def keepResultsVisible(self): self.results.moveCursor(QTextCursor.End) self.results.ensureCursorVisible() def closeEvent(self, event): self.stop() event.accept() def stop(self): if self.process is not None and self.process.state(): self.process.terminate() QThread.msleep(100) if self.process.state(): self.process.kill() self.results.setWindowTitle(self.results.windowTitle().replace( "[Running]", "[Stopped]")) def raiseResultsWindow(self): if self.results is not None: self.results.activateWindow() self.results.raise_() class ConfigForm(QDialog): def __init__(self, parent=None): super(ConfigForm, self).__init__(parent) self.highlightingChanged = False fm = QFontMetrics(self.font()) monofont = QFont(Config["fontfamily"], Config["fontsize"]) pixmap = QPixmap(16, 16) self.colors = {} self.boldCheckBoxes = {} self.italicCheckBoxes = {} self.completionCheckBoxes = {} self.editors = {} generalWidget = QWidget() self.rememberGeometryCheckBox = QCheckBox("&Remember Geometry") self.rememberGeometryCheckBox.setToolTip("Check this to make " "Sandbox remember the size and position of the Shell " "window and one Editor window") self.rememberGeometryCheckBox.setChecked( Config["remembergeometry"]) self.editorWindowRadioButton = QRadioButton( "Start With Editor &Window") self.shellWindowRadioButton = QRadioButton( "Start With &Shell Window") startWithButtonGroup = QButtonGroup(self) startWithButtonGroup.setExclusive(True) startWithButtonGroup.addButton(self.editorWindowRadioButton) startWithButtonGroup.addButton(self.shellWindowRadioButton) startwithshell = Config["startwithshell"] self.shellWindowRadioButton.setChecked(startwithshell) self.editorWindowRadioButton.setChecked(not startwithshell) self.autoHideFindDlgCheckBox = QCheckBox("Automati&cally Hide the " "Find and Replace dialog") self.autoHideFindDlgCheckBox.setToolTip("If checked, when you " "press Find or Replace the dialog will be hidden; " "further finds and replaces can be done through the Edit " "menu (which lists the keyboard shortcuts). Uncheck this " "if you prefer the dialog to stay visible and to click " "its buttons") self.autoHideFindDlgCheckBox.setChecked( Config["autohidefinddialog"]) self.backupLineEdit = QLineEdit(Config["backupsuffix"]) self.backupLineEdit.setToolTip("""If nonempty, a backup will be kept with the given suffix. If empty, no backup will be made.""") regex = QRegExp(r"[~.].*") self.backupLineEdit.setValidator(QRegExpValidator(regex, self)) self.backupLineEdit.setFont(monofont) backupLabel = QLabel("&Backup suffix:") backupLabel.setBuddy(self.backupLineEdit) regex = QRegExp(r"#.*") self.inputLineEdit = QLineEdit(Config["beforeinput"]) self.inputLineEdit.setValidator(QRegExpValidator(regex, self)) self.inputLineEdit.setInputMask("\#" + "x" * 40) self.inputLineEdit.setFont(monofont) inputPromptLabel = QLabel("&Input Prompt:") inputPromptLabel.setBuddy(self.inputLineEdit) self.outputLineEdit = QLineEdit(Config["beforeoutput"]) self.outputLineEdit.setValidator(QRegExpValidator(regex, self)) self.outputLineEdit.setInputMask("\#" + "x" * 40) self.outputLineEdit.setFont(monofont) outputPromptLabel = QLabel("&Output Prompt:") outputPromptLabel.setBuddy(self.outputLineEdit) self.cwdLineEdit = QLineEdit(Config["cwd"]) cwdLabel = QLabel("Working &Directory:") cwdLabel.setBuddy(self.cwdLineEdit) self.showWindowInfoCheckBox = QCheckBox( "Show Window Information at Start&up") self.showWindowInfoCheckBox.setToolTip("Uncheck this to stop the " "`This window shows Python's output' messages") self.showWindowInfoCheckBox.setChecked(Config["showwindowinfo"]) self.toolTipSizeSpinBox = QSpinBox() self.toolTipSizeSpinBox.setAlignment(Qt.AlignVCenter|Qt.AlignRight) self.toolTipSizeSpinBox.setRange(20, MAX_TOOLTIP_LEN) self.toolTipSizeSpinBox.setSuffix(" chars") self.toolTipSizeSpinBox.setValue(Config["tooltipsize"]) tooltipSizeLabel = QLabel("Max. Tooltip &Length:") tooltipSizeLabel.setBuddy(self.toolTipSizeSpinBox) self.maxLinesSizeSpinBox = QSpinBox() self.maxLinesSizeSpinBox.setAlignment(Qt.AlignVCenter| Qt.AlignRight) self.maxLinesSizeSpinBox.setRange(0, 10000) self.maxLinesSizeSpinBox.setSuffix(" lines") self.maxLinesSizeSpinBox.setValue(Config["maxlinestoscan"]) self.maxLinesSizeSpinBox.setToolTip("""The maximum lines to scan for function, method, and class names, and for imports when a file is loaded. This is used for setting up completions and tooltips and for the Goto class and Goto def submenus. It can be slow for large files.""") maxLinesSizeLabel = QLabel("&Max. Lines to Scan:") maxLinesSizeLabel.setBuddy(self.maxLinesSizeSpinBox) minWidth = 0 for label in (backupLabel, inputPromptLabel, outputPromptLabel, cwdLabel, tooltipSizeLabel, maxLinesSizeLabel): minWidth = max(minWidth, fm.width(label.text())) for label in (backupLabel, inputPromptLabel, outputPromptLabel, cwdLabel, tooltipSizeLabel, maxLinesSizeLabel): label.setMinimumWidth(minWidth) vbox = QVBoxLayout() vbox.addWidget(self.rememberGeometryCheckBox) hbox = QHBoxLayout() hbox.addWidget(self.editorWindowRadioButton) hbox.addWidget(self.shellWindowRadioButton) hbox.addStretch() vbox.addLayout(hbox) vbox.addWidget(self.autoHideFindDlgCheckBox) hbox = QHBoxLayout() hbox.addWidget(backupLabel) hbox.addWidget(self.backupLineEdit) hbox.addStretch() vbox.addLayout(hbox) hbox = QHBoxLayout() hbox.addWidget(inputPromptLabel) hbox.addWidget(self.inputLineEdit) hbox.addStretch() vbox.addLayout(hbox) hbox = QHBoxLayout() hbox.addWidget(outputPromptLabel) hbox.addWidget(self.outputLineEdit) hbox.addStretch() vbox.addLayout(hbox) hbox = QHBoxLayout() hbox.addWidget(cwdLabel) hbox.addWidget(self.cwdLineEdit) vbox.addLayout(hbox) vbox.addWidget(self.showWindowInfoCheckBox) hbox = QHBoxLayout() hbox.addWidget(tooltipSizeLabel) hbox.addWidget(self.toolTipSizeSpinBox) hbox.addStretch() vbox.addLayout(hbox) hbox = QHBoxLayout() hbox.addWidget(maxLinesSizeLabel) hbox.addWidget(self.maxLinesSizeSpinBox) hbox.addStretch() vbox.addLayout(hbox) vbox.addStretch() generalWidget.setLayout(vbox) highlightingWidget = QWidget() self.tabWidthSpinBox = QSpinBox() self.tabWidthSpinBox.setAlignment(Qt.AlignVCenter|Qt.AlignRight) self.tabWidthSpinBox.setRange(2, 20) self.tabWidthSpinBox.setSuffix(" spaces") self.tabWidthSpinBox.setValue(Config["tabwidth"]) tabWidthLabel = QLabel("&Tab width:") tabWidthLabel.setBuddy(self.tabWidthSpinBox) self.fontComboBox = QFontComboBox() self.fontComboBox.setCurrentFont(monofont) fontLabel = QLabel("&Font:") fontLabel.setBuddy(self.fontComboBox) self.fontSpinBox = QSpinBox() self.fontSpinBox.setAlignment(Qt.AlignVCenter|Qt.AlignRight) self.fontSpinBox.setRange(6, 20) self.fontSpinBox.setSuffix(" pt") self.fontSpinBox.setValue(Config["fontsize"]) vbox = QVBoxLayout() hbox = QHBoxLayout() hbox.addWidget(tabWidthLabel) hbox.addWidget(self.tabWidthSpinBox) hbox.addStretch() vbox.addLayout(hbox) hbox = QHBoxLayout() hbox.addWidget(fontLabel) hbox.addWidget(self.fontComboBox) hbox.addWidget(self.fontSpinBox) hbox.addStretch() vbox.addLayout(hbox) minWidth = 0 minButtonWidth = 0 count = 1 labels = [] buttons = [] for name, labelText in (("normal", "Normal:"), ("keyword", "Keywords:"), ("builtin", "Builtins:"), ("constant", "Constants:"), ("decorator", "Decorators:"), ("comment", "Comments:"), ("string", "Strings:"), ("number", "Numbers:"), ("error", "Errors:"), ("pyqt", "PyQt:")): label = QLabel(labelText) minWidth = max(minWidth, fm.width(label.text())) labels.append(label) boldCheckBox = QCheckBox("Bold") boldCheckBox.setChecked(Config["%sfontbold" % name]) self.boldCheckBoxes[name] = boldCheckBox italicCheckBox = QCheckBox("Italic") italicCheckBox.setChecked(Config["%sfontitalic" % name]) self.italicCheckBoxes[name] = italicCheckBox self.colors[name] = [Config["%sfontcolor" % name], None] pixmap.fill(QColor(self.colors[name][0])) if count <= 9: colorButton = QPushButton("&%d Color..." % count) count += 1 elif name == "pyqt": colorButton = QPushButton("&Q Color...") else: colorButton = QPushButton("Color...") minButtonWidth = max(minButtonWidth, 10 + pixmap.width() + fm.width(colorButton.text())) buttons.append(colorButton) colorButton.setIcon(QIcon(pixmap)) self.colors[name][1] = colorButton hbox = QHBoxLayout() hbox.addWidget(label) hbox.addWidget(boldCheckBox) hbox.addWidget(italicCheckBox) hbox.addWidget(colorButton) hbox.addStretch() vbox.addLayout(hbox) self.connect(colorButton, SIGNAL("clicked()"), lambda name=name: self.setColor(name)) for label in labels: label.setMinimumWidth(minWidth) for button in buttons: button.setMinimumWidth(minButtonWidth) vbox.addStretch() highlightingWidget.setLayout(vbox) tabWidget = QTabWidget() tabWidget.addTab(generalWidget, "&General") tabWidget.addTab(highlightingWidget, "&Highlighting") for name, label, msg in ( ("newfile", "On &New File", "The text here is automatically " "inserted into new files.
It may be convenient to add " "your standard imports and copyright notice here."), ("shellstartup", "&At Startup", "Sandbox executes the lines above " "whenever the Python interpreter is started.
" "Use them to add convenience, e.g. " "
from math import *; import re; " "etc.
" "Changes made here only take " "effect when Sandbox is next run." "
The codecs and sys imports, " "and the other codecs lines should
not be changed " "unless you are an expert.
")): editor = TextEdit() editor.setPlainText(Config[name]) editor.setTabChangesFocus(True) PythonHighlighter(editor.document()) vbox = QVBoxLayout() vbox.addWidget(editor, 1) vbox.addWidget(QLabel(msg)) widget = QWidget() widget.setLayout(vbox) tabWidget.addTab(widget, label) self.editors[name] = editor buttonBox = QDialogButtonBox(QDialogButtonBox.Ok| QDialogButtonBox.Cancel) layout = QVBoxLayout() layout.addWidget(tabWidget) layout.addWidget(buttonBox) self.setLayout(layout) self.connect(buttonBox, SIGNAL("accepted()"), self.accept) self.connect(buttonBox, SIGNAL("rejected()"), self.reject) self.setWindowTitle("Sandbox - Configure") def updateUi(self): pass # TODO validation, e.g., valid shellstartup, etc. def setColor(self, which): color = QColorDialog.getColor(QColor(self.colors[which][0]), self) if color is not None: self.colors[which][0] = color.name() pixmap = QPixmap(16, 16) pixmap.fill(QColor(color.name())) self.colors[which][1].setIcon(QIcon(pixmap)) def accept(self): Config["remembergeometry"] = \ self.rememberGeometryCheckBox.isChecked() Config["showwindowinfo"] = self.showWindowInfoCheckBox.isChecked() Config["startwithshell"] = self.shellWindowRadioButton.isChecked() Config["autohidefinddialog"] = \ self.autoHideFindDlgCheckBox.isChecked() Config["backupsuffix"] = self.backupLineEdit.text() Config["beforeinput"] = self.inputLineEdit.text() Config["beforeoutput"] = self.outputLineEdit.text() Config["cwd"] = self.cwdLineEdit.text() for name in ("shellstartup", "newfile"): Config[name] = unicode(self.editors[name].toPlainText()) Config["tabwidth"] = self.tabWidthSpinBox.value() Config["tooltipsize"] = self.toolTipSizeSpinBox.value() Config["maxlinestoscan"] = self.maxLinesSizeSpinBox.value() family = self.fontComboBox.currentFont().family() if Config["fontfamily"] != family: self.highlightingChanged = True Config["fontfamily"] = family size = self.fontSpinBox.value() if Config["fontsize"] != size: self.highlightingChanged = True Config["fontsize"] = size for name in ("normal", "keyword", "builtin", "constant", "decorator", "comment", "string", "number", "error", "pyqt"): bold = self.boldCheckBoxes[name].isChecked() if Config["%sfontbold" % name] != bold: self.highlightingChanged = True Config["%sfontbold" % name] = bold italic = self.italicCheckBoxes[name].isChecked() if Config["%sfontitalic" % name] != italic: self.highlightingChanged = True Config["%sfontitalic" % name] = italic color = self.colors[name][0] if Config["%sfontcolor" % name] != color: self.highlightingChanged = True Config["%sfontcolor" % name] = color QDialog.accept(self) class MainWindow(QMainWindow): NextId = 1 Instances = set() Shell = None Toolbars = {} def __init__(self, filename=QString(), isShell=False, parent=None): super(MainWindow, self).__init__(parent) self.setAttribute(Qt.WA_DeleteOnClose) MainWindow.Instances.add(self) self.setWindowTitle("Sandbox[*]") if isShell: self.editor = ShellWidget() MainWindow.Shell = self else: self.editor = EditWidget() self.setCentralWidget(self.editor) fileNewAction = self.createAction("&New File", self.fileNew, QKeySequence.New, "filenew", "Create a Python file") if not isShell: fileNewShellAction = self.createAction("Sh&ell", self.fileNewShell, icon="filenew", tip="Create a new (or go to the existing) Shell") fileOpenAction = self.createAction("&Open...", self.fileOpen, QKeySequence.Open, "fileopen", "Open an existing Python file") # TODO # fileOpenRecentAction = self.createAction("&Open Recent", # self.fileOpenRecent, icon="fileopen", # tip="Open an existing Python file") if not isShell: fileOpenAtAction = self.createAction( "Open &File at Cursor", self.fileOpenAt, "Ctrl+Shift+O", "fileopen", "Open the file whose name is at the cursor position") fileSaveAction = self.createAction("&Save", self.fileSave, QKeySequence.Save, "filesave", "Save the file") fileSaveAsAction = self.createAction("Save &As...", self.fileSaveAs, icon="filesaveas", tip="Save the file using a new filename") fileSaveAllAction = self.createAction("Save A&ll", self.fileSaveAll, icon="filesaveall", tip="Save all the files") fileConfigureAction = self.createAction("Config&ure...", self.fileConfigure, icon="fileconfigure", tip="Configure Sandbox") fileCloseAction = self.createAction("&Close", self.close, QKeySequence.Close, "fileclose", "Close this Python editor") fileQuitAction = self.createAction("&Quit", self.fileQuit, "Ctrl+Q", "filequit", "Close the application") editUndoAction = self.createAction("&Undo", self.editor.undo, QKeySequence.Undo, "editundo", "Undo the last editing action") editRedoAction = self.createAction("&Redo", self.editor.redo, QKeySequence.Redo, "editredo", "Redo the last editing action") editCopyAction = self.createAction("&Copy", self.editor.copy, QKeySequence.Copy, "editcopy", "Copy text to the clipboard") editCutAction = self.createAction("Cu&t", self.editor.cut, QKeySequence.Cut, "editcut", "Cut text to the clipboard") editPasteAction = self.createAction("&Paste", self.editor.paste, QKeySequence.Paste, "editpaste", "Paste in the clipboard's text") editSelectAllAction = self.createAction("Select &All", self.editor.selectAll, QKeySequence.SelectAll, "editselectall", "Select all the editor's text") editCompleteAction = self.createAction("Com&plete", self.editor.complete, "Ctrl+P", "editcomplete", "Complete the word (can also use Tab)") if isShell: editClearOutputAction = self.createAction("Clear &Output", self.editor.clearOutput, icon="editclear", tip="Clear the output text") else: editFindAction = self.createAction("&Find...", self.editor.findText, QKeySequence.Find, "editfind", "Find the given text") editFindNextAction = self.createAction("Find &Next", self.editor.findNext, QKeySequence.FindNext, "editfindnext", "Find the next occurrence of the given text") editFindWordAtAction = self.createAction( "Find &Word at Cursor", self.editor.findWordAt, "Shift+F3", "editfindnext", "Find the next occurrence of the word at the cursor") editReplaceNextAction = self.createAction("R&eplace Next", self.editor.replaceNext, QKeySequence.Replace, "editreplace", "Replace the next occurrence of the given text") editIndentRegionAction = self.createAction("&Indent Region", self.editor.indentRegion, "Ctrl+]", "editindent", "Indent the selected text or the current line") editUnindentRegionAction = self.createAction( "Unin&dent Region", self.editor.unindentRegion, "Ctrl+[", "editunindent", "Unindent the selected text or the current line") editCommentRegionAction = self.createAction("C&omment Region", self.editor.commentRegion, icon="editcomment", tip="Comment out the selected text or the " + \ "current line") editUncommentRegionAction = self.createAction( "Uncomment Re&gion", self.editor.uncommentRegion, icon="edituncomment", tip="Uncomment the selected text or the current line") actionRunAction = self.createAction("E&xecute", self.editor.execute, "F5", "actionrun", "Execute the input window's code or run the current file") if not isShell: actionSetArgsAction = self.createAction("Set &arguments...", self.editor.setArgs, icon="actionsetargs", tip="Set the command line arguments") actionStopAction = self.createAction("&Stop", self.editor.stop, icon="actionstop", tip="Forcibly stop the application") else: actionRestartAction = self.createAction("Restart &Python", self.editor.restartProcess, icon="actionrestart", tip="Stop the interpreter and start a new one") actionShowPrevAction = self.createAction( "Show Previous Command", self.editor.showPrevious, "Ctrl+Up", "previous", "Show the previous command ready for editing " + \ "and running") actionShowNextAction = self.createAction( "Show Next Command", self.editor.showNext, "Ctrl+Down", "next", "Show the next command ready for editing " + \ "and running") helpHelpAction = self.createAction("&Help", self.helpHelp, QKeySequence.HelpContents, icon="helphelp", tip="Online help") helpAboutAction = self.createAction("&About", self.helpAbout, icon="icon", tip="About Sandbox") fileMenu = self.menuBar().addMenu("&File") self.addActions(fileMenu, (fileNewAction,)) if not isShell: self.addActions(fileMenu, (fileNewShellAction,)) self.addActions(fileMenu, (fileOpenAction, # fileOpenRecentAction # TODO )) if not isShell: self.addActions(fileMenu, (fileOpenAtAction, fileSaveAction, fileSaveAsAction)) self.addActions(fileMenu, (fileSaveAllAction, None, fileConfigureAction, None, fileCloseAction, fileQuitAction)) editMenu = self.menuBar().addMenu("&Edit") self.addActions(editMenu, (editUndoAction, editRedoAction, None, editCopyAction, editCutAction, editPasteAction, editSelectAllAction, None, editCompleteAction)) if isShell: self.addActions(editMenu, (None, editClearOutputAction,)) else: self.addActions(editMenu, (None, editFindAction, editFindNextAction, editFindWordAtAction, editReplaceNextAction, None, editIndentRegionAction, editUnindentRegionAction, editCommentRegionAction, editUncommentRegionAction)) self.gotoMenu = self.menuBar().addMenu("&Goto") self.connect(self.gotoMenu, SIGNAL("aboutToShow()"), self.updateGotoMenu) actionMenu = self.menuBar().addMenu("&Action") self.addActions(actionMenu, (actionRunAction,)) if not isShell: self.addActions(actionMenu, (actionSetArgsAction, actionStopAction)) else: self.addActions(actionMenu, (actionRestartAction, None, actionShowPrevAction, actionShowNextAction)) viewMenu = self.menuBar().addMenu("&View") self.windowMenu = self.menuBar().addMenu("&Window") self.connect(self.windowMenu, SIGNAL("aboutToShow()"), self.updateWindowMenu) helpMenu = self.menuBar().addMenu("&Help") self.addActions(helpMenu, (helpHelpAction, helpAboutAction)) self.fileToolbar = self.addToolBar("File Toolbar") self.fileToolbar.setObjectName("FileToolbar") MainWindow.Toolbars[self.fileToolbar] = None self.addActions(self.fileToolbar, (fileNewAction, fileOpenAction)) if not isShell: self.addActions(self.fileToolbar, (fileSaveAction,)) self.editToolbar = self.addToolBar("Edit Toolbar") self.editToolbar.setObjectName("EditToolbar") MainWindow.Toolbars[self.editToolbar] = None self.addActions(self.editToolbar, (editUndoAction, editRedoAction, None, editCopyAction, editCutAction, editPasteAction)) if not isShell: self.addActions(self.editToolbar, (None, editFindAction, editFindNextAction, editReplaceNextAction, None, editIndentRegionAction, editUnindentRegionAction, editCommentRegionAction, editUncommentRegionAction)) self.actionToolbar = self.addToolBar("Action Toolbar") self.actionToolbar.setObjectName("ActionToolbar") MainWindow.Toolbars[self.actionToolbar] = None self.addActions(self.actionToolbar, (actionRunAction,)) if not isShell: self.addActions(self.actionToolbar, (actionStopAction,)) else: self.addActions(self.actionToolbar, (actionRestartAction, None, actionShowPrevAction, actionShowNextAction)) for toolbar in (self.fileToolbar, self.editToolbar, self.actionToolbar): action = viewMenu.addAction("&%s" % toolbar.windowTitle()) self.connect(action, SIGNAL("toggled(bool)"), self.toggleToolbars) action.setCheckable(True) MainWindow.Toolbars[toolbar] = action self.connect(self, SIGNAL("destroyed(QObject*)"), MainWindow.updateInstances) if not isShell: status = self.statusBar() status.setSizeGripEnabled(False) status.showMessage("Ready", 5000) self.columnCountLabel = QLabel("(empty)") status.addPermanentWidget(self.columnCountLabel) self.lineCountLabel = QLabel("(empty)") status.addPermanentWidget(self.lineCountLabel) self.connect(self.editor, SIGNAL("cursorPositionChanged()"), self.updateIndicators) self.connect(self.editor.document(), SIGNAL("blockCountChanged(int)"), self.updateIndicators) if Config["remembergeometry"]: if isShell: self.resize(Config["shellwidth"], Config["shellheight"]) self.move(Config["shellx"], Config["shelly"]) else: self.resize(Config["windowwidth"], Config["windowheight"]) if int(isShell) + len(MainWindow.Instances) <= 2: self.move(Config["windowx"], Config["windowy"]) self.restoreState(Config["toolbars"]) self.filename = QString(".") if isShell: self.editor.restoreState(Config["splitter"]) self.setWindowTitle("Sandbox - Shell") else: self.filename = filename if self.filename.isEmpty(): self.filename = QString("unnamed%d.py" % \ MainWindow.NextId) MainWindow.NextId += 1 self.editor.setText(Config["newfile"]) self.editor.moveCursor(QTextCursor.End) self.editor.document().setModified(False) self.setWindowModified(False) self.setWindowTitle("Sandbox - %s[*]" % self.filename) else: self.loadFile() self.connect(self.editor, SIGNAL("textChanged()"), self.updateDirty) self.updateGotoMenu() QTimer.singleShot(0, self.updateToolbars) @staticmethod def updateInstances(qobj): MainWindow.Instances = set([window for window \ in MainWindow.Instances if isAlive(window)]) if MainWindow.Shell is not None and not isAlive(MainWindow.Shell): MainWindow.Shell = None def createAction(self, text, slot=None, shortcut=None, icon=None, tip=None, checkable=False, signal="triggered()"): action = QAction(text, self) if icon is not None: action.setIcon(getIcon("%s.png" % icon)) if shortcut is not None: action.setShortcut(shortcut) if tip is not None: action.setToolTip(tip) action.setStatusTip(tip) if slot is not None: self.connect(action, SIGNAL(signal), slot) if checkable: action.setCheckable(True) return action def addActions(self, target, actions): for action in actions: if action is None: target.addSeparator() else: target.addAction(action) def updateDirty(self): self.setWindowModified(self.editor.document().isModified()) def updateIndicators(self): lines = self.editor.document().blockCount() cursor = self.editor.textCursor() self.columnCountLabel.setText("Column %d" % ( cursor.columnNumber() + 1)) if lines == 0: text = "(empty)" else: text = "Line %d of %d" % (cursor.blockNumber() + 1, lines) self.lineCountLabel.setText(text) def updateToolbars(self): for toolbar, action in MainWindow.Toolbars.items(): action.setChecked(toolbar.isVisible()) def toggleToolbars(self, on): title = self.sender().text() for toolbar, action in MainWindow.Toolbars.items(): if action.text() == title: toolbar.setVisible(on) action.setChecked(on) def closeEvent(self, event): if self == MainWindow.Shell: self.editor.endProcess() if Config["remembergeometry"]: Config["shellwidth"] = self.width() Config["shellheight"] = self.height() Config["shellx"] = self.x() Config["shelly"] = self.y() Config["splitter"] = self.editor.saveState() else: if self.editor.document().isModified(): reply = QMessageBox.question(self, "Sandbox - Unsaved Changes", "Save unsaved changes in %s" % self.filename, QMessageBox.Save|QMessageBox.Discard| QMessageBox.Cancel) if reply == QMessageBox.Save: self.fileSave() elif reply == QMessageBox.Cancel: event.ignore() return # else accept and discard if Config["remembergeometry"]: Config["windowwidth"] = self.width() Config["windowheight"] = self.height() Config["windowx"] = self.x() Config["windowy"] = self.y() if self.editor.findForm is not None: Config["findcasesensitive"] = self.editor.findForm \ .caseSensitiveCheckBox.isChecked() Config["findwholewords"] = self.editor.findForm \ .wholeWordsCheckBox.isChecked() Config["toolbars"] = self.saveState() del MainWindow.Toolbars[self.fileToolbar] del MainWindow.Toolbars[self.editToolbar] del MainWindow.Toolbars[self.actionToolbar] event.accept() def fileConfigure(self): form = ConfigForm(self) if form.exec_(): # Should only do this if the highlighting was actually # changed since it is computationally expensive. if form.highlightingChanged: font = QFont(Config["fontfamily"], Config["fontsize"]) textcharformat = QTextCharFormat() textcharformat.setFont(font) PythonHighlighter.initializeFormats() for window in MainWindow.Instances: if isAlive(window): if window == MainWindow.Shell: window.editor.input.setFont(font) window.editor.input.textcharformat = \ textcharformat window.editor.inputHighlighter.rehighlight() window.editor.output.setFont(font) window.editor.output.textcharformat = \ textcharformat window.editor.outputHighlighter.rehighlight() else: window.statusBar().showMessage( "Rehighlighting...") window.editor.setFont(font) window.editor.textcharformat = textcharformat window.editor.highlighter.rehighlight() window.statusBar().clearMessage() saveConfig() def fileQuit(self): QApplication.closeAllWindows() shell = MainWindow.Shell if shell is not None: shell.editor.endProcess() def fileNew(self): MainWindow().show() def fileNewShell(self): if MainWindow.Shell is not None and isAlive(MainWindow.Shell): MainWindow.Shell.activateWindow() MainWindow.Shell.raise_() else: MainWindow(isShell=True).show() def fileOpen(self): if not self.filename.isEmpty(): path = QFileInfo(self.filename).path() else: path = "." filename = QFileDialog.getOpenFileName(self, "Sandbox - Open File", path, "Python files (*.py *.pyw)") if not filename.isEmpty(): # To prevent opening the same file twice for window in MainWindow.Instances: if isAlive(window) and window != MainWindow.Shell: if window.filename == filename: window.activateWindow() window.raise_() return if MainWindow.Shell != self and \ not self.editor.document().isModified() and \ self.filename.startsWith("unnamed"): self.filename = filename self.loadFile() else: MainWindow(filename).show() def loadFile(self): # File loading is slow due to reading CAT data and highlighting, # but mostly due to the former. If you load one PyQt4 app, it # will be slow, but subsequent ones will be quick becaue all the # CAT data will have been gathered already. QApplication.setOverrideCursor(QCursor(Qt.WaitCursor)) fh = None try: try: fh = QFile(self.filename) if not fh.open(QIODevice.ReadOnly): raise IOError, unicode(fh.errorString()) stream = QTextStream(fh) stream.setCodec("UTF-8") text = stream.readAll() self.editor.setPlainText(text) self.editor.document().setModified(False) # Get function, method, and class names for completion; # get imports for CAT. for i, line in enumerate(text.split("\n")): # This is slow! if i >= Config["maxlinestoscan"]: break if CATABLE_LINE_RE.indexIn(line) > -1: uline = unicode(line) checkForCAT(uline) self.editor.checkForClassOrDef(uline) except (IOError, OSError), e: QMessageBox.warning(self, "Sandbox - Load Error", "Failed to load %s: %s" % (self.filename, e)) finally: if fh is not None: fh.close() QApplication.restoreOverrideCursor() self.editor.document().setModified(False) self.setWindowModified(False) self.setWindowTitle("Sandbox - %s[*]" % \ QFileInfo(self.filename).fileName()) def fileOpenRecent(self): print "fileOpenRecent" # TODO def fileOpenAt(self): filename = self.editor.currentWord() if filename in ("py", "pyw"): cursor = self.editor.textCursor() cursor.movePosition(QTextCursor.PreviousWord) filename = self.editor.currentWord(cursor) + "." + filename if filename.isEmpty(): return filename = unicode(filename) dir = os.path.dirname(unicode(QDir(self.filename).absolutePath())) candidates = [filename, os.path.join(dir, filename)] if not filename.endswith((".py", ".pyw")): filename += ".py" candidates.append(filename) candidates.append(os.path.join(dir, filename)) for path in sys.path: candidates.append(os.path.join(path, filename)) for candidate in candidates: if QFile.exists(candidate): QApplication.setOverrideCursor(QCursor(Qt.WaitCursor)) MainWindow(QString(candidate)).show() QApplication.restoreOverrideCursor() break else: self.statusBar().showMessage("'%s' not found" % filename, TIMEOUT) def fileSave(self): if self.filename.startsWith("unnamed"): return self.fileSaveAs() if not Config["backupsuffix"].isEmpty() and \ QFile.exists(self.filename): backup = self.filename + Config["backupsuffix"] ok = True if QFile.exists(backup): ok = QFile.remove(backup) if not ok: QMessageBox.information(self, "Sandbox - Save Warning", "Failed to remove the old backup %s") if ok: # Must use copy rather than rename to preserve file # permissions; could use rename on Windows though if not QFile.copy(self.filename, backup): QMessageBox.information(self, "Sandbox - Save Warning", "Failed to save a backup %s") fh = None try: try: fh = QFile(self.filename) if not fh.open(QIODevice.WriteOnly): raise IOError, unicode(fh.errorString()) stream = QTextStream(fh) stream.setCodec("UTF-8") stream << self.editor.toPlainText() self.editor.document().setModified(False) self.setWindowModified(False) self.setWindowTitle("Sandbox - %s[*]" % \ QFileInfo(self.filename).fileName()) self.statusBar().showMessage("Saved %s" % self.filename, TIMEOUT) except (IOError, OSError), e: QMessageBox.warning(self, "Sandbox - Save Error", "Failed to save %s: %s" % (self.filename, e)) finally: if fh is not None: fh.close() return True def fileSaveAs(self): filename = QFileDialog.getSaveFileName(self, "Sandbox - Save File As", self.filename, "Python files (*.py *.pyw)") if not filename.isEmpty(): self.filename = filename return self.fileSave() return False def fileSaveAll(self): count = 0 for window in MainWindow.Instances: if isAlive(window) and window != MainWindow.Shell and \ window.editor.document().isModified(): if window.fileSave(): count += 1 self.statusBar().showMessage("Saved %d of %d files" % ( count, len(MainWindow.Instances) - \ int(MainWindow.Shell is not None)), 5000) def updateGotoMenu(self): self.gotoMenu.clear() isShell = self == MainWindow.Shell self.gotoMenu.addAction(self.createAction("&Matching", self.editor.gotoMatching, "Ctrl+M", "gotomatching", "Move the cursor to the matching parenthesis")) if not isShell: self.gotoMenu.addAction(self.createAction("&Line...", self.editor.gotoLine, "Ctrl+L", "gotoline", "Move the cursor to the given line")) self.gotoMenu.addAction(self.createAction( "&Previous position", self.editor.gotoPrevious, "Ctrl+Up", "previous", "Move the cursor to the " "previous line in the line history")) self.gotoMenu.addAction(self.createAction( "&Next position", self.editor.gotoNext, "Ctrl+Down", "next", "Move the cursor to the " "next line in the line history")) if self.editor.classes or self.editor.defs: self.gotoMenu.addSeparator() if self.editor.classes: i = 1 classMenu = self.gotoMenu.addMenu( getIcon("forward.png"), "&class") for text in sorted(self.editor.classes, key=unicode.lower): action = classMenu.addAction(text, self.gotoClassOrDef) accel = "" if i < 10: accel = "&%d " % i elif i < 36: accel = "&%c " % chr(i + ord("@") - 9) action.setText("%s%s" % (accel, text)) action.setToolTip(text) # Stash class name action.setData(QVariant(0)) action.setIcon(getIcon("forward.png")) i += 1 if self.editor.defs: i = 1 defMenu = self.gotoMenu.addMenu(getIcon("forward.png"), "&def") for text in sorted(self.editor.defs, key=unicode.lower): action = defMenu.addAction(text, self.gotoClassOrDef) accel = "" if i < 10: accel = "&%d " % i elif i < 36: accel = "&%c " % chr(i + ord("@") - 9) action.setText("%s%s" % (accel, text)) action.setToolTip(text) # Stash def name action.setData(QVariant(1)) action.setIcon(getIcon("forward.png")) i += 1 def gotoClassOrDef(self): action = self.sender() if not isinstance(action, QAction): return if action.data().toInt()[0] == 0: text = "class " else: text = "def " text += action.toolTip() # Retrieve class or def name if not self.editor.find(text, QTextDocument.FindCaseSensitively): if not self.editor.find(text, QTextDocument.FindCaseSensitively| QTextDocument.FindBackward): self.statusBar().showMessage("'%s' not found" % text, TIMEOUT) if text.startsWith("def"): self.editor.defs.discard(unicode(action.toolTip())) else: self.editor.classes.discard(unicode(action.toolTip())) def updateWindowMenu(self): self.windowMenu.clear() shell = MainWindow.Shell if shell is not None and isAlive(shell): action = self.windowMenu.addAction("&Shell", self.raiseWindow) action.setData(QVariant(long(id(shell)))) action.setIcon(getIcon("icon.png")) if hasattr(self.editor, "results") and \ self.editor.results is not None and \ self.editor.results.isVisible(): action = self.windowMenu.addAction("&Results", self.editor.raiseResultsWindow) action.setIcon(getIcon("window.png")) i = 1 menu = self.windowMenu for window in MainWindow.Instances: if window != shell and isAlive(window): text = window.windowTitle().replace("Sandbox - ", "") \ .replace("[*]", "") if i == 10: self.windowMenu.addSeparator() menu = menu.addMenu("&More") accel = "" if i < 10: accel = "&%d " % i elif i < 36: accel = "&%c " % chr(i + ord("@") - 9) text = "%s%s" % (accel, text) i += 1 action = menu.addAction(text, self.raiseWindow) action.setData(QVariant(long(id(window)))) action.setIcon(getIcon("window.png")) def raiseWindow(self): action = self.sender() if not isinstance(action, QAction): return for window in MainWindow.Instances: if isAlive(window) and \ id(window) == action.data().toLongLong()[0]: window.activateWindow() window.raise_() break def helpHelp(self): HelpForm(self).show() def helpAbout(self): iconLabel = QLabel() icon = getPixmap("icon.png") iconLabel.setPixmap(icon) nameLabel = QLabel(" " "Sandbox") versionLabel = QLabel("" "Python %s on %s
PyQt %s - Qt %s
" "Sandbox %s
" % ( sys.version[:sys.version.find(" ")], sys.platform, QT_VERSION_STR, PYQT_VERSION_STR, __version__)) aboutLabel = QTextBrowser() aboutLabel.setOpenExternalLinks(True) aboutLabel.setHtml("""\ Sandbox provides facilities for learning and experimenting with Python, and for creating and editing Python files. It is designed to be easy to learn and use, and to provide a PyQt-based alternative to IDLE. (If you need a full-featured IDE, with support for debugging, we suggest you use eric4.)

Sandbox's home is at www.qtrac.eu.

If you want to learn more about Python GUI programming with PyQt (the GUI library used by Sandbox), you might like to buy my book, Rapid GUI Programming with Python and Qt, ISBN: 0132354187.

I hope you find it useful!

--
Mark Summerfield, mark@qtrac.eu""") thanksLabel = QTextBrowser() thanksLabel.setOpenExternalLinks(True) thanksLabel.setHtml("""

  • David Boddie
  • Phil Thompson
  • KDE, from whom I got most of the icons
""") licenseLabel = QTextBrowser() licenseLabel.setOpenExternalLinks(True) licenseLabel.setHtml(__license__.replace("\n\n", "

") \ .replace("(c)", "©")) changesLabel = QTextBrowser() changesLabel.setOpenExternalLinks(True) changesLabel.setHtml(u"""\

  • 0.9.0 You can now execute files (and set their command line arguments), and stop them if necessary. The Find dialog now remembers previous find and replace texts.
  • 0.8.1 Removed Todo tab; now keep todos as comments at the end. Improved completion behaviour.
  • 0.8.0 Improved Goto submenus and Window menu and submenu. Implemented Find Word at Cursor.
  • 0.7.2 Changed a couple of default values. Implemented Open File at Cursor.
  • 0.7.1 Fixed subtle bug in Goto class and def regex. If a class or def is not found (i.e., it has been deleted or renamed), it is now removed from the Goto menu.
  • 0.7.0 Implemented Goto menu, including Goto class and Goto def submenus. Fixed subtle bug in Goto Matching.
  • 0.6.3 Added key bindings to online help. Improved Goto Matching and completions. Implemented Ctrl+\N{UPWARDS ARROW} and Ctrl+\N{DOWNWARDS ARROW} to move to the previous and next lines in the line history. Switched off line wrapping for the input window and for editors.
  • 0.6.2 Added Changes tab to the about box. The shell now uses the Python executable in sys.executable, i.e., the Python that was used to run Sandbox. (This is useful for those with multiple Pythons installed.)
""") tabWidget = QTabWidget() tabWidget.addTab(aboutLabel, "&About") tabWidget.addTab(thanksLabel, "&Thanks to") tabWidget.addTab(licenseLabel, "&License") tabWidget.addTab(changesLabel, "&Changes") okButton = QPushButton("OK") layout = QVBoxLayout() hbox = QHBoxLayout() hbox.addWidget(iconLabel) hbox.addWidget(nameLabel) hbox.addStretch() hbox.addWidget(versionLabel) layout.addLayout(hbox) layout.addWidget(tabWidget) hbox = QHBoxLayout() hbox.addStretch() hbox.addWidget(okButton) hbox.addStretch() layout.addLayout(hbox) dialog = QDialog(self) dialog.setLayout(layout) dialog.setMinimumSize( min(self.width(), int(QApplication.desktop().availableGeometry() \ .width() / 2)), int(QApplication.desktop().availableGeometry() \ .height() / 2)) self.connect(okButton, SIGNAL("clicked()"), dialog.accept) dialog.setWindowTitle("Sandbox - About") dialog.exec_() ICONS["actionrestart.png"] = """\ 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\ """ ICONS["actionrun.png"] = """\ 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\ x4wyAAAAAElFTkSuQmCC\ """ ICONS["actionsetargs.png"] = """\ iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABHNCSVQICAgIfAhkiAAAABl0\ RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAASRSURBVFiFtZfNixxFGIeft7q6\ e6Zn52M3LAl4MBjUm5FIbvHjX8gxf4CCB88J4kFB1BWiCJ4UBD2LOehBzNW4iRjIQkSMIWRR\ Me5G153J7Md0Vb0euucrbrI9ktQwXUXRvL/n/b1V1d2iqlRt6WfxKUReFBURBRMMBiMxViwx\ kUZyp9/7qvfK1rtVY9qqN8af2sbJIyc/OH309GJiEgJKwOPwOBxOHB7PhZvfPtN+s/Hl5mv9\ n6rENVUBEBaPHThWr0mNoAGnObnm5DogDwNcyMnDgMMLh2vAo1XDVnZAgaCBQRjgcHgCjnzU\ O/WFF8FVzmkmAAF88LjgGP5KSXIcAV/0ITwcALQAGDvgcerw4qeAvPqHBSD44DUPOU6nHQgS\ yMkLgOCR6htrhhIECMGT+3xKfNSXTnjvKAr2wAEE5z1DB4J48qETMobw6osV++ABIHivuR9M\ ZF/ugOGMlLtgFoD6h+mTrbT50YlHnl1UVJVAoBgpStCgAeX5Qy9EC+lCkntXHj++lHbjq3i6\ vgdz+k7n/bnXRYEAIEgABLRcIOJky/wjL1ujZunsifeee3z+CXzweC2y8BrwweG1DF720w5M\ 7ADxOHEsb10w/rge1ShABBoBkaIRqNVyTlEHjXPpko3Vdh5rH8GFvBT2uBLEh3KraVHbKcHR\ uDwFxHG59wOXNi4hpshaUQTGVxnOCVgFQ9ui/CfrkejIkSHAVMVHfT/c4fz6N6z0V9BIIRIE\ nYIAQaUQH0EAVp3qwA8K8buzLk++Yjwsw1h8bfdPLv69zEp3BWdyiCgDDyXugphwQFFQsKiS\ h3xkswuOMCqDHzsSfHHeq+OX7jUuri9zvX99VNNh1ir3gZDJ0kgJ4GHH7wDsmXXhRuD29jrX\ Nn7m+1sXub17u8g2ogw/IShazhVtCkImylHuVSsKy398x/GDx3Gl/Vv5Fje7N1jt3mS1t8pv\ vV/pu/70BvaTwhNjEfZYfnuuCRSseOGTHz/m3PXPSaMaQT1r22v7nyB6DwjZu/6jOZmEAIsW\ w43djf1Fq0DIvRfhsESjuQBWHOfDZnhKEmne7wQVgBgrVqafNHdDBJC+5BgFI4hR1AhqKM6H\ SFCjmJ7pkct5UVUOvNp6WkUPjZX2yBQOypwsmQU5uBdgalPiOMFsS9691n0DuLyPf7fWz25e\ sQB/vdW9ss/NHDjTOrzXcz6zGZnNqNuMxMZoEOnSvbx+dvPr/WLCjI/jSQBBaCVtWkmLRtwg\ izPSKMUFx42qQWcC8JRPtqJ10g6ddJ5W2qYZN8nijHpUZ8dvzyA/yyvZRGsmLdolQCft0E7b\ NGyDepzRc93qr0P/ByAxCc24SStp0U7bYyeSFlmcEQ2imeLNDFC3GVmc0YgbBcgERDNp4nce\ 1ltx2Wo2pR7VRyBz8RytpFVCdOgnd2YqQfVPM8CIwZqYOIpJTEIa1ahH9cKNpEknnSeLs5kS\ mgVgnZxdgyGSCGvs6B9HBUwjzljrru0Aq1WDyiyf54tn2qdMZF5KbWriKJYkSiS1NZPaVGpx\ zUQmkqu/X/1i7e2Npaox/wWeGylq91mxwAAAAABJRU5ErkJggg==\ """ ICONS["actionstop.png"] = """\ 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\ AABJRU5ErkJggg==\ """ ICONS["next.png"] = """\ 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\ """ ICONS["previous.png"] = """\ iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABmJLR0QA/wD/AP+gvaeTAAAA\ CXBIWXMAAAsNAAALDQHtB8AsAAAAB3RJTUUH0wkJFQ03P5hTAwAABchJREFUeNrFl1tsHNUZ\ x38zszO768va67gkcRIC1CBHQHE8IkWEhAaJVoqKShESLxBxWyAPvFXlDQTioWofeAgPRKMa\ gpAQKGojHiIEhUDS0DZiHezUahSxyEE468SX3bV3d2Z3Zs7hYWdv3t0Yp0gc6ZPOzLn8/+f/\ fed858BPXJT1dB47OHkjyH0gRoFRpGiYQ04hxRTCPWFyJAUIy7Lkj0Jg7ODkE8DjwF6QgADV\ ADVS6SAc8J3GIV8gvHdM+ddxwLUsS1wTgbGDkztR1NeQ4l5UDYx+MAYwwlG2xZa5c2sGgLwD\ n8xsx3bKUJoHZxGkC0gb3/mDydtvAU47Isoaqx4HILoJo+c6nto1y+039XDL1i4ihtoyZnah\ xL//l+PUefhyRofCxUqDX/67yZtPA8uWZXlrEqiBa2GI3cKjO7/jd7sHifeEOqolV0116lyW\ wyd0Ls/NgZcH4U6ZcvzXwHyjEkpH8FAX27ZuJHFvmbGbe9uCHj25WKs/vHdDS/vSisv4R0t8\ NlWskPDt4yZvPwIUqySUZvCvdoIygaazZcs2XnhAsCmutwU/fibL8w9urn0fOpZm/67+ln5O\ WXL4wwynp/MVEqXFB8zQ0U8B27IsudqRb4IPsRGe3OMQ6wpRLMkWWw0O8PyDmzl+JtvSV0g4\ sC/G8PXxSkej/2UgDoQAtPrqJ54A+SzRIR7blWHH9h5cX7bY6emVFvBq+eVIL+9/vsB1cb1p\ jARuGBScTPWDu7w5LXZkh5Sps6ZplusKSPE4aGwc7GVkezcFR7TYmfP5juCNSpw5n28ZG+vW\ +f1oDhQdtMhDQD+gqwBjz315I7CXyAbuG17E86FY8ptseqa4JngjiemZYm1sruDxTbqIRIPI\ BlC00WRp/w4gUt1X+yr+GWAwtkzBbj4vrmTdHwzeSOLQsTQzC4L/zG6pNxg22HOg9+0GzlZd\ MAqwqU8gaV79tYA3kmgCB4xwtFJRjREgWiegRYhHy+Rt0WTXCl4t/3wJlIbNrqlU4kDVh4FI\ PQjVCLFwmUJJ1KwT+KFj6bb/n3p9qe3/Uy82kyDUDVIqtSCsHkgF12DFpmadQL76Lty27cLS\ AHteWZdAal0B4ZCxI8zlozVbvaI9r1RALiwNXHXW1SSq34oSKOEV6mLULhO+c0+2FMaTzQln\ /5/cWr3XACHBkxqgrosEQEgF16eSqqWbAkQoOISmUDQKtoeuhTpkO/WaA1HX6nXbKQcpupRC\ w62gCfcEmgblRVT1/4v6dkVr5F4OMmg5M0EUJwRgciSV5NkvsOfv1pVBQlrnyTK2viZgPOq2\ /e/5gD0PwjtnRj+6CNhVvQXCeweVu5ezC/x8S3fnySPV7RFrabspvnxVYqnZQsX/Xv5jNAo1\ ApZlyUSC8STPHLBzl+4SG28g3i3XLXVfuNxZuYKCnbtUWb323gdADgIXBMXFd/6IFj2ZujjL\ Pb/4Gbq2PhK9HQi4vkLq4nxwQ0m/QZgskAXcmrcnJibkb81QOi1vy/hC/c1CtsTwkIGha+ia\ 0mIn/5tn7+31q9qr76bb9lPxOD2Voei4UM68ZurHPgHSQNayLH/1nvNMjhxO+gdCK0X+/I9k\ hvvu6KWnp31M/OXo5Vo9rLdu03y+wKeTKxQdrwKuvf834DKQAbymG1GgAqZp+kNMTqblrTlX\ aPdfmHVwhcq2DSp6SCWksqYp0iP5dZF/TWdxPdEIngauVO+DHa/liURCBbqS3sO/wuh/GUUb\ C+sq12/q4tYhwcBAH5rWvB1932VpKcf0JZVv54qUXAHCO4eTfsMMHz8brPxK4434qg+TgEQE\ iCflo0+iRR5C0Uar7VEDBvsqHlzIediN8Se8c3j5j4NozwILgewtr6OrPs0SiYQS5ItuoD9Z\ 2r8DvW83qjGCqg8HKTU4q90UfilFOTMRHDKFYKtlg7rX7rH6gx6ngRp6oEg0sEjwT60dZuAC\ DmAH5qz1OP0eJWjqqRCVduEAAAAASUVORK5CYII=\ """ ICONS["forward.png"] = """\ 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\ J4S73uX037syRqkZVn5MAAAAAElFTkSuQmCC\ """ ICONS["editcomment.png"] = """\ iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABmJLR0QAAAAAAAD5Q7t/AAAA\ CXBIWXMAAAsLAAALCwFtBIS3AAAAB3RJTUUH0woNCgsyxhAJwAAAAqxJREFUeJztlrtuE0EU\ hr/N5oKxg5NIBJCAnlfgAahGNCloUyAKpPAyUYpUTiqUggZNR5Qn2DS4oEjDRVYcDPgWO15f\ dilmhp1sxs4ig6DwL608Hs3M+c9/Zv+zMMMMM/xjeFPujzOsWQSG49bOuyY3DzZjgOP28Wr5\ ebkFRI5lnhDi2uhSyjWgBfRcJFwEPIDtjW223mzVy5SX9eZhap0vpVwHHgBr+ixb0VjvuQ98\ 0eNBFgLzAEeNI3Y2duCA9t6zvRWgkyLhCSG+OvY7IaXM4SjFWAVqgxqHjUNDouEgEWl57wJF\ x1kxqnQ9oAkU9FzfJjGWwIK3gO/5VMIKuxu7ThJCiB+OTB8DIx081ufd1P9DVBkmEgAgF+fw\ Y59qv0p9ULdJFIEuEEspV4A7OjvfyrCjfw0JEzwkQwmofKiwP9onX8yTW83RP+9z8vqEoBQ8\ AW4DVaAvhGiMS0CrUSCpe6SVuZ5A87RJoViACDrNDmfvzghKwUtUrZeB78BAK7Cu5+ZSCvSA\ JZLbn90Hon7EsDHkggta71sEpeAVyWVqpw6MrQeU8cwBN1ClGupnlJkAQxh1RnQ/dwlKwZbO\ 6BT1PtdITGViCVJwOqKTQNgMCZsh5bflF6hsq8BHTaKrs5nL4oQG4xzR1QsWgXvAI1TNG8An\ Hdz2gXlgFXhI4oRpmEADoI5SsIHliOM29YBvqHrXgDMyOqGU8imJD9gknHAp4KEu0LIemxt9\ pRcAt7jshDHqXU/7gEkqUwnMvK/HEe5u6Ash0qR+QUpZtEhA0pwuKTLN94CPckCXE7aBc/1r\ k7iCqQhMUsBASrnEBCP6EwrYTuiR1DxEqWArkemD5HdgPD6yghssAHk9vtIFDaZRwEN5Rp7E\ fs28CWS6YOdvEDD7fSu4C84uOMN/g5++gjuED3GGqgAAAABJRU5ErkJggg==\ """ ICONS["editcomplete.png"] = """\ iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABGdBTUEAALGPC/xhBQAAAAlw\ SFlzAAALEgAACxIB0t1+/AAAAUBJREFUeJztlrFKw0AYx38RhzyCY/oUqVskm5tEcBQEFynk\ EcQXKBS6SKDg2KG46ChmbJ7CjL5BXKQd7gvlzgRbm3An+ofj7rsj933587/7n8ciBICkUL0Z\ 94Qw8gE46DXLFvDMichfaHH+kXSb0WDYKQZWNnI7xcB2uBNN3OpaidMRAC+TqRZ/3p8AUA1f\ ASjyqfadfQYCGZT9a6CRbesMHIbXVwCU2cwDCGSh3HNjUxM1LiTfPJsBDjDgpTKY7K+BnU7U\ H/YCAw4wsHGnfy/4GVpeUL/HC4byB8v+NeCoFyzjUxkWjRWG0ZFazd9lpr4nurkfrDNg7U0Y\ pyM37oGvOE5UE1xK+x6+tN1gnYEuNKCdnsG56t+e5LQYbmquu8PAmVT2+CwVV6rCyA9UfFMC\ kI+bN6p18tCWqcUzrDOwBh2zVCR+JZVeAAAAAElFTkSuQmCC\ """ ICONS["editclear.png"] = """\ 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\ """ ICONS["editcopy.png"] = """\ iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABmJLR0QA/wD/AP+gvaeTAAAA\ CXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH1AcZDRI4gmRrggAAAvxJREFUeNrFl01v20YQ\ hp9ZrhJANRDHhZWDfCjinOLCQOv+gl7aY6/tb/Mhp/bav2FYQdt7HPTgXhOkjdOQ3JnpgTRN\ WqQlWiqygERhuR/vzr7P7ErcnU9ZIsDri79GqTg7X/DTjz/IppO7e/V18erS1y0Xry79zZu/\ /edffvVtCAh3vUylkn8seP/+A2/f/sO7d1fN++l0erkNEWFVAwmBmGXELOvUH395dLCzM329\ qYhBASKCiBBEQARptdzd3WHv80d88/VXTx/vPvpzExHxTnWZoMkRIGYZqomz8wVn551mX9TP\ E+DlVgU0kQgBTQmA77/7FlPn6uqKD//mAPz2+x/8LxHoREOELBPMIM9zylKr+lDtzenpi8XR\ 82Nms9kobFeaMGQ3PlB1irykTIkQAg8mEyaxWsN8/oTDZwc8PZx3PtdRG/LJSgGaDFUlz0uK\ osDMKipiIIuBGKshVJ38YzEa2zCUAzQZmgxzp0wJU8XdKzJCIMZIzDImk8lG2Ibe9FiH3twp\ ioKyVMyq/Q6ZkMVQ/Q4BEanN6vfCNva5HqAoEkVRoOq10Qw8IATMFYITqATd7jsG214KUqmk\ lOoVgXklwswIwXGrJw1OkKwT/rHY9m6BqrZywFKIkFa3mkLcpRdb6Md2UIAmw+sB2oO1O4dw\ E+4bXT4K20EPeJ8nghOsMmVTT6gmbQlxs0FsTSqiwi3hcYiClWc51o2cKmbD2F57KLuF55IA\ MwP3ZrXujhud1Q+dGX3YggGCW5eUXgHN6kXAfSkaZlY/O1eqJhOWZTkK2yUBIsLDh93MZnoj\ xNyrMDdCjFibSlWbRLQuto2AKlksNr7hNtjqMraOdrDtCKiPypN1Jzo9fbGYz58AUBYJNWPv\ 8V5zLixHAdyk1+DtLVj7NnP0/JjDZweYOkVZoqpM4gM+25mOwnbUhaRdZrMZ+/v7zZnRJmcI\ 49vYrn0fWFXui+1WBHSw7Ulid2G70RZsA9uNBGwLWwC5ztf3KCebTu7uL+VT/z3/D4ypWLEx\ 98aLAAAAAElFTkSuQmCC\ """ ICONS["editcut.png"] = """\ iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABGdBTUEAAK/INwWK6QAAABl0\ RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAgLSURBVHjaYvz//z8DDDAyMjKQ\ AzZvPmTIxcWxnpWVVf7OnTs+SUmhe4DCv4D4Pz59ILsBAoiJgUKwatVORWZm5n0mJlryYmKC\ DNeuXRYHCvMBMTMx+gECiGIH/P79r0FTU0GAj48byP7D8O7dOx6gMAcoQNHV7tlzSmDz5v2C\ yHIAAUSxA4DAXkFBCsz48+cvw69fv7HG44YNBwR+/vx1gJWVfSOQC3IkC0gcIIBYKLWdm5tT\ /u/ff2A20HIg/oOhZvHibWDLDQ3V9d++/QgSkgDi50D8BSCAKA6BHz9+QS3+zQC0BBgKqA6Y\ Pn0t2HJgGtGXk5NgACZWkDAoGthADIAAotgB37//hDsA5BhQOoCB7u7FYMutrfX1lZSkwWKP\ Hr2AhTw4qgACiIUaIfDz528wGxQCsChITCwBWb7byckEbPm/f/8YPnz4zHDr1r3LQOm/sCwK\ EEBUcAAkBGCOAYWAtrYpn7i4/BZ7eyM9ZWUZsNibNx8Ztm078nXLltXTgUq/gTIQSA9AALFQ\ Kw3A2MzMbDx2dkGrHB1NtFRUZIGW/wVa/gFUWH3dtGl52f79W84Clb4BKQfpAQggqoQAKOhh\ USAlpVbh5mbBo6oqB06Qr1+/Z9i48eDXnTtXlx05shNk+TMgfg/KtSA9AAFEpRD4A6YvXrzN\ 4OpqwaOmJg+3fP36fV/3799YdvLkXpDlT6G+/wlLAwABxEJ5SfiH4dOnrwwrV+5mMDJSZ9DQ\ UABb/urVO4a1a/d+PXp0W9n58wexWg4CAAFElAMONGg6/vv/nwlYVzE6Ntw4gJyKQT5fsWIX\ g66uCoO6ujzc8lWrdn09c2Z32ZUrx3BaDgIAAYTTAVvKNAWYmRgm8IoyB0hoc/CzcTIx/P/1\ n+HsBJ1Pb97+qnJvvjX3QJMa15E3txnYZOzAFn/9+p3h27cfDGvW7P16+vSuslu3TuG1HAQA\ AgirA1bmajgysjKuMwzlExDXYkeRkzbn5Lu7+eOU1cXKnILcLHGCf5gYotMCwRb39y9nePr0\ FdDnxFkOAgABhOGARRnqjvzizPuckngZWDkYGb7e/Mbw68M/BiY2RgZeBRYGZlZGBmVnToZv\ r/50y1hxMCg9+cCwevVecG548eQhw+/XR/qAlp+Dpna8loMAQAAxIjdI5qZoCnBxM94PzuMC\ BT/Dg6O/GG5c/77n8LUvO+VF2URtdHiytHw5eZiBjvn55R8DOw+kJL9xlZvh9ZU/DJKKvxnm\ Ln5U1LH+xRao77/jsxxkN0AAoYQAMEEXmtgxCbD9/c1wec9fhi2HPvZXLXsCqj7fAfHXOZmK\ H8TUmdrEFZkY2EEl+VdgkfbxP8P/e98ZTIN5GT7c/8tgo8HrdqxVIPzff4Zz33782+jWcmsf\ NM9jdQhAAKFURsxs/+NVFf8yfHryi+HU+R97oJaDgvIRyEesjMwPP78GmvXlFxh/ff6L4dax\ fwzyTlwMHMAanluYkcE6mM/DMl3AXNuDO1NWhX3Hjmr1aaBaG1fFBxBAKIISov/kGb7+Yvjx\ 4TfDw5e/7wKFPgDxayD+DIpLZgbmCBF+YLELVPP55R+GMwcZvjJzMQKrWKDnvv9h4AVWsgLi\ jGA2iNZwZmdQ1mZLWV2o2g7Uz4vNEQABhCIgzAvM3t9+Mfz9+oeBiZFZFFpeg8rZf9PiNRJl\ Jf74CrD9ZPj06i/D8WPMX6duf1X25zfQ8h+/4fjz/V8M7679AtMgvoopE4O0JGtOvL2oIdAc\ TvSmGkAAoaSBB48YGazUfjFIAgPMSJE7qMZHmYWZmfEmFxuThZbsd1tz1R8MH14xMxy6zP11\ wYHXZetPvztbGgFsg/6AVMGPrzAwXLr8C5hoP+900OZ10dZhc5fVYWCQV2dgMLzK7b/w4Ovr\ UE/9hdkJEEAoDrj9jPnoy2f/rcX5fjP46f9i0Jdi9QOJC3D/Y+Dn+svw/gMrw97r/F8XH31d\ tunsO3A+h4QAsBACRtLlS/8v+7TfBgX3284NLzZsr1afJSTO4CgKDEsRXjZHoPhUIP6I7ACA\ AEKJglcff5UvOijw+eVLUI39m0Ge+xsYc/75xXDuLhfDpvMCL+cffAOy/AyskPnzC+KAFw//\ M+y7/Hk9tKYDJdwnz9/9WQASZ/37hwHYaAa1A7mAmBXZToAAQgmBaQcenIqxkAma8E24Rk3k\ nz0X+z8GcHnwge3es48/dq8/+3jrg9c/QBaDnPgWlDD//Pn3ERjX/L+AOf79l3+gRPsJmmh/\ ABPt81/f/4LTAjcnM6i/wI5uJ0AAoZeEf5aceHIESEcCsRC0+QzqYPyDlmifoDnjM7RF8//h\ U6bLwBCw4QE2MWWFOPWBYuuh6hmALXQ7NsY/4BC6dv/fZWx9BYAAQs8W/6EWgYrQh6BkAcQ3\ gfgWEN+FBvsH5G4X0HMP377+zyDO9ZNBUYQlOMxMHJTa2brDVY0Y/v3PleX/wQCS//Tt3wuo\ o/8hWwgQQNgqo/9Qhb+Jqarff/m75OJt9mgnrU8MnnofeX784F+nI8V3gof9v6af0Qd+tr9/\ GS7e5mO49fz7UWgOQGm3AwQQIxU6p6w9EWoHw43fWcoI/MSQfP2FlWHWEcHLNWvvZkND9QU0\ BMF1AUAAUaNr9ufGs2/p684Jfn78khlSJkAxiL/spNDXtadfdUOz30f0EAAIIGqEADgUgowk\ DNTEuWtVRP75inH/Ynj1lY3h2kuGPXuvv1148fGn69D08xY5akF2AwQYAAIlqdJ6KtTAAAAA\ AElFTkSuQmCC\ """ ICONS["editfindnext.png"] = """\ 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\ """ ICONS["editfind.png"] = """\ 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\ RK5CYII=\ """ ICONS["gotoline.png"] = """\ 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\ gg==\ """ ICONS["gotomatching.png"] = """\ iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABGdBTUEAALGPC/xhBQAAAAlw\ SFlzAAALEAAACxABrSO9dQAAAYNJREFUeJztV9GNhSAQHMw14LZAC5agNfnLrzX5SrAFW1hL\ 4D5UsiIgenrmkpvEBAVmBtbsAvCPCAyMNTD2FZ67xC/x3S1+ivcp8RR/ITufEg4Z2RkQneos\ ITMH2znche8ohXEcAcDKh5lrInJjlrYcg7ZtQ2asM5ApbrXWO6NE1AMAM9dSVMB2XRfl3RiI\ bT8z11rr9VWJZ2dkGd94/c6Ur1HkbL9Y5WZy3ztNyaGI6IP9TuxgYGx2CJi5kbGO4PQPnG1g\ WdUGTdOkxFWi77SB5MrKskzNS87N3oEUpmnCMAyX5t5iAICqqurXDdySui8bSMT9VgOhzAZg\ jnvG3B8ZsJH25r3ve5mQALi0nIXiQvXLzpxHMDDKrwUxciWKjfsGzMnIS0gWmDNnqDT7Gl8J\ g0oIWpmGl2ID8W1DOo4jpmn6iAIWxXoeiIUhlF4VEX2IyBnxx2it1ZH4qhk6EflhCJZfwNUH\ lRpzwL05E56uZFeR1Hr1WP6UiVdvR0/fNf4+vgET3cb2N0kZDwAAAABJRU5ErkJggg==\ """ ICONS["editindent.png"] = """\ iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAABGdBTUEAAK/INwWK6QAAABl0\ RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAEgUExURf//6f7IjP//3Mynfv//\ 2nhfQv//7f//+4BlR/vQotqyh9+zg7KOZ/7HjP//0sqlfbiTa//0v5h5V6GBXtKsg/jQo///\ /aCAXf//6oFmSHlfQ//msMqmfdOpe//xwty2jIxvT////+y7hv/erf/Tov//1s+pgNqte//W\ nv/6x+GygOa3g/vKlXthRMSgeMifc6GAXY9xUf/2wv/bo/nEi4ZqS6OBXf/tvui9jrCMZP/b\ pP/1wLiSacafdN+3i///2+rCl3pgQ//3xdKoe///9aGBXY1vT//5x4ptTdqtfIltTcWhef//\ 4dixiP/KkP/bpvrIkrGPav//z559WdexiOa4hvzQoP/suYNnSbSQaP//2I5wUL+Zb5aWlgAA\ AP///9v++JMAAABgdFJOU///////////////////////////////////////////////////\ ////////////////////////////////////////////////////////////////////////\ ////AHNt5EMAAAFCSURBVHjaYognAAACiCE+Fi+IBwggBkImAAQQQRMAAoigCQABRNAEgAAi\ aAJAAMEUxCGE4pBBPEAAwRXE4TABIIDgCjTisJsAEEBwBcZh2M0ACCC4giB/+ThsJgAEEAOM\ zy7toIbNDIAAAipgYGNi8WFXFOQUYY7DNAEggIAK2MQY7JkkXIxUfJmZMc0ACCCgAiYGPk0n\ d2srHS5+b704dBMAAgiogCUq3I+RkZHXREs/UpgH3QyAAAIqUJWzsBWIkdVWDzbl4MAwASCA\ gArMQ8QNo4UCnW0ipFgx3QAQQEAFdq4KHm4GMtxmuqxYfAEQQPBwEA31csQWDgABBA9JZU9J\ rCEJEEBwBQGW2OMCIIDgCpRwxCZAABFMDwABRDBFAQQQwTQJEEAEUzVAABE0ASCACJoAEEAE\ TQAIIIImAAQYAFmFY4BPZteNAAAAAElFTkSuQmCC\ """ ICONS["editpaste.png"] = """\ iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABGdBTUEAAK/INwWK6QAAABl0\ RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAVESURBVHjaYvz//z8DCDAyMjLg\ Aq2t8xYwMTHFQ3j/GaBawODv33+XT5zY47V16/IXQO4fBhIBQAAxgBzwH9lENFBTM33B9Olr\ /3///vM/NrB+/f7/lZWTr5uaOqgClbOSaj9AADEih8DNydoCf//9N/z15z/Tn7//GVe8yoiV\ khKLS0ryZeDkZMdpyNq1+xhOnrx8U+P/6kp9iY9f2VgY/wHNuG9VdfM+OMggGCsACCC4A851\ axmysTLuY2bnFuAUU2R48oGPYfcjE4a0tCAGDg42gj5ZsWIXg9CPYwyWCs8Yfn18xfD702uG\ p29/5bm33J4DlP4JxP+w6QMIIBYY488fxvVC2iYCCl6eQN5vhl83fzKwrWFi+P37NxgTAry8\ XAw8ssoMCn5GYP7byxcZvm3dPinbXez81J2vzgKFfmALCYAAgjvgxy8GeRl7HYb/f59CEtef\ PwzfvvEyfPr0jai4/Pr1BwMP10eg/p9gvpCGBMODo0IMPBwfdIDcm9BQwHAAQADBHfDt538G\ JpY/DP+h6Zid9S/DnTtPQDkAZxSip924aA6g/l8wA4GeYGD4/P0fKP5AmAlbNAAEENwB34EB\ BNcMBPLSDAzL5rGTnqvgDvjG8APoqV+/GRihlmPN5wABhBICyA6gCPz9B4oThp+//jH8/vMf\ r1KAAEIJgX9EJDaiwEdgugGa9ePnP1AI4AUAAUR2CPz6K8Lw5ZcCOB38+wcpzP4D6X8g/ufv\ YDazjDSDTRCXzfITZRtxRQFAAOFMA4TA5x+yDOyCXgzs7OzwBAlyBMgxf0X/AbP1PwZ9mX8M\ TJfvhCxatJk1Ls43ERQ5UAwHAAHEhAgBiAOIxf+ASRxkOQ8PNxjz8nIz8PHxMPDz84BpCOYG\ 1RUM6uqq/vPmbQRlJz4gZkZ2AEAAwR3wAxwFv4nG/4DlxP//hEMKFBJ6ekpAR6gEzJixHsMR\ AAEEdwAo7nD69tdPhn9fvyHwxy8Mf4FB9p8IF/z9+x8YUqwMRkaqDJqaKgGTJq1BcQRAADEh\ K/4HsgwZAxPGv1cfGP49fQuhYfjdJ4Z/334SlyOBUQCq6UGOMDBQZpCWlgro7V0BdwRAALGg\ FiKIPPP/M9CCz7gt+ctEXBTw8nIybNp0HBwSIMeAaCEhkQCgVAUoMwEEEAu2Uuz/R2CW+Io/\ R/xj/ktUFBgZqYHTwY8fv+Hqb99+BqL4gfgVQAChOuA3MM5/AH328TtBg/8xExcCrKysQAf8\ ZmBhYQFnUaSkB7KbCSCAUBwASmz/X39lIMbkv2AHEFb3+zckpEANHiYmRKEFAwABhOqAj0DL\ fxNXGP378xevO/8A5X/+/AO2EGE5A9wRMAAQQKhR8OErJD8Sk7pZUEMAZOivX3+A+C/YcphP\ QTmAiYkRyXLUAAYIILQoIL4oRg4BmMUISzF9jOwIZIcDBBDWXECUA/7+gVsOylqYljLgdARy\ CAAEEIoDPnz8y8DP9Y+4KPj9BxzUkBY1ZjDjcwSyAwACiAXRhvh/dNMpXmsL1c/A5hjhdPBB\ BOIT5NRNrCOQowAggOAOuPLsU/jPPzxTXn8SDCAmBMT0OBgMMHxOnCOQQwAggOAO2H3j5Usg\ TgEyK6HlNAs+B9RbFwLVMiYSSnDYaOQQAAggZEtAqeoDtP3Ohl5RoQMeHsH3IIOwW4bfEcgh\ ABBA6L4EpaqvUEygiOX4ATOIVEcgOwAggJjIbXeCKhjk/gLIMkh0INMQS9HFkaMAIIBYyG55\ A6vWy5cfMzx48AbeFkTGoLLhH9C7mGL/gR2el3BzAAIMAPtmf2zDNnayAAAAAElFTkSuQmCC\ """ ICONS["editredo.png"] = """\ iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABmJLR0QA/wD/AP+gvaeTAAAA\ CXBIWXMAAAsSAAALEgHS3X78AAAAB3RJTUUH0wkCFTMtVD42BQAABCFJREFUeNrtl11oW2UY\ x3/nJE3SNm3SJnWtG7O4mVZwraI4FaGdE2HshTlYBwoylF3JoHO7ERkKU+cQtRNleKGzMEHm\ xRwmaje6MqT1wjmc1Ora0u+1TdKvtEmaj5Nzjhc7tqZNY79gF/aFl0Dy5Px/7/N/eN7nSLqu\ czeXzF1eGwAbAJIQImtAW7XXiYX9WHhelqgBHAtjNJ32VJzW8FTsE9F7sBtQAX1NAFfdXicW\ XpOt1MsSjuOPdbC11E5VuTMtLhiK80dfiPO/l9AfKSYe5YIS7D4u9GN+A2TlAJeLvPtlmS+3\ 2EOOo89M8mRVGS5H7n+epuX6IKd+KGRwxjmsRAMvieThNkDJlo1FAD86vY2yxKGXH/mLg89u\ J9eWsyJPp6ZjfHwpQMtAeVgNDxwQ6pGfgMRSEHMAvsLvnMA1oPqdvbfZXOpieDSAoii0D0kA\ lDkU3IVmStwuStyuJSEUReGsL8jPQ6WjjLXuFrYPeoBkpljzfCWlGgtsavWBh27z7Y08boxa\ ga2Qmm0HLYKW8mOybUM2b0Iyb3qgeJy6R6OUlN2X4bE57N1ZRHtQKQs7q04Q5ygwmakmJCEE\ vvxvziDJ9fNHmGkmHmwT0uvXjPQl/uWlyae8uQP7/S+SY9+zrTjKvicKMJkXW3Wrx4+3owj8\ zY8L+2d/AtFFANTN7EKSW+5kIXmTiV8+FPmf9gHTQBiYNdKnGQCykbk8n3pyJ/btjcWFZvue\ h01okiX96WqSr9sUSIyfEeqR94CJhVmQqAvdBKpRZr4QqVfPAyFgHJgxTp4yxNP/dwfC5tNP\ P03elgsmq6PguR2LzWjthHAo0CaShw8Bw0A8vQa0VBHJyRNCP9ZsiAeBiCGcsXJ91nO16JoM\ SKhxCSVyTtVS9V1+F+Xu9Fi7DcKSudqnnqwlxxnEnBeb+1HXQpIQwg6UGKecNHzSlqpwn+Xz\ ecsWll5eCU950r8bD0NHz1jWRmQGLIZoMpv4P/eHz3z2KyTzC5vvLWOre+X9/9deUCKjEA+e\ Mnk8Hs2o8NRy+7dH+/5KF7srw5Hog4rsIN8Kqra8fb0X1MgQxEYahPktr8nj8azmElM9etPl\ Lm1XRTQSrkzKRdhtoOnZ92/9KkQGIO5vEDlvXwRGVwugA4qHK01dWk1FLDpdOYuLgtylxdsH\ EhDth0SwQVjevQiMAoHVAsxDSM1NXVpNRXI2VBnR3RntuDUUg+gAxAMNwnp6ThyYXetAogIz\ Qn7jFbTkpdh4J/1jkFLnd/dQGKKDEA82CNv7aeKAth4TURqEMtXJ4AQkU9A3Mg2xkSXFAdZi\ QQY7rjZ1abUVeiJUGY6pkBjPKr7eM2FaJkiGIB78KJv4smbCVSwTUAjcA+Qal9pYJvH0eWCd\ M2F0VZPxuWSHlTbeDf/3AH8DCP0QxXmU4WYAAAAASUVORK5CYII=\ """ ICONS["editreplace.png"] = """\ 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\ NjGv/Ac1w6RWgpLp/gAAAABJRU5ErkJggg==\ """ ICONS["editselectall.png"] = """\ iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABmJLR0QA/wD/AP+gvaeTAAAA\ B3RJTUUH1gkMEiAZoThzJwAAA2pJREFUWIW1l0trFEEQx//d88g+XGfHSGJiWHyggrrm6N2z\ 4MFDwJvkKwgePIsY/CqCBz+IJwXxkE1AiJpssjgxzs7038O8H72zUVOwbE93Tdevq6qrewRi\ ISlev3n5kEQLZyhC4OT5sxfvhRAEADMZ2Np69Whj48lbt39BniXAeHygtrZePQbwrgBAyY7j\ 9GWrtXAmhkkCABynLynZSfpN7Rv/2XgCALAwdqYAkeHIaAZQlFqA3f1fGHvBv5guAQB317og\ y+vXABwdh/g82tNO3+1GIfS84wYAwHGcqH25gzonaEMwGo20AIPBAACws7Oj1UlkOBwiCQEZ\ U80D0CSu68J13fiJuXnL7Wo4GgFMA5AMtcZtU6BlZeWiODmTAIAU6C0YWHas0wEEIaCEoQXw\ A2LineD42IsB8qORIdu20G634bRNXOxZUQKS8yXhPOJ53swccBwHa2traQ1IAMoEfw3Q7ztw\ nGH8NDsHCgDzFCLLELh57UrNSOTCbqcL25RZtCnieQlCAACuL7VAMtLLAQhRnLEWYBoq+ArI\ 7+c8xO/JzypYTqfT6WDx3Lks7nkPlAi0Idje3tYNNcpgMKi4PfUAGgCSF9bX782Ia1m/2laK\ ub5TJCFJSAEsmDI7SKK/eDxZQbk/DRhA4NPXqEyfbxu4dN4swOgBYsogJPZ+HNQUjmoxqTvl\ SOJ73L660sdyL6kpVd2SB5hSjkbNdX4eubrSTw3XwdaGgCSGw7univvnjx8AAjdur5d0sjtB\ VqY1AMlhJYWAbchsgmQQ1VgjzuqTb19AErce3C8k3oKZQCTxbyhEJBEqhW/74wLarHxot1tY\ cnsgicWukTNYNN4cAmalc3d3t6Ksk9XV1cKxm7o757q0f55tSAJ37tyea78n7WjTlVeqazcA\ rDgWLnSNXCUrGivfcklgr7zSXLuYhDNyINIjei0DPcgCQN5o3ar29w9memC+WzFjxnL51Kw6\ H+vNzac5nep4XRGqAswwWp24zt11q643rAGoGtUnlL6vadV1APZkPMHh0aFy+25622SlUT8p\ Nf11cjgeq8l4AgA2AD89nJPPc98PukEwNab+1PR935oGgRkEgRmGoaHC0FCKUiklCRYOdgFB\ KaWSUihpGKER/2zLmlqWFVi2FViWHdi26eU/z/8A81bdkxXVzcIAAAAASUVORK5CYII=\ """ ICONS["edituncomment.png"] = """\ iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABmJLR0QAAAAAAAD5Q7t/AAAA\ CXBIWXMAAAsLAAALCwFtBIS3AAAAB3RJTUUH0woNCgssPB80owAAArxJREFUeJztlr1rFEEY\ xn97X0m8S9SoRBG1ONMIKYSAVUqFwGCTItglf0D+mpAiVa61sBtIYf4DDwwhELCLFjEemJx3\ 8XJ7d7sW74y77leWgIhwDwz7NfPO8z7zzDsLY4wxxj+Gk/HNzzG+Agxz9k1EKeW9o5S6crDW\ ehb4AVxGSSwdLNXqR/UOQGO1UQZGSUTTCJS01nPAI+C26RdWy0cyfwh4wMA8/0b9qN7ZXNlk\ 490GwCzQSSIaI6CUyi2n1voFUIiQY+3tmr+1ssXe+Z599Rj4YkgOsgg4WutJYBqYAW4Ck0mT\ mGAdwEVUiE3eGrTs67tAKyFGnABirBpwwzy7iCc+JCjwLJr59so2x/1jik6RslO2nyopScQI\ +EAfaCPrZQcVtNZPgaoJ5iCmGtns7eT73X36fp+iX2TKn4rOF0MSgQEibVcp5WYNNgoUgEJj\ tVE7XD/szr+Zp1Kr0DvrcdG+wP3kkpR5GgFLYkjghyriA3tvFfBMP7v+5eZO8znwce7lHHjQ\ a/XonnSzcqBwxbcJxIy3EGNWQiRtsxgAneZO89Xp+1N6rR7D8yGe65GFNAXy4j5i0hHimRai\ xPLi+uLuxJ0JWx1SY8Z2QZ4KaKG1LhGoOAJ+AicAzZ3m8sLrhd2rYkTNUUbkthWwnNIPJLfv\ wGfgjKASlhCvPACemHht4MiQ+8PYaaXYwjeT+0qpWDYplXAIXJjJPOAesrVjZTgpMwdx+4y5\ OqYVEANWEWM6ZqI28BU5kEaRWCWCneOT8yzwTScrp6OU6kdZW0QrYQRDxBOX5jnxNMz6H4Ag\ 82mkPNutaCthFzg116gCuZDqgTynYqgSXhtpCthDyWZeI1h7B5HSQ9b1G39BgfCpWCXYjuHq\ 55l27d+xLAJJp6IlFibgmn7Z9TYDWSZ0gGKoT/SXzF4T3T3Gf4Nfi0P5Rstj6dwAAAAASUVO\ RK5CYII=\ """ ICONS["editundo.png"] = """\ iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABmJLR0QA/wD/AP+gvaeTAAAA\ CXBIWXMAAAsSAAALEgHS3X78AAAAB3RJTUUH0wkCFTYqty1X4wAABBVJREFUeNrtl11oW2UY\ x3/nnHy1SZtk7Wo/trVFl6066CpsyMoc4gaCp65leKFebJGhIF7sQkEmrAzKcFgQdjEUNChi\ L8qYnT2DCYrCXBER3HTMNcrsZ9KWJEubpPk6H170dDNt+qXVXdgXnpvwPu/zy/M+z/99jmAY\ Bg9yiTzgtQGwASDIsrzshmt7+v2inYCq89n+gbZXgQyg/ScZ+Lq2369GCRzz3kSf4KXLm/s/\ BcoB6V8H+NLb79ezBN5oucXrHbv44HAYm06HUtYXWE+IogCX3f1+IHDiiZscbH2UUBzqt9bw\ 3pFxrBapXXFdWDeIRQBK+Rd+wzAC/pZBHt+9i7Eo96xicx2dz4yCaGtXnL3rAlEAoLgu+jH0\ QHvTCE1NOwjFWWTOTVt58ykToqTnH0NIPp9vLriz149AYF9DnN1NtSQyLGnY3Gx3T3AjXLYz\ KD23w6devALkAeNvtaFS0uNHEAP1VVb27bSt2nl0Ks13t3XQMn1y9tjLwMxaW1Tg+bgfCFR4\ 3ex9eO0pHIrAr0PTcxC542uGEDgSWTJtzdsrqCwr/G3gN0gnosUd0qGjsnjycyC52uuwkI+3\ kk8+gp4pLCZnQ1cojsvjLHSoKofhhA7ZWA+52E+AiiAagCFbu64DVkBYCUCxfehHEDstsn7i\ RySCSDhMx7kN+vkXIwn25tRCx1ovDE+6QMsdJjNxRXZ0fw9kzYA5IL1icMv7pzH0U+jqLcnn\ 8+nmAbNm6pJAMsihCtTU0xVeNwag6fetym1lctqwYSk9FMw0f+OTrg4C0+YZS3aDIp5rDEpt\ fWAcBR1mRztFc7NmOt63bOwjdJXbYVC1QgN4rNENFpcLR7Wi0B1QtK49f83ggsAdinjuE+AO\ unoAXYX8zAXZdub6cq+hpNB9HsnxSmNDPaX24ptGIpCIjYM2C4Y+BgwDN4BtgBtB3A+AzQP2\ SkiNgJockIW33gbClmWuSiMzcRpH1bN/jITrGrfVYCmid9UeqPbUEUtCNGlsQZ3dgpZpBcC+\ CUkUqHZDLAXpyCAY6h3iP7+LlwgwtdI8ICm5k09i81zC6il7qLqGUtvadCKnwvjkXchMgTb7\ lSydegeIAONA/J4UL7EMn3Q1FMy0/IDAwVQyURZXvTjtQkFRFrNUFkKxPInoEOSnU6TDZ2Xb\ mY+BSSBsFq264kRkFpZdmTneiLPhLJKjDdEyd5/WcuzWwrrLZtKgzkI2Augp8slLJH/vlb09\ IfOfR4HUvFquBmAewgq4leRrzdgrX8BSegDBUkS89RRa7hfyM9dIj30re3umgDhwF0iYWqGv\ eiZcWBNACeAyn2EnYF/wrBuAas6O87qSMrVGWyzFa1uaeVjGvEObCbWw/3UTImfqir5kaje+\ Df/3AH8CAs3OiTI23BYAAAAASUVORK5CYII=\ """ ICONS["editunindent.png"] = """\ iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAABGdBTUEAAK/INwWK6QAAABl0\ RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAEgUExURf//6f7IjP//3Mynfv//\ 2nhfQv//7f//+4BlR/vQotqyh9+zg7KOZ/7HjP//0sqlfbiTa//0v5h5V6GBXtKsg/jQo///\ /aCAXf//6oFmSHlfQ//msMqmfdOpe//xwty2jIxvT////+y7hv/erf/Tov//1s+pgNqte//W\ nv/6x+GygOa3g/vKlXthRMSgeMifc6GAXY9xUf/2wv/bo/nEi4ZqS6OBXf/tvui9jrCMZP/b\ pP/1wLiSacafdN+3i///2+rCl3pgQ//3xdKoe///9aGBXY1vT//5x4ptTdqtfIltTcWhef//\ 4dixiP/KkP/bpvrIkrGPav//z559WdexiOa4hvzQoP/suYNnSbSQaP//2I5wUL+Zb5aWlgAA\ AP///9v++JMAAABgdFJOU///////////////////////////////////////////////////\ ////////////////////////////////////////////////////////////////////////\ ////AHNt5EMAAAFHSURBVHjaYognAAACiCE+Fi+IBwggBkImAAQQQRMAAoigCQABRNAEgAAi\ aAJAACEpiIPRyCAeIIAQCuLisJoAEEBwBXEa2E0ACCCYgrgwY+wmAAQQVEGcvH8QdhMAAgii\ IE7NQZodJoQKAAIIrCCOWYRTUJHdh4WJjSEOzQSAAGIAyzP7qhi5SDDZM4ixoZsAEEBABXF6\ 3vxcOlbW7k6afAxM6CYABBBDfByPcKS+lgkvIyOjX3gUC7oJAAEEMoGDwzRYXVs2RsDWQk4V\ 3QSAAAK7gVUqwsY5UCjaUDzEHN0EgACC+IJV14xbxsDNQ8HVDt0EgACChoOjV6go9nAACCBY\ SEp6KmMPSYAAgseFZQD2uAAIIERsKmE3ASCACKYHgAAimKIAAohgmgQIIIKpGiCACJoAEEAE\ TQAIIIImAAQQQRMAAgwAc39jgBib/28AAAAASUVORK5CYII=\ """ ICONS["fileclose.png"] = """\ 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\ AA147xggwADtTBaocZkPNgAAAABJRU5ErkJggg==\ """ ICONS["fileconfigure.png"] = """\ iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABmJLR0QAAAAAAAD5Q7t/AAAA\ CXBIWXMAAAsBAAALAQE2cE9bAAAAB3RJTUUH0woNCgoG/r/MNAAABM9JREFUeJztl89rG0cU\ xz+zO7Or1TrR2pKNIIIkoGBoDznkkNxyMSanQo69pOdQiimBUppbCoFiekxskvwNSS6F0FMP\ hRCIHUPimPyASsGNpUiJflhKIq92pwdrt5J/xBZ16aVfGObHzuz7zpv33ryB//EfQww5/k+h\ dxV048YNffz4cYQQmKaJEALDMA60XLlyhevXrysgiMjInnzD8zyWl5cxTXOgSCkPZExKSafT\ AUgCHWAD0EaPgFJK/Uta34ZRIAGYkQYEYNq2zePHj5FSopRCSjnQVkrF7WhHSiksy9pxztZ+\ 3wZdoN2TK6IjQErJzZs30XqbncS4e/du3D59+jRSSkZGRpBy8zdaa4QQhGE4ULTW/f+VgNHf\ gX1a/dTUFADVapXFxUVOnjxJp9OhXq+TSqUwDIORkZHYiHchEEH0E9gTWmtWVlYAKBaLABQK\ BYIgIAgCtNaMjo5SKBRIpVK4rotSCq11TGKr8KEI9AteXV0lnU7TaDT48OEDQRBQq9VwXZdc\ LseTJ0/I5XKMj49j2/ZuGhiewNOnTwFot9uxeuv1OhsbGyQSCQBev37N9PQ0Dx48IAgCMpkM\ lmUNT2DrAq01L168iPtTU1MsLi7y6NEjLly4gNYa27YxDINnz56RyWQACIKAbre766Y+qYGI\ RKTCW7dusb6+ztraGq1Wi1evXlGv17FtG4BMJoPjOGitCYIAKSWGYQx/BNGCqERGFIYhyWSS\ o0ePEgRBPN80TbTWJBIJXNfd5gE7GOH+NdBv5f1aEeJvz42ERXOFEHS73W0a3DeBMAxjlwNw\ XRfTNEkmk7FrffHLZ6jvsvi/l5hvfst09Ruy2SzNZhOA9+/fYxgGjuPwqTA/QCBiurKywtLS\ ErVajWKxyLlz5/B9n1wuRzqdRimFPxnw/fjX/Jj/gU6py5kzZ1heXqZYLFIul8nn83ieRzab\ JZPJ7EpiRwL5fJ6JiQkKhQLHjh3jzp07nD9/nnK5TLlcBsCwNT9XrmGsa9Q7mPt1jrNnz3Li\ xAkmJyfj6xzg48ePcXtPApHBWJZFNpvF8zzu3bvHwsLC4MrPBetBCVUzMHv26Ps+Uko8z4vj\ gmEYSCkJw3BvApGxhWEYx/VEIkGxWOThw4cDC+0jmk4L7FqICTSbTcIwRAiBZVmxO/ZfUHsS\ iKz5/v37LCws0Gw2efnyJZVKhfn5earVKr7vI4Tgpz++wmqDWzYwx0yeP39Os9nE8zwcx2Ft\ bY2ZmRmklDiOg2ma+ydw6tQpLl++HI9fvXqVt2/fopRibGwMpRSJJcGhdy4iaGGGIRcvXmRu\ bo5SqQTApUuXqNVqOI6DlHLAbfc8Aq01t2/fplQqsbq6CoBSKr7ldtvNzMwMhw8fxvf9WFOW\ ZQ1/BFEYTafTJBIJhBAIIVBKxdZsdQyO1NP8SYtux4dDm0mN67pIKeOApJRCCDEQOfckEEUt\ 27YxTXNgcTRn9svfaLfbvEm/odVqxZdRlKYBA6F8C4GB9AhANxoNZmdnqVQqOzLdCWEYDoRc\ IUR8Ae0DGnqJYY9ICpjo1bLv20FA90oXaABvenVX9n302cxWYchEZQh0ezL8SG60QwFYgN2r\ jb7xg0B05iGbD5L4YdIvQLD5WOgndZDQfXX8NPsLzuabzPO9pmgAAAAASUVORK5CYII=\ """ ICONS["filenew.png"] = """\ iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABHNCSVQICAgIfAhkiAAAABl0\ RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAATrSURBVFiFtZdbbFRFHIe/mTnn\ bLdlW9vabUuLbZHYFixoxNQQMFGwkRiR1hgwMTYmwIvGV01QefJFE6I+ob5YuRiNdBVFQwmK\ JYWSGi7BWwu2jS1UehG7S9fu7jlnfDhL71vapvySyezMzszvOzP/mTNHaK2ZqpaL1/qAnGRR\ TGswk5LDaFf3R6OREzXrK+rn0s1IUZ/2UEXQskw12UNDLA7xGMQSXh6PQ+xWHgdbDuS5rvN8\ qKltBHi5tubh6U84QXKmSiFASjEtKSVI8wl8PkGaJfBZAmtibgqUNJzi4DJTSeMlEPtDTW1q\ Jo9ZAWbtIMGywDTBtMCakguJm5FusaJ4ZZqSZh2IUKipzZovwKzrrtQEU3M8N00wFH7LB1kB\ H6X5K/1K+jahxbFQU1v6fABuK9NImk+ZDSGQPgv8fggsMSnOrfQr6a9Gi+ZQU1vWogHAzEsh\ pLdMfr+XMjIMglkVfiUCVVqL1lBTW97EMVLtgjnLNL3doV1wNSgpaDnfyeCNiFc/lrSltVuB\ EKcbGhoq6+vr7UUBkHIChIbiYDary7MBuHkT2jvAcbz/wiPDnGs/mVfgZw/w5qIAQDIoTXBd\ LwFE/4POLm9LK+UBSOmVJz3AYgAAGIYXE4YBo6PQ3e3VW+ErKGcEpTyQqVo0ABifhZ6epPlf\ JygLv0Je06NIHNQMbosKMDo0QN/hvTj/dGIoED2nyKjZSO7j5cQjV1EKigdOm0UdjatmB9A4\ 8zWPx6Gv4XXKnh6k8Oou3N6zk9ZbKbirdTfPPRNOX7c2UdtYI9amBBBCpEciMDLivWhs2wui\ VBoZgYEBcIofQUcjZG3dSuGN3ajrF8baZLa+xX1bfBjllUT7huKNXfRAql2QJLdtSNjjWwzG\ I1m741Fv2159+vqddH33GmVPdpBVuxXpfu4NZxmU1uWiikoZ+vQL/dW1Bw4duHz2emqA5JQp\ g0mHCcncdSdDKQVu/2USf7fjL9tA78fvULxDEHh2O7g2vvvXoKVFeP9BfvjtnnhJYDjt8nax\ eYlJs5jpQtJ6qS+6annQbyg1ZjpbSvx+nCWJ9/GtqQLXAxVCIrMKQPkA0JEh3OGwd3FxgYTN\ 9Y8O9aaeAQmGur251pCQEnc0ngwWz0DLqZteg2N75i5o28FJxJ3Zl2COAOlVGwl3F9B/oR2t\ wTyzl6U7qtGASETRdhzhzyR66nuOni+MSeV+k3bx8IFPOmhJdQ5o71Y0DnK7lL18Ffnr6gj0\ t7B0VzUqv5TIZwfBTRD/5RJ2ZzuB7duoeXDQUln5w1uO6a8bu/TgnA6iVDCG4QWqkSzHmveR\ s0mjgiUMH25ksLfIexrboft4DPvPdnK21YnHgudf3LdBZMMCTsKpMMYEoFjXzwhp8O+XjQzf\ +zbpJVVj/TI2v8GVn0zily6SVpBnGpIVCwKYDSj3hXe5dqaYxNoPKVqzDimTB4rWSCXwP7WH\ hm+D0SMnRWhnM+dSAuixW/78ZAWyWbblVYLLy1EK/JXriRw5ytCPf5BxdxAlYbiw2o6trv1V\ a+3AIt0HZpIQEFi9kXBuCZlP5JCR7iMyMjqt3R0DuKXMohVjv+Wdfh0vRCnPgTtlKKZ8cqRc\ AsdxseXcvkvnKsdxp9XNCKC1tj9oaHId7Y4TLGhOJneyDOVoV9tzAKCgIG3wPcC/ENv56H//\ Ng2EWMcCswAAAABJRU5ErkJggg==\ """ ICONS["fileopen.png"] = """\ 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\ QRt4t1DIAAAAAElFTkSuQmCC\ """ ICONS["filequit.png"] = """\ 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\ f/+e/xfF1j2asPNr6AAAAABJRU5ErkJggg==\ """ ICONS["filesaveall.png"] = """\ iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABmJLR0QA/wD/AP+gvaeTAAAA\ CXBIWXMAAAsSAAALEgHS3X78AAAAB3RJTUUH0woaFjotVbc2tQAAAB10RVh0Q29tbWVudABD\ cmVhdGVkIHdpdGggVGhlIEdJTVDvZCVuAAAEiUlEQVR42r2Wz2sdVRTHv5O8JI+XXy+mqQ1p\ k6UoakDtSkGo1WVF0P/AtbgQl0I34qIi7rR/gF01bkRBMBKsqwptCNiKKVQamuYZ25f3Y97M\ PT+ui3vf/MpMIYXmwuXeuXNnzvee8zlnJsBj2tW13ywRg4wgCABmATODKB3zcwZzdsyvEQla\ +wd49LCLzZuXAwCoVRlfW7tm33jzZTArRBWnT80BALb+2snte+m50zhK+2ljC2wIX30xbX9d\ /zKoFEDMiGJCP4zBkgo4++5FLC40Ua+PoT4xhhs/XDySgO1/WhARnHv7Ndz6831bLYAEIgpi\ Nw7bZGMCr7y4grmZBpqzDRy1sX+fMQa12kh1CJgFLOo6S7I+PlbD4skmmjMNNGeeTACLot3u\ QFXxGA8wRNV5QlMPnFqYxeLJWS9g8sgCiAWdgy4GYQRVi+Bp037zxrc5AZ9+/h3Cbh+dvX38\ /OMfqD1t2s+99QnWf7kEVUWr1Uav0wMxgwy5EBwH7YuLH+Drbz5Gvx/BGAYzwxiCtRa146J9\ /fctF0ZiEDMMEVQtasdFu4mdYfadKPHA8dBuiJxh9rAadgKYBcIKlnwI3nvnVaw+v4ypxgSm\ JutHFtBudxB2+4h9rJ+dnwEx4+5OC0wuBM4DzGCRxGXD9tlHF/AkrYr2C+fPgllw6fL3Pl19\ FjAJRCyIFSKKv+/uQdVCrYX1o6rCWvh1hbU22aNqk2siwv6/7VLai7WDiTNZoA5AUcWt7d3c\ tXhAs6ObO+Es7qXdTh/9Xj8tYAXacwLEiUtDwOLD4IpRlSEpCIkNod8LEfZDb8BV0zLab99+\ gJWVZ0BMEHZ7HITeAy4EUnpizoiJY4M4NhiEEeKYUsPEYEqNF2lfWJhK9kjeA5LUABHF/NwU\ VBWiNhlNTIgig8EgwlgwhonRAI2xUWeEJBNbb7SE9l5vgOnpCTBT8q1J64A/HbPi/OsvJESL\ KMIwRhwb/5Db48RKZk0O3S/S3u1GqNdH/Vc2I2B48mIhimNCGEa5lw67iPgfFim9X0b7iROT\ Dk5hqAiEM1nAmThba30aUemL80Kq7xdp395uYWlpFsKOAREGkA2Bf7DXG+D69XsuI0QSMPNj\ arzsXhnt8/MN7xVKQp5AOPwahv0IUURYXp4txFkLMddDDGQPUUb7yEjgQyBQHoZgFDUmBoti\ MIgRDiIwM9rtMHPCvMGq6+FcREppv3//HkQE/dYDqFpwHMPahvMAMaPXC5PfqXZ7kHNr1tXZ\ 0OTX03kZ7VeuXIO1QOfhCADA2gZENoIakbiiYgyYBMYwzpyZKU2tYiZUQVlG+87O1aDs41Uj\ Q4ii2P0w+GKyubkLEYFq1rVa8ErqCdX8WhntVa0WGwKRuLQjJ2JpafpQjmdPnAUu6/60Rhym\ vVKAI5Z93jsGxsdHwGwRBMMeIAhG/EncGhAACBAEQTK31vUy2isFmIFB779H6Oy2QMy4c2f7\ UI4zawlsWloPhmEo0l7VAgBYXf3QHhz0YAyj2Wwkv2bWWt+RuDF/nc6La3t7+34/ILIRVAn4\ H3oJzBj8WcbGAAAAAElFTkSuQmCC\ """ ICONS["filesaveas.png"] = """\ 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\ uuUpCPAAAAAASUVORK5CYII=\ """ ICONS["filesave.png"] = """\ iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABGdBTUEAAK/INwWK6QAAABl0\ RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAATWSURBVHjaYvz//z/DQAKAAGJi\ GGAAEEAD7gCAAGJBF1i3/sj/37//MPz+9ZeBkZGB4c+fv0AM5P9G0KjsP2A1CBpVDMR/9eYj\ w/t3nxkunJ/JiG4fQAChOGD9+qP/be31gZr/Mfz9949BRkIQLH755hMUTbrqMiT5cvvBywx/\ fv1mcHIu+b9vbw+KIwACCMUBv4G++vHzN8PXbz8Z/vxFOMDUv5FBUlSAgYODlYGDnZXh/JZG\ khxw5+Erhr9//zI4uZowSEqG/n/+fDXcEQABhOoAYHD9BVr8+w+EhgFuLnYGIx15BkE+LgYB\ fi6S4/kP1Lxfv34xsLCgJjuAAGJBVwjyORgD2TDAxsrCICkmwCAAcgAfeQ4AmfnhwyeGf//+\ ocgBBBBaCPwBxz04JJAUSojyAx3AD3UAN8kOAIXop4+fGb5/+wF0AGq5AxBAGCHw9w8oBFCj\ INDNmEFfU46BBxgVPNwcJDsA5PNvn78y/AQmRPSCDyCAMBIhyHJYkMFAXZ4fWXkcFNyvXn1g\ +PLpC9js30AHoEcBQAChhgA4Ef4HKv4HDoHbD16Cg+wf0NX/oTTIAJAnIOL/wD76B5f7D+f/\ /v2b4c3rDwxfv/4AJr4/4HLjF5YQAAggzFzwD5IAQfT1O89R+H+hCRSZhrAhDgfRoHT0+dNX\ hq9fviIKMFCBBHLA798YaQAggDCjABz8f+GFES6L/qI5BBS/X798Y/j29Ru0JISUphAzofzf\ mCEAEECYUQDKBeAo+IvVx3+QHPPz5y8wBqXun8ACDG4xiP6NsBzmsd/AqEB3AEAAsaBnF1gZ\ ALJEWJAHHOd///2H07+AFv34AbT0+w8GVkZWBnZmRgYuVmaIJb//ItULUEuB9IMnr8DR8AtL\ CAAEEGY5APUdKApcrLXgciDxb8AiGuRjSAUFUQNx7F8ksb8Y8j2z1kMdhpkLAAKIBVuRiV4Q\ gYL3GzCYkQ2F4b9//0KL8L9Y5ZFrSlAooIcAQABh5II/SPEMUgzJRr+xGozqENzyYAf8hUQL\ ugMAAgh7FEA1fvnyneH06ceQHPH3LzxhotIIy7HJycsLAdPBb2AJC0kP6A4ACCCMRAirDb8B\ ff7jx28GOTl+tHj+hxbn/zDSALInYDniL44QAAggtGz4Bxz837//ZPj2/QdYw4cP35B8iGoh\ Lj6MDcK8vOxA/m94ywrdAQABhBECoGD68uUbvDn14cN3lGBFDmrkqEEVR7A5OJihtSx2BwAE\ EEYiBBcqwIYDKNhAZbisLB/WrIWeE3AlSkjC/sPwD+RQLA4ACCBUB/wCFTI/gYXNH3hhcvHi\ c7AF//4hB+0/tFBBhAS4wEISk5bmBydAUBoAYQYGVAcABBCKA0Dl+W+wz39DKhAglpbmxcjj\ yD5GTnDIwY8oI/6A0wCsgEMPAYAAwsiG4CITnO8haYCNjQnI/g9sosMwIxAzQX0CEWNgYARj\ kByM/f8/BEOiABgyf2BRwIziAIAAQnHAr++/GL68fc/w6fkrcGK8e/cORh7/8+cflsT2D2t5\ AIuGr69egKvhPz9/Ah2A2qYECCBG9CAxMEj9//HjF3ACFBDggjfNQOogmAEejKh8BBtd7OXL\ N1D1oOL+AEq/ACCAGAe6cwoQQAPeNwQIoAF3AECAAQCAcO8MtwKEUgAAAABJRU5ErkJggg==\ """ ICONS["helphelp.png"] = """\ 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\ f5KTsrpteUOGAAAAAElFTkSuQmCC\ """ PIXMAPS["icon.png"] = ICONS["icon.png"] = """\ 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\ """ ICONS["window.png"] = """\ iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABGdBTUEAAK/INwWK6QAAABl0\ RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAMLSURBVHjaYvz//z/DQAKAAGIE\ ERaTHhr+//dX8O/PH0z//v5l+Pf3H8O/PyAMZSPzQfQ/CP0XLP+f4T9I7O9fFD1/wfz/YHGw\ PFzN7w//zqefB1r7D4j/AwQQo8XkR4mMTMzzRPnZgAYCNf7+D6Gh+M9vGBsoDmT/AauBioH5\ /+FsFPVAc/7ALf7H8P8fkP79h+Hfz1cM/3++yme4UjgH6IAfAAHEArLcVE2AQUaYi+H3bwYw\ /vULFf9EZv9Eo0HiPyF8OBtI/waGLdANDP9AYcwKDW+gIxi/n2D4z/Z/IpC3CYhfAAQQE0gc\ bvkfiAP+AGlgSMLxXyQMNIMB6CEwDcZQsX//Uen/IIyevJiYGBj/i8B4IAYHQACxgFggn4As\ BTkAmf77B9VyuCP+QhyB4qC/mA4ikL7ZgJgZIIDADrCWRPIBzNC/CIP/4sHoIfQH5nhg4gTx\ YbkMQv1n+PmGi6FrGcIVAAHEAhRjcJCjXrYCWfgf7Jn/UPZ/KBsi9+kjJ4oDAAKIiZrlADbL\ /0OjAtkxyAAggKjmAFyWo/JBDviHog8ggJj+//tPR8sZMBwAEEBMDBSGAO5gx7QcWxQABBBF\ UYDPctREyIAzCgACiOwowO5TdMsZMNSAsicyAAggskIA2WBUy/DxYaGFGgIAAURyCKBnKeIs\ Z4BHFboDAAKIpBDAlrhIDQn0RAgQQEwMRIYAuuXIpRspIYEeAgABRFQI4LOc5JD4i+oAgABi\ +v+PVMuxpQPSHIMMAAIIbwhgt5xCx6DZARBABBxAyHLc0YIrjaADgADCmQ3xFa2UhAR6EAAE\ ENa6AF+lQklIYIsCgADCCAH8NRpllv/DEtoAAYSSBghZjt8xuIMdWR49xAECiAUUAp8/fwMX\ ELCi8j+UhvD/o8hhVfMfSRxqCdwa5GgHiv8GNRiRAEAAsYBcZ9J0C96BAPVs/qL3dJB7PMg9\ JjQ59EKGGAAQQKBuAx8QywIxP527hR+B+DFAAMH6LZxI/Rd6AWAXiOE7QAAxDnTvGCCAmBgG\ GAAEGAA4876roWF48QAAAABJRU5ErkJggg==\ """ def getIcon(name): icon = ICONS.get(name) if icon is None: return QIcon() if isinstance(icon, basestring): pixmap = QPixmap() pixmap.loadFromData(base64.b64decode(icon)) icon = QIcon(pixmap) ICONS[name] = icon return icon def getPixmap(name): image = PIXMAPS.get(name) if image is None: return QPixmap() if isinstance(image, basestring): pixmap = QPixmap() pixmap.loadFromData(base64.b64decode(image)) PIXMAPS[name] = image = pixmap return image def isAlive(qobj): import sip try: sip.unwrapinstance(qobj) except RuntimeError: return False return True def main(): if not hasattr(sys, "ps1"): sys.ps1 = ">>> " if not hasattr(sys, "ps2"): sys.ps2 = "... " app = QApplication(sys.argv) app.setOrganizationName("Qtrac Ltd.") app.setOrganizationDomain("qtrac.eu") app.setApplicationName("Sandbox") app.setWindowIcon(getIcon("icon.png")) loadConfig() for name in KEYWORDS + BUILTINS + CONSTANTS: if len(name) >= MIN_COMPLETION_LEN and not name.startswith("_"): CAT[name] = "" try: tip = eval("%s.__doc__" % name) if tip is not None: CAT[name] = tip[:MAX_TOOLTIP_LEN] \ .strip().replace("\n\n", "

") except: pass for name in MODULES.keys(): CAT[name] = None if len(sys.argv) > 1: args = sys.argv[1:] if args[0] in ("-h", "--help"): args.pop(0) print """usage: sandbox.pyw [-n|filenames] -n or --new means start with new file filenames means start with the given files (which must have .py or .pyw suffixes); otherwise starts with new file or Python shell depending on configuration Sandbox requires Python 2.5 and PyQt 4.2 (or later versions) For more information run the program and click Help->About and Help->Help""" return if args and args[0] in ("-n", "--new"): args.pop(0) MainWindow().show() dir = QDir() for fname in args: if fname.endswith((".py", ".pyw")): MainWindow(dir.cleanPath( dir.absoluteFilePath((fname)))).show() if not MainWindow.Instances: MainWindow(isShell=Config["startwithshell"]).show() app.exec_() saveConfig() main() ### Ideas for improvements ############################################## # # - Make Indent & Unindent smarter, i.e., Indent should insert _up to_ # tabwidth spaces to make the number of leading spaces an exact multiple # of tabwidth. Similarly, Unindent should remove _up to_ tabwidth # spaces on the same basis # - Recent files handling # - Add tooltips to all ConfigForm editing widgets & improve validation # - Add tooltips to all main window actions that don't have any. # - Goto menu. Use submenus to reflect the structure of the code, i.e., # classes # Class1 # Method1 # Method2 # functions # Func1 # Func2 # InnerFuncA # This should be doable when reading, but trickier to maintain when # users add or change classes and defs since the right "parent" must be # found, and the name put in the right place in the tree # - Robustness? catch exceptions better, etc. # - Help? Handle help() in the shell? # - Tools menu? (modeless delete on close dialogs); e.g., # Regular Expression Editor (similar to the one I wrote as an # example for TT); support multiple syntaxes, e.g., re, glob, # (fnmatch?), QRegExp, and QRegExp wildcard # - i18n? use a custom dictionary-like function and keep all # translations in this same file; or embed .qm files in base64? # - Autosave? If not zero auto saves every N seconds to # .sb$.self.filename. On close the .sb$. file is deleted after the # save. On open, if a .sb$. file exists, rename it self.filename#1 (or # 2 or whatever to make it unique) and then give a warning to the user # that they may wish to diff against it. So we don't do recover as # such. # - Make keyboard shortcuts customisable? (apart from the standard ones)