source: orange/Orange/OrangeCanvas/orngOutput.py @ 11093:d1c2af85ace2

Revision 11093:d1c2af85ace2, 8.4 KB checked in by Ales Erjavec <ales.erjavec@…>, 14 months ago (diff)

Capture stderr in the output window.

(references #1276)

Line 
1# Author: Gregor Leban (gregor.leban@fri.uni-lj.si)
2# Description:
3#     print system output and exceptions into a window. Enables copy/paste
4#
5from PyQt4.QtCore import *
6from PyQt4.QtGui import *
7import sys
8import string
9from time import localtime
10import traceback
11import os.path, os
12
13try:
14    __IPYTHON__  #We are running orange from ipython - it already has redirected sys.stdout
15    __DISABLE_OUTPUT__ = True
16except NameError:
17    __DISABLE_OUTPUT__ = False
18
19def thread_safe_queue(func):
20    """
21    """
22    from functools import wraps, partial
23    @wraps(func)
24    def safe_wrapper(self, *args, **kwargs):
25        if not hasattr(self, "_thread_safe_thread"):
26            self._thread_safe_thread = self.thread()
27        if QThread.currentThread() is not self._thread_safe_thread:
28            QMetaObject.invokeMethod(self, "queuedInvoke", Qt.QueuedConnection, Q_ARG("PyQt_PyObject", partial(safe_wrapper, self, *args, **kwargs)))
29        else:
30            return func(self, *args, **kwargs)
31
32    return safe_wrapper
33
34class OutputWindow(QDialog):
35    def __init__(self, canvasDlg, *args):
36        QDialog.__init__(self, None, Qt.Window)
37        self.canvasDlg = canvasDlg
38
39        self.textOutput = QPlainTextEdit(self)
40        self.textOutput.setReadOnly(1)
41#        self.textOutput.zoomIn(1)
42
43        self.setLayout(QVBoxLayout())
44        self.layout().addWidget(self.textOutput)
45        self.layout().setMargin(2)
46        self.setWindowTitle("Output Window")
47        self.setWindowIcon(QIcon(canvasDlg.outputPix))
48
49        self.defaultExceptionHandler = sys.excepthook
50        self.defaultSysOutHandler = sys.stdout
51        self.defaultSysErrHandler = sys.stderr
52
53        self.logFile = open(os.path.join(canvasDlg.canvasSettingsDir, "outputLog.html"), "w") # create the log file
54        self.unfinishedText = ""
55
56        w = h = 500
57        if canvasDlg.settings.has_key("outputWindowPos"):
58            desktop = qApp.desktop()
59            deskH = desktop.availableGeometry(self).height()
60            deskW = desktop.availableGeometry(self).width()
61            w, h, x, y = canvasDlg.settings["outputWindowPos"]
62            if x >= 0 and y >= 0 and deskH >= y+h and deskW >= x+w: 
63                self.move(QPoint(x, y))
64            else: 
65                w = h = 500
66        self.resize(w, h)
67           
68        self.hide()
69
70    def stopCatching(self):
71        self.catchException(0)
72        self.catchOutput(0)
73
74    def showEvent(self, ce):
75        ce.accept()
76        QDialog.showEvent(self, ce)
77        settings = self.canvasDlg.settings
78        if settings.has_key("outputWindowPos"):
79            w, h, x, y = settings["outputWindowPos"]
80            self.move(QPoint(x, y))
81            self.resize(w, h)
82       
83    def hideEvent(self, ce):
84        self.canvasDlg.settings["outputWindowPos"] = (self.width(), self.height(), self.pos().x(), self.pos().y())
85        ce.accept()
86        QDialog.hideEvent(self, ce)
87               
88    def closeEvent(self,ce):
89        self.canvasDlg.settings["outputWindowPos"] = (self.width(), self.height(), self.pos().x(), self.pos().y())
90        if getattr(self.canvasDlg, "canvasIsClosing", 0):
91            self.catchException(0)
92            self.catchOutput(0)
93            ce.accept()
94            QDialog.closeEvent(self, ce)
95        else:
96            self.hide()
97
98    def catchException(self, catch):
99        if __DISABLE_OUTPUT__:
100            return
101        if catch:
102            sys.excepthook = self.exceptionHandler
103        else:
104            sys.excepthook = self.defaultExceptionHandler
105
106    def catchOutput(self, catch):
107        if __DISABLE_OUTPUT__:
108            return
109        if catch:
110            sys.stdout = self
111            sys.stderr = self
112        else:
113            sys.stdout = self.defaultSysOutHandler
114            sys.stderr = self.defaultSysErrHandler
115
116    def clear(self):
117        self.textOutput.clear()
118
119    # print text produced by warning and error widget calls
120    def widgetEvents(self, text, eventVerbosity = 1):
121        if self.canvasDlg.settings["outputVerbosity"] >= eventVerbosity:
122            if text != None:
123                self.write(str(text))
124            self.canvasDlg.setStatusBarEvent(QString(text))
125
126    # simple printing of text called by print calls
127    @thread_safe_queue
128    def write(self, text):
129        Text = self.getSafeString(text)
130        Text = Text.replace("\n", "<br>\n")   # replace new line characters with <br> otherwise they don't get shown correctly in html output
131#        text = "<nobr>" + text + "</nobr>"
132
133        if self.canvasDlg.settings["focusOnCatchOutput"]:
134            self.canvasDlg.menuItemShowOutputWindow()
135
136        if self.canvasDlg.settings["writeLogFile"]:
137            #self.logFile.write(str(text) + "<br>\n")
138            self.logFile.write(Text)
139
140        cursor = QTextCursor(self.textOutput.textCursor())                # clear the current text selection so that
141        cursor.movePosition(QTextCursor.End, QTextCursor.MoveAnchor)      # the text will be appended to the end of the
142        self.textOutput.setTextCursor(cursor)                             # existing text
143#        if text == " ": self.textOutput.insertHtml("&nbsp;")
144#        else:           self.textOutput.insertHtml(Text)                                  # then append the text
145
146        self.textOutput.insertPlainText(text)
147       
148        cursor.movePosition(QTextCursor.End, QTextCursor.MoveAnchor)      # and then scroll down to the end of the text
149
150        self.textOutput.setTextCursor(cursor)
151
152#        self.textOutput.insertPlainText(text)
153
154        if Text[-1:] == "\n":
155            if self.canvasDlg.settings["printOutputInStatusBar"]:
156                self.canvasDlg.setStatusBarEvent(self.unfinishedText + text)
157            self.unfinishedText = ""
158        else:
159            self.unfinishedText += text
160
161    def writelines(self, lines):
162        for line in lines:
163            self.write(line)
164
165    def flush(self):
166        pass
167   
168    def getSafeString(self, s):
169        return str(s).replace("<", "&lt;").replace(">", "&gt;")
170
171    @thread_safe_queue
172    def exceptionHandler(self, type, value, tracebackInfo):
173        if self.canvasDlg.settings["focusOnCatchException"]:
174            self.canvasDlg.menuItemShowOutputWindow()
175
176        t = localtime()
177        text = "<nobr>Unhandled exception of type %s occured at %d:%02d:%02d:</nobr><br><nobr>Traceback:</nobr><br>\n" % ( self.getSafeString(type.__name__), t[3],t[4],t[5])
178
179        if self.canvasDlg.settings["printExceptionInStatusBar"]:
180            self.canvasDlg.setStatusBarEvent("Unhandled exception of type %s occured at %d:%02d:%02d. See output window for details." % ( str(type) , t[3],t[4],t[5]))
181
182        # TO DO:repair this code to shown full traceback. when 2 same errors occur, only the first one gets full traceback, the second one gets only 1 item
183        list = traceback.extract_tb(tracebackInfo, 10)
184        space = "&nbsp; "
185        totalSpace = space
186        for i in range(len(list)):
187            (file, line, funct, code) = list[i]
188            if code == None:
189                continue
190            (dir, filename) = os.path.split(file)
191            text += "<nobr>" + totalSpace + "File: <b>" + filename + "</b>, line %4d" %(line) + " in <b>%s</b></nobr><br>\n" % (self.getSafeString(funct))
192            text += "<nobr>" + totalSpace + "Code: " + code + "</nobr><br>\n"
193            totalSpace += space
194
195        lines = traceback.format_exception_only(type, value)
196        for line in lines[:-1]:
197            text += "<nobr>" + totalSpace + self.getSafeString(line) + "</nobr><br>\n"
198        text += "<nobr><b>" + totalSpace + self.getSafeString(lines[-1]) + "</b></nobr><br>\n"
199
200        cursor = QTextCursor(self.textOutput.textCursor())                # clear the current text selection so that
201        cursor.movePosition(QTextCursor.End, QTextCursor.MoveAnchor)      # the text will be appended to the end of the
202#        self.textOutput.setTextCursor(cursor)                             # existing text
203#        self.textOutput.insertHtml(text)                                  # then append the text
204        self.textOutput.appendHtml(text)
205        cursor.movePosition(QTextCursor.End, QTextCursor.MoveAnchor)      # and then scroll down to the end of the text
206        self.textOutput.setTextCursor(cursor)
207
208#        self.textOutput.appendPlainText(traceback.format_exc(10))
209
210        if self.canvasDlg.settings["writeLogFile"]:
211            self.logFile.write(str(text) + "<br>\n")
212           
213    @pyqtSignature("queuedInvoke(PyQt_PyObject)")
214    def queuedInvoke(self, func):
215        func()
Note: See TracBrowser for help on using the repository browser.