source: orange/Orange/OrangeCanvas/application/outputview.py @ 11257:cb4e4ab85ce8

Revision 11257:cb4e4ab85ce8, 7.2 KB checked in by Ales Erjavec <ales.erjavec@…>, 16 months ago (diff)

Added colored/formated output for the stdout/err output, output thread safety.

Line 
1"""
2"""
3from functools import wraps
4from PyQt4.QtGui import (
5    QWidget, QPlainTextEdit, QVBoxLayout, QTextCursor, QTextCharFormat,
6    QFont, QFontMetrics
7)
8
9from PyQt4.QtCore import Qt, QObject, QEvent, QCoreApplication
10from PyQt4.QtCore import pyqtSignal as Signal
11
12
13class OutputView(QWidget):
14    def __init__(self, parent=None, **kwargs):
15        QWidget.__init__(self, parent, **kwargs)
16
17        self.__lines = 5000
18
19        self.setLayout(QVBoxLayout())
20        self.layout().setContentsMargins(0, 0, 0, 0)
21
22        self.__text = QPlainTextEdit()
23        self.__text.setTextInteractionFlags(Qt.TextBrowserInteraction)
24        self.__text.setMaximumBlockCount(self.__lines)
25        font = self.__text.font()
26        font.setFamily("Monaco")
27        self.__text.setFont(font)
28
29        self.__currentCharFormat = self.__text.currentCharFormat()
30
31        self.layout().addWidget(self.__text)
32
33        metrics = QFontMetrics(font)
34        width = metrics.boundingRect("X").width()
35        self.resize(width * 80, width * 30)
36
37    def setMaximumLines(self, lines):
38        """
39        Set the maximum number of lines to keep displayed.
40        """
41        if self.__lines != lines:
42            self.__lines = lines
43            self.__text.setMaximumBlockCount(lines)
44
45    def maximumLines(self):
46        """
47        Return the maximum number of lines in the display.
48        """
49        return self.__lines
50
51    def clear(self):
52        """
53        Clear the displayed text.
54        """
55        self.__text.clear()
56
57    def setCurrentCharFormat(self, charformat):
58        """Set the QTextCharFormat to be used when writing.
59        """
60        if self.__currentCharFormat != charformat:
61            self.__currentCharFormat = charformat
62
63    def currentCharFormat(self):
64        return self.__currentCharFormat
65
66    def toPlainText(self):
67        """
68        Return the full contents of the output view.
69        """
70        return self.__text.toPlainText()
71
72    # A file like interface.
73    def write(self, string):
74        self.__text.moveCursor(QTextCursor.End, QTextCursor.MoveAnchor)
75        self.__text.setCurrentCharFormat(self.__currentCharFormat)
76
77        self.__text.insertPlainText(string)
78
79    def writelines(self, lines):
80        self.write("".join(lines))
81
82    def flush(self):
83        QCoreApplication.flush()
84
85    def writeWithFormat(self, string, charformat):
86        self.__text.moveCursor(QTextCursor.End, QTextCursor.MoveAnchor)
87        self.__text.setCurrentCharFormat(charformat)
88        self.__text.insertPlainText(string)
89
90    def writelinesWithFormat(self, lines, charformat):
91        self.writeWithFormat("".join(lines), charformat)
92
93    def formated(self, color=None, background=None, weight=None,
94                 italic=None, underline=None, font=None):
95        """
96        Return a formated file like object proxy.
97        """
98        charformat = update_char_format(
99            self.currentCharFormat(), color, background, weight,
100            italic, underline, font
101        )
102        return formater(self, charformat)
103
104
105def update_char_format(baseformat, color=None, background=None, weight=None,
106                       italic=None, underline=None, font=None):
107    """
108    Return a copy of `baseformat` :class:`QTextCharFormat` with
109    updated color, weight, background and font properties.
110
111    """
112    charformat = QTextCharFormat(baseformat)
113
114    if color is not None:
115        charformat.setForeground(color)
116
117    if background is not None:
118        charformat.setBackground(background)
119
120    if font is not None:
121        charformat.setFont(font)
122    else:
123        font = update_font(baseformat.font(), weight, italic, underline)
124        charformat.setFont(font)
125
126    return charformat
127
128
129def update_font(basefont, weight=None, italic=None, underline=None,
130                pixelSize=None, pointSize=None):
131    """
132    Return a copy of `basefont` :class:`QFont` with updated properties.
133    """
134    font = QFont(basefont)
135
136    if weight is not None:
137        font.setWeight(weight)
138
139    if italic is not None:
140        font.setItalic(italic)
141
142    if underline is not None:
143        font.setUnderline(underline)
144
145    if pixelSize is not None:
146        font.setPixelSize(pixelSize)
147
148    if pointSize is not None:
149        font.setPointSize(pointSize)
150
151    return font
152
153
154class formater(object):
155    def __init__(self, outputview, charformat):
156        self.outputview = outputview
157        self.charformat = charformat
158
159    def write(self, string):
160        self.outputview.writeWithFormat(string, self.charformat)
161
162    def writelines(self, lines):
163        self.outputview.writelines(lines, self.charformat)
164
165    def flush(self):
166        self.outputview.flush()
167
168    def formated(self, color=None, background=None, weight=None,
169                 italic=None, underline=None, font=None):
170        charformat = update_char_format(self.charformat, color, background,
171                                        weight, italic, underline, font)
172        return formater(self.outputview, charformat)
173
174    def __enter__(self):
175        return self
176
177    def __exit__(self, *args):
178        self.outputview = None
179        self.charformat = None
180
181
182class QueuedCallEvent(QEvent):
183    QueuedCall = QEvent.registerEventType()
184
185    def __init__(self, function, args, kwargs):
186        QEvent.__init__(self, QueuedCallEvent.QueuedCall)
187        self.function = function
188        self.args = args
189        self.kwargs = kwargs
190        self._result = None
191        self._exc_info = None
192        self._state = 0
193
194    def call(self):
195        try:
196            self._result = self.function(*self.args, **self.kwargs)
197            self._state = 1
198        except Exception, ex:
199            self._exc_info = (type(ex), ex.args, None)
200            raise
201
202    def result(self):
203        if self._state == 1:
204            return self._result
205        elif self._exc_info:
206            raise self._exc_info[0], self._exc_info[1]
207        else:
208            # Should this block, add timeout?
209            raise RuntimeError("Result not yet ready")
210
211    def isready(self):
212        return self._state == 1 or self._exc_info
213
214
215def queued(method):
216    """
217    Run method from the event queue.
218    """
219    @wraps(method)
220    def delay_method_call(self, *args, **kwargs):
221        event = QueuedCallEvent(method.__get__(self), args, kwargs)
222        QCoreApplication.postEvent(self, event)
223
224    return delay_method_call
225
226
227def queued_blocking(method):
228    """
229    Run method from the event queue and wait until the event is processed.
230    Return the call's return value.
231
232    """
233    @wraps(method)
234    def delay_method_call(self, *args, **kwargs):
235        event = QueuedCallEvent(method, args, kwargs)
236        QCoreApplication.postEvent(self, event)
237        QCoreApplication.flush()
238        return event.result()
239
240    return delay_method_call
241
242
243class TextStream(QObject):
244    stream = Signal(basestring)
245    flushed = Signal(basestring)
246
247    def __init__(self, parent=None):
248        QObject.__init__(self, parent)
249
250    @queued
251    def write(self, string):
252        self.stream.emit(string)
253
254    @queued
255    def writelines(self, lines):
256        self.stream.emit("".join(lines))
257
258    @queued_blocking
259    def flush(self):
260        self.flushed.emit()
261
262    def customEvent(self, event):
263        if event.type() == QueuedCallEvent.QueuedCall:
264            event.call()
265            event.accept()
Note: See TracBrowser for help on using the repository browser.