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("&gt; <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