Changeset 11257:cb4e4ab85ce8 in orange


Ignore:
Timestamp:
01/11/13 17:15:19 (16 months ago)
Author:
Ales Erjavec <ales.erjavec@…>
Branch:
default
Message:

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

Location:
Orange/OrangeCanvas
Files:
4 edited

Legend:

Unmodified
Added
Removed
  • Orange/OrangeCanvas/application/canvasmain.py

    r11256 r11257  
    3333from .aboutdialog import AboutDialog 
    3434from .schemeinfo import SchemeInfoDialog 
    35 from .outputview import OutputText 
     35from .outputview import OutputView 
    3636from .settings import UserSettingsDialog 
    3737from ..document.schemeedit import SchemeEditWidget 
     
    275275        self.output_dock.hide() 
    276276 
    277         output_view = OutputText() 
     277        output_view = OutputView() 
    278278        self.output_dock.setWidget(output_view) 
    279279 
  • Orange/OrangeCanvas/application/outputview.py

    r11167 r11257  
    11""" 
    22""" 
    3  
     3from functools import wraps 
    44from PyQt4.QtGui import ( 
    5     QWidget, QPlainTextEdit, QVBoxLayout, QTextCursor 
     5    QWidget, QPlainTextEdit, QVBoxLayout, QTextCursor, QTextCharFormat, 
     6    QFont, QFontMetrics 
    67) 
    78 
    8 from PyQt4.QtCore import Qt 
    9  
    10  
    11 class OutputText(QWidget): 
     9from PyQt4.QtCore import Qt, QObject, QEvent, QCoreApplication 
     10from PyQt4.QtCore import pyqtSignal as Signal 
     11 
     12 
     13class OutputView(QWidget): 
    1214    def __init__(self, parent=None, **kwargs): 
    1315        QWidget.__init__(self, parent, **kwargs) 
     
    2527        self.__text.setFont(font) 
    2628 
     29        self.__currentCharFormat = self.__text.currentCharFormat() 
     30 
    2731        self.layout().addWidget(self.__text) 
    2832 
     33        metrics = QFontMetrics(font) 
     34        width = metrics.boundingRect("X").width() 
     35        self.resize(width * 80, width * 30) 
     36 
    2937    def setMaximumLines(self, lines): 
    30         """Set the maximum number of lines to keep displayed. 
     38        """ 
     39        Set the maximum number of lines to keep displayed. 
    3140        """ 
    3241        if self.__lines != lines: 
     
    3544 
    3645    def maximumLines(self): 
    37         """Return the maximum number of lines in the display. 
     46        """ 
     47        Return the maximum number of lines in the display. 
    3848        """ 
    3949        return self.__lines 
    4050 
    4151    def clear(self): 
    42         """Clear the displayed text. 
     52        """ 
     53        Clear the displayed text. 
    4354        """ 
    4455        self.__text.clear() 
    4556 
     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 
    4666    def toPlainText(self): 
    47         """Return the full contents of the output view. 
     67        """ 
     68        Return the full contents of the output view. 
    4869        """ 
    4970        return self.__text.toPlainText() 
     
    5273    def write(self, string): 
    5374        self.__text.moveCursor(QTextCursor.End, QTextCursor.MoveAnchor) 
     75        self.__text.setCurrentCharFormat(self.__currentCharFormat) 
     76 
    5477        self.__text.insertPlainText(string) 
    5578 
    5679    def writelines(self, lines): 
    57         for line in lines: 
    58             self.write(line) 
     80        self.write("".join(lines)) 
    5981 
    6082    def flush(self): 
    61         pass 
     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() 
  • Orange/OrangeCanvas/application/tests/test_outputview.py

    r11167 r11257  
     1import multiprocessing.pool 
     2 
    13from datetime import datetime 
     4from threading import current_thread 
    25 
     6from PyQt4.QtCore import Qt, QThread 
    37from ...gui.test import QAppTestCase 
    48 
    5 from ..outputview import OutputText 
     9from ..outputview import OutputView, TextStream 
    610 
    711 
    812class TestOutputView(QAppTestCase): 
    913    def test_outputview(self): 
    10         output = OutputText() 
     14        output = OutputView() 
    1115        output.show() 
    1216 
     
    3943 
    4044        self.app.exec_() 
     45 
     46    def test_formated(self): 
     47        output = OutputView() 
     48        output.show() 
     49 
     50        output.write("A sword day, ") 
     51        with output.formated(color=Qt.red) as f: 
     52            f.write("a red day...\n") 
     53 
     54            with f.formated(color=Qt.green) as f: 
     55                f.write("Actually sir, orcs bleed green.\n") 
     56 
     57        bold = output.formated(weight=100, underline=True) 
     58        bold.write("Shutup") 
     59 
     60        self.app.exec_() 
     61 
     62    def test_threadsafe(self): 
     63        output = OutputView() 
     64        output.resize(500, 300) 
     65        output.show() 
     66 
     67        blue_formater = output.formated(color=Qt.blue) 
     68        red_formater = output.formated(color=Qt.red) 
     69 
     70        correct = [] 
     71 
     72        def check_thread(*args): 
     73            correct.append(QThread.currentThread() == self.app.thread()) 
     74 
     75        blue = TextStream() 
     76        blue.stream.connect(blue_formater.write) 
     77        blue.stream.connect(check_thread) 
     78 
     79        red = TextStream() 
     80        red.stream.connect(red_formater.write) 
     81        red.stream.connect(check_thread) 
     82 
     83        def printer(i): 
     84            if i % 12 == 0: 
     85                fizzbuz = "fizzbuz" 
     86            elif i % 4 == 0: 
     87                fizzbuz = "buz" 
     88            elif i % 3 == 0: 
     89                fizzbuz = "fizz" 
     90            else: 
     91                fizzbuz = str(i) 
     92 
     93            if i % 2: 
     94                writer = blue 
     95            else: 
     96                writer = red 
     97 
     98            writer.write("Greetings from thread {0}. " 
     99                         "This is {1}\n".format(current_thread().name, 
     100                                                fizzbuz)) 
     101 
     102        pool = multiprocessing.pool.ThreadPool(100) 
     103        res = pool.map_async(printer, range(10000)) 
     104 
     105        self.app.exec_() 
     106 
     107        res.wait() 
     108 
     109        self.assertTrue(all(correct)) 
     110        self.assertTrue(len(correct) == 10000) 
  • Orange/OrangeCanvas/main.py

    r11256 r11257  
    2121from Orange.OrangeCanvas.application.application import CanvasApplication 
    2222from Orange.OrangeCanvas.application.canvasmain import CanvasMainWindow 
     23from Orange.OrangeCanvas.application.outputview import TextStream 
    2324 
    2425from Orange.OrangeCanvas.gui.splashscreen import SplashScreen, QPixmap 
     
    235236        settings.value("output/redirect-stderr", True).toBool() 
    236237 
    237     # cmd line option overrides settings, and not redirect possible 
     238    # cmd line option overrides settings / no redirect is possible 
    238239    # under ipython 
    239240    if options.no_redirect or running_in_ipython(): 
     
    243244 
    244245    if stdout_redirect: 
    245         stdout = output_view 
     246        stdout = TextStream() 
     247        stdout.stream.connect(output_view.write) 
     248        # also connect to original fd 
     249        stdout.stream.connect(sys.stdout.write) 
    246250    else: 
    247251        stdout = sys.stdout 
    248252 
    249253    if stderr_redirect: 
    250         stderr = output_view 
     254        error_writer = output_view.formated(color=Qt.red) 
     255        stderr = TextStream() 
     256        stderr.stream.connect(error_writer.write) 
     257        # also connect to original fd 
     258        stderr.stream.connect(sys.stderr.write) 
    251259    else: 
    252260        stderr = sys.stderr 
Note: See TracChangeset for help on using the changeset viewer.