1#!/usr/bin/env kross 2 3""" 4Interactive Python Console Docker for Sheets. 5 6(C)2007 Sebastian Sauer <mail@dipe.org> 7http://kross.dipe.org 8http://www.calligra.org/sheets 9Dual-licensed under LGPL v2+higher and the BSD license. 10""" 11 12import Kross, KoDocker, KSpread 13import PyQt4.Qt as Qt 14import sip #wrapinstance, unwrapinstance 15 16T = Kross.module("kdetranslation") 17 18def _getHome(): 19 """ Return the homedirectory. """ 20 import os 21 try: 22 home = os.getenv("HOME") 23 if not home: 24 import pwd 25 user = os.getenv("USER") or os.getenv("LOGNAME") 26 if not user: 27 pwent = pwd.getpwuid(os.getuid()) 28 else: 29 pwent = pwd.getpwnam(user) 30 home = pwent[6] 31 return home 32 except (KeyError, ImportError): 33 return os.curdir 34 35def executeFile(fileName): 36 """ Execute a file. """ 37 if fileName.startswith("file://"): 38 fileName = fileName[7:] 39 fileName = fileName.replace("$HOME", _getHome()) 40 execfile(fileName, globals(), globals()) 41 42class _ConsoleDocker(Qt.QWidget): 43 """ The docker widget. """ 44 45 class Editor(Qt.QWidget): 46 """ An editor to edit python scripting code. """ 47 48 def __init__(self, docker): 49 self.docker = docker 50 self.filename = None 51 Qt.QWidget.__init__(self, self.docker) 52 layout = Qt.QVBoxLayout(self) 53 layout.setMargin(0) 54 layout.setSpacing(0) 55 self.setLayout(layout) 56 menubar = Qt.QMenuBar(self) 57 layout.addWidget(menubar) 58 self.edit = Qt.QTextEdit(self) 59 self.edit.setWordWrapMode(Qt.QTextOption.NoWrap) 60 layout.addWidget(self.edit) 61 self.status = Qt.QLabel('', self) 62 layout.addWidget(self.status) 63 menu = Qt.QMenu(T.i18nc("Menu", "File").decode('utf-8'), menubar) 64 menubar.addMenu(menu) 65 self.addAction(menu, T.i18n("New").decode('utf-8'), self.newClicked) 66 self.addAction(menu, T.i18n("Open...").decode('utf-8'), self.openClicked) 67 self.addAction(menu, T.i18n("Save").decode('utf-8'), self.saveClicked) 68 self.addAction(menu, T.i18n("Save As...").decode('utf-8'), self.saveAsClicked) 69 self.editmenu = self.edit.createStandardContextMenu() 70 self.editmenu.setTitle(T.i18nc("Menu", "Edit").decode('utf-8')) 71 menubar.addMenu(self.editmenu) 72 menu = Qt.QMenu(T.i18nc("Menu", "Build").decode('utf-8'), menubar) 73 menubar.addMenu(menu) 74 self.addAction(menu, T.i18n("Compile").decode('utf-8'), self.compileClicked) 75 self.addAction(menu, T.i18n("Execute").decode('utf-8'), self.executeClicked) 76 def addAction(self, menu, text, func): 77 action = Qt.QAction(text, self) 78 Qt.QObject.connect(action, Qt.SIGNAL("triggered(bool)"), func) 79 menu.addAction(action) 80 return action 81 def newClicked(self, *args): 82 self.edit.clear() 83 self.status.setText('') 84 self.filename = None 85 def openClicked(self, *args): 86 if self.filename: 87 filename = self.filename 88 else: 89 filename = _getHome() 90 filename = Qt.QFileDialog.getOpenFileName(self, T.i18n("Open File").decode('utf-8'), filename, "*.py;;*") 91 if filename: 92 try: 93 f = open(filename, "r") 94 self.edit.setText( "%s" % f.read() ) 95 f.close() 96 self.filename = filename 97 except IOError, (errno, strerror): 98 Qt.QMessageBox.critical(self, T.i18n("Error").decode('utf-8'), T.i18n("<qt>Failed to open file \"%1\"<br><br>%2</qt>", [filename], [strerror]).decode('utf-8')) 99 def saveClicked(self, *args): 100 if not self.filename: 101 self.saveAsClicked() 102 return 103 try: 104 f = open(self.filename, "w") 105 f.write( "%s" % self.edit.toPlainText() ) 106 f.close() 107 except IOError, (errno, strerror): 108 qt.QMessageBox.critical(self, T.i18n("Error").decode('utf-8'), T.i18n("<qt>Failed to save file \"%1\"<br><br>%2</qt>", [self.filename], [strerror]).decode('utf-8')) 109 def saveAsClicked(self, *args): 110 if self.filename: 111 filename = self.filename 112 else: 113 filename = _getHome() 114 filename = Qt.QFileDialog.getSaveFileName(self, T.i18n("Save File").decode('utf-8'), filename, "*.py;;*") 115 if filename: 116 self.filename = filename 117 self.saveClicked() 118 def compileClicked(self, *args): 119 import time, traceback 120 text = "%s" % self.edit.toPlainText() 121 try: 122 compile(text, '', 'exec') 123 self.status.setText(T.i18n("Compiled! %1", [time.strftime('%H:%M.%S')]).decode('utf-8')) 124 except Exception, e: 125 self.status.setText("%s" % e) 126 traceback.print_exc(file=sys.stderr) 127 def executeClicked(self, *args): 128 import time 129 err = self.docker.execute("%s" % self.edit.toPlainText()) 130 if not err: 131 self.status.setText(T.i18n("Executed! %1", [time.strftime('%H:%M.%S')]).decode('utf-8')) 132 else: 133 self.status.setText("%s" % err) 134 135 class Model(Qt.QAbstractItemModel): 136 """ The model for the treeview that displays the content of our globals(). """ 137 138 class Item: 139 """ An item within the model. """ 140 141 def __init__(self, parentItem = None, object = None, name = ""): 142 self.parent = parentItem 143 self.object = object 144 self.name = name 145 self.children = [] 146 if self.parent: 147 self.parent.children.append(self) 148 def lazyLoadChildren(self): 149 if self.object: 150 for s in dir(self.object): 151 if not s.startswith('_'): 152 try: 153 _ConsoleDocker.Model.Item(self, getattr(self.object,s), s) 154 except: 155 pass 156 def row(self): 157 return 0 158 def hasChildren(self): 159 import types 160 if not self.parent: 161 return True 162 if isinstance(self.object, types.ClassType) or isinstance(self.object, types.ModuleType): 163 return True 164 if type(self.object) == type(Kross): 165 return True 166 return False 167 def child(self, row): 168 if len(self.children) == 0: 169 self.lazyLoadChildren() 170 return self.children[row] 171 def childCount(self): 172 if len(self.children) == 0: 173 self.lazyLoadChildren() 174 return len(self.children) 175 def data(self, column): 176 if column == 0: 177 return Qt.QVariant("%s" % self.name) 178 if column == 1 and self.object: 179 if type(self.object) == type(Kross): return Qt.QVariant("ext") 180 t = "%s" % type(self.object) 181 return Qt.QVariant( t[ t.find("'")+1 : t.rfind("'") ] ) 182 return Qt.QVariant() 183 184 def __init__(self): 185 Qt.QAbstractItemModel.__init__(self) 186 self.rootItem = _ConsoleDocker.Model.Item() 187 for s in globals(): 188 if not s.startswith('_') and not s.startswith('PyQt4.Qt'): 189 _ConsoleDocker.Model.Item(self.rootItem, globals()[s], s) 190 def columnCount(self, parent): 191 return 2 192 def flags(self, index): 193 return Qt.QAbstractItemModel.flags(self, index) 194 def index(self, row, column, parent): 195 if parent.isValid(): 196 parentItem = parent.internalPointer() 197 else: 198 parentItem = self.rootItem 199 childItem = parentItem.child(row) 200 if childItem: 201 return self.createIndex(row, column, childItem) 202 return Qt.QModelIndex() 203 def parent(self, index): 204 if index.isValid(): 205 parentItem = index.internalPointer().parent 206 if parentItem and parentItem != self.rootItem: 207 return self.createIndex(parentItem.row(), 0, parentItem) 208 return Qt.QModelIndex() 209 def data(self, index, role): 210 if index.isValid(): 211 if role == Qt.Qt.DisplayRole: 212 return index.internalPointer().data(index.column()) 213 return Qt.QVariant() 214 def rowCount(self, parent): 215 if parent.isValid(): 216 parentItem = parent.internalPointer() 217 else: 218 parentItem = self.rootItem 219 return parentItem.childCount() 220 def hasChildren(self, parent): 221 if parent.isValid(): 222 parentItem = parent.internalPointer() 223 else: 224 parentItem = self.rootItem 225 return parentItem.hasChildren() 226 def headerData(self, section, orientation, role = 0): 227 if role == Qt.Qt.DisplayRole: 228 if section == 0: 229 return Qt.QVariant(T.i18n("Name").decode('utf-8')) 230 if section == 1: 231 return Qt.QVariant(T.i18n("Type").decode('utf-8')) 232 return Qt.QVariant() 233 234 def __init__(self): 235 Qt.QWidget.__init__(self) 236 layout = Qt.QVBoxLayout() 237 layout.setMargin(0) 238 layout.setSpacing(0) 239 self.setLayout(layout) 240 241 self.pages = Qt.QTabWidget(self) 242 layout.addWidget(self.pages) 243 244 consoleWidget = Qt.QWidget(self) 245 consoleLayout = Qt.QVBoxLayout() 246 consoleLayout.setMargin(0) 247 consoleLayout.setSpacing(0) 248 consoleWidget.setLayout(consoleLayout) 249 self.pages.addTab(consoleWidget, T.i18n("Console").decode('utf-8')) 250 251 self.browser = Qt.QTextBrowser(consoleWidget) 252 self.browser.setFrameShape(Qt.QFrame.NoFrame) 253 consoleLayout.addWidget(self.browser) 254 255 self.edit = Qt.QComboBox(consoleWidget) 256 self.edit.setEditable(True) 257 self.edit.insertItems(0, ['','print globals().keys()','print dir(Kross)','print dir(KSpread)','print KSpread.sheetNames()','print Qt.PYQT_VERSION_STR','print sip.SIP_VERSION_STR']) 258 self.browser.setFocusProxy(self.edit) 259 Qt.QObject.connect(self.edit.lineEdit(), Qt.SIGNAL("returnPressed()"), self.returnPressed) 260 consoleLayout.addWidget(self.edit) 261 262 self.pages.addTab(_ConsoleDocker.Editor(self), T.i18n("Editor").decode('utf-8')) 263 264 inspWidget = Qt.QWidget(self) 265 inspLayout = Qt.QVBoxLayout() 266 inspLayout.setMargin(0) 267 inspLayout.setSpacing(0) 268 inspWidget.setLayout(inspLayout) 269 self.pages.addTab(inspWidget, T.i18n("Inspect").decode('utf-8')) 270 271 self.treeFilter = Qt.QLineEdit(inspWidget) 272 inspLayout.addWidget(self.treeFilter) 273 274 self.tree = Qt.QTreeView(inspWidget) 275 inspLayout.addWidget(self.tree) 276 self.tree.setFrameShape(Qt.QFrame.NoFrame) 277 self.tree.setRootIsDecorated(True) 278 self.tree.setSortingEnabled(False) 279 self.tree.header().setClickable(False) 280 281 self.model = _ConsoleDocker.Model() 282 self.proxyModel = Qt.QSortFilterProxyModel(self.tree) 283 self.proxyModel.setDynamicSortFilter(True) 284 self.proxyModel.setFilterCaseSensitivity(Qt.Qt.CaseInsensitive) 285 self.proxyModel.setSourceModel(self.model) 286 self.tree.setModel(self.proxyModel) 287 288 self.treeExpired = True 289 Qt.QObject.connect(self.treeFilter, Qt.SIGNAL("textChanged(QString)"), self.proxyModel, Qt.SLOT("setFilterFixedString(QString)")) 290 Qt.QObject.connect(self.tree, Qt.SIGNAL("activated(QModelIndex)"), self.itemActivated) 291 Qt.QObject.connect(self.pages, Qt.SIGNAL("currentChanged(int)"), self.currentChanged) 292 293 docker = sip.wrapinstance(KoDocker.__toPointer__(), Qt.QDockWidget) 294 docker.setWidget(self) 295 296 def returnPressed(self): 297 text = "%s" % self.edit.currentText() 298 self.edit.clearEditText() 299 self.execute(text) 300 self.treeExpired = True 301 302 def itemActivated(self, index): 303 s = self.proxyModel.data(index, 0).toString() 304 parent = index 305 while True: 306 parent = self.proxyModel.parent(parent) 307 if not parent.isValid(): break 308 s = "%s.%s" % (self.proxyModel.data(parent, 0).toString(), s) 309 self.edit.lineEdit().setText("print %s" % s) 310 self.pages.setCurrentIndex(0) 311 #self.returnPressed() 312 313 def currentChanged(self, *args): 314 if self.pages.currentWidget() == self.tree: 315 if self.treeExpired: 316 self.tree.reset() 317 self.treeExpired = False 318 319 def execute(self, code): 320 import sys, traceback 321 _stdout = sys.stdout 322 _stderr = sys.stderr 323 err = None 324 try: 325 class Base(): 326 def __init__(self, browser): 327 self.browser = browser 328 def write(self, text): 329 self.browser.append(text) 330 class StdOut(Base): 331 def __init__(self, browser): 332 Base.__init__(self, browser) 333 def write(self, text): 334 Base.write(self, text.strip().replace("\n","<br>")) 335 class StdErr(Base): 336 def __init__(self, browser): 337 Base.__init__(self, browser) 338 def write(self, text): 339 Base.write(self, "<b>%s</b>" % text.strip().replace("\n","<br>")) 340 sys.stdout = StdOut(self.browser) 341 sys.stderr = StdErr(self.browser) 342 sys.stdout.write("> <i>%s</i>" % code.strip()) 343 try: 344 exec code in globals(), globals() 345 except: 346 err = sys.exc_info()[1] 347 sys.stderr.write("".join( traceback.format_exception(sys.exc_info()[0],sys.exc_info()[1],sys.exc_info()[2]) )) 348 finally: 349 sys.stdout = _stdout 350 sys.stderr = _stderr 351 return err 352 353print "Execute _ConsoleDocker Script" 354_ConsoleDocker() 355