Ignore:
Files:
76 added
92 deleted
12 edited

Legend:

Unmodified
Added
Removed
  • MANIFEST.in

    r11480 r11173  
    11recursive-include Orange/testing * 
    2 recursive-include Orange/datasets * 
     2recursive-include Orange/doc * 
    33 
    44recursive-include Orange/OrangeWidgets *.png *.svg *.gs *.vs *.obj *.html 
  • Orange/OrangeCanvas/application/canvasmain.py

    r11496 r11495  
    1111import pkg_resources 
    1212 
    13 import Orange.utils.addons 
    14  
    1513from PyQt4.QtGui import ( 
    1614    QMainWindow, QWidget, QAction, QActionGroup, QMenu, QMenuBar, QDialog, 
     
    4745from .outputview import OutputView 
    4846from .settings import UserSettingsDialog 
    49 from .addons import AddOnManagerDialog 
    50  
    5147from ..document.schemeedit import SchemeEditWidget 
    5248 
     
    316312                       [self.freeze_action, 
    317313                        self.dock_help_action] 
    318  
    319  
    320         def addOnRefreshCallback(): 
    321             pass #TODO add new category 
    322  
    323         Orange.utils.addons.addon_refresh_callback.append(addOnRefreshCallback) 
    324314 
    325315        # Tool bar in the collapsed dock state (has the same actions as 
     
    514504                    ) 
    515505 
    516         self.canvas_addons_action = \ 
    517             QAction(self.tr("&Add-ons..."), self, 
    518                     objectName="canvas-addons-action", 
    519                     toolTip=self.tr("Manage add-ons."), 
    520                     triggered=self.open_addons, 
    521                     menuRole=QAction.PreferencesRole 
    522                     ) 
    523  
    524  
    525506        self.show_output_action = \ 
    526507            QAction(self.tr("Show Output View"), self, 
     
    644625        self.options_menu.addSeparator() 
    645626        self.options_menu.addAction(self.canvas_settings_action) 
    646         self.options_menu.addAction(self.canvas_addons_action) 
    647627 
    648628        # Widget menu 
     
    13951375            self.__update_from_settings() 
    13961376 
    1397     def open_addons(self): 
    1398  
    1399         def getlr(): 
    1400             settings = QSettings() 
    1401             settings.beginGroup("addons") 
    1402             lastRefresh = settings.value("addons-last-refresh", 
    1403                           defaultValue=0, type=int) 
    1404             settings.endGroup() 
    1405             return lastRefresh 
    1406          
    1407         def setlr(v): 
    1408             settings = QSettings() 
    1409             settings.beginGroup("addons") 
    1410             lastRefresh = settings.setValue("addons-last-refresh", int(v)) 
    1411             settings.endGroup() 
    1412              
    1413         dlg = AddOnManagerDialog(self, self) 
    1414         dlg.loadtimefn = getlr 
    1415         dlg.savetimefn = setlr 
    1416         dlg.show() 
    1417         dlg.reloadQ() 
    1418         status = dlg.exec_() 
    1419  
    14201377    def show_output_view(self): 
    14211378        """Show a window with application output. 
  • Orange/OrangeCanvas/config.py

    r11496 r11495  
    6262      "Show shadow around the scheme view"), 
    6363 
    64      ("mainwindow/toolbox-dock-exclusive", bool, True, 
     64     ("mainwindow/toolbox-dock-exclusive", bool, False, 
    6565      "Should the toolbox show only one expanded category at the time"), 
    6666 
  • Orange/OrangeCanvas/orngDlgs.py

    r11479 r11094  
    1111from PyQt4.QtGui import * 
    1212from orngCanvasItems import MyCanvasText 
    13 import time 
    1413 
    1514import OWGUI 
    16 import Orange.utils.addons 
    1715 
    1816has_pip = True 
     
    744742        self.resize(600,500) 
    745743        self.layout().setSizeConstraint(QLayout.SetMinimumSize) 
    746         self.savetimefn = None 
    747         self.loadtimefn = None 
    748744         
    749745        mainBox = OWGUI.widgetBox(self, orientation="vertical", sizePolicy=QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)) 
     
    769765        QObject.connect(lst, SIGNAL("itemChanged(QListWidgetItem *)"), self.cbToggled) 
    770766        QObject.connect(lst, SIGNAL("currentItemChanged(QListWidgetItem *, QListWidgetItem *)"), self.currentItemChanged) 
     767 
     768        import Orange.utils.addons 
    771769 
    772770        # Bottom info pane 
     
    846844    def accept(self): 
    847845        self.to_upgrade.difference_update(self.to_remove()) 
     846        import Orange.utils.addons 
    848847        add, remove, upgrade = self.to_install(), self.to_remove(), self.to_upgrade 
    849848        if len(add) + len(remove) + len(upgrade) > 0: 
     
    939938    def reloadRepo(self): 
    940939        # Reload add-on list. 
     940        import Orange.utils.addons 
    941941        try: 
    942942            self.busy("Reloading add-on repository ...") 
    943943            self.repaint() 
    944944            Orange.utils.addons.refresh_available_addons(progress_callback = self.pcb) 
    945             if self.savetimefn: 
    946                 self.savetimefn(int(time.time())) 
    947945        except Exception, e: 
    948946            QMessageBox.critical(self, "Error", "Could not reload repository: %s." % e) 
     
    951949        # Finally, refresh the tree on GUI. 
    952950        self.refreshView() 
    953  
    954     def reloadQ(self): 
    955         #ask the user if he would like to reload the repository 
    956         lastRefresh = 0 
    957         if self.loadtimefn: 
    958             lastRefresh = self.loadtimefn() 
    959         t = time.time() 
    960         if t - lastRefresh > 7*24*3600 or Orange.utils.addons.addons_corrupted: 
    961             if Orange.utils.addons.addons_corrupted or \ 
    962                QMessageBox.question(self, "Refresh", 
    963                                     "List of available add-ons has not been refreshed for more than a week. Do you want to download the list now?", 
    964                                      QMessageBox.Yes | QMessageBox.Default, 
    965                                      QMessageBox.No | QMessageBox.Escape) == QMessageBox.Yes: 
    966                 self.reloadRepo() 
    967951             
    968952    def upgradeCandidates(self): 
  • Orange/OrangeCanvas/orngView.py

    r11482 r10733  
    413413            if widget.instance == widgetInstance: 
    414414                widget.setProgressBarValue(value) 
     415                qApp.processEvents()        # allow processing of other events 
    415416                return 
    416417 
  • Orange/OrangeWidgets/OWBaseWidget.py

    r11482 r11458  
    199199        widgetId += 1 
    200200        self.widgetId = widgetId 
    201  
     201         
     202        self._private_thread_pools = {} 
    202203        self.asyncCalls = [] 
    203204        self.asyncBlock = False 
    204         self.__progressBarValue = -1 
    205205 
    206206    # uncomment this when you need to see which events occured 
     
    746746        self.processingStateChanged.emit(1) 
    747747 
    748     def progressBarSet(self, value, processEventsFlags=QEventLoop.AllEvents): 
    749         """ 
    750         Set the current progress bar to `value`. This method will also call 
    751         `qApp.processEvents` with the `processEventsFlags` unless the 
    752         processEventsFlags equals ``None``. 
    753  
    754         """ 
    755         old = self.__progressBarValue 
     748    def progressBarSet(self, value): 
    756749        if value > 0: 
    757750            self.__progressBarValue = value 
     
    772765            self.progressBarHandler(self, value) 
    773766 
    774         if old != value: 
    775             self.progressBarValueChanged.emit(value) 
    776  
    777         if processEventsFlags is not None: 
    778             qApp.processEvents(processEventsFlags) 
     767        self.progressBarValueChanged.emit(value) 
     768 
     769        qApp.processEvents() 
    779770 
    780771    def progressBarValue(self): 
    781772        return self.__progressBarValue 
    782773 
    783     progressBarValue = pyqtProperty( 
    784         float, 
    785         fset=lambda self, val: 
    786             self.progressBarSet(val, processEventsFlags=None), 
    787         fget=progressBarValue 
    788     ) 
    789  
    790     def progressBarAdvance(self, value, processEventsFlags=QEventLoop.AllEvents): 
    791         self.progressBarSet(self.progressBarValue + value, processEventsFlags) 
     774    progressBarValue = pyqtProperty(float, fset=progressBarSet, 
     775                                    fget=progressBarValue) 
     776 
     777    def progressBarAdvance(self, value): 
     778        self.progressBarSet(self.progressBarValue + value) 
    792779 
    793780    def progressBarFinished(self): 
  • Orange/OrangeWidgets/OWConcurrent.py

    r11481 r9671  
    88""" 
    99from __future__ import with_statement 
    10  
    11 import sys 
    12 import threading 
    13 import logging 
    14  
    1510from functools import partial 
    16 from contextlib import contextmanager 
    17  
    18 from PyQt4.QtGui import qApp 
    19  
    20 from PyQt4.QtCore import ( 
    21     Qt, QObject, QMetaObject, QTimer, QThreadPool, QThread, QMutex, 
    22     QRunnable, QEventLoop, QCoreApplication, QEvent, QString, SIGNAL, 
    23     Q_ARG, pyqtSignature, 
    24 ) 
    25  
    26 from PyQt4.QtCore import pyqtSignal as Signal, pyqtSlot as Slot 
    27  
    28 _log = logging.getLogger(__name__) 
    29  
    30  
    31 @contextmanager 
    32 def locked(mutex): 
    33     """ 
    34     A context manager for locking an instance of a QMutex. 
    35     """ 
    36     mutex.lock() 
    37     try: 
    38         yield 
    39     finally: 
    40         mutex.unlock() 
    41  
    42  
    43 class _TaskDepotThread(QThread): 
    44     """ 
    45     A special 'depot' thread used to transfer Task instance into threads 
    46     started by a QThreadPool. 
    47  
    48     """ 
    49     def start(self): 
    50         """ 
    51         Reimplemented from `QThread.start` 
    52         """ 
    53         QThread.start(self) 
    54         # Need to also handle method invoke from this thread 
    55         self.moveToThread(self) 
    56  
    57     def run(self): 
    58         """ 
    59         Reimplemented from `QThread.run` 
    60         """ 
    61         # Start the event loop. 
    62         # On some old Qt4/PyQt4 installations base QThread.run does not seem 
    63         # to enter the loop, despite being documented to do so. 
    64         self.exec_() 
    65  
    66     @Slot(object, object) 
    67     def transfer(self, obj, thread): 
    68         """ 
    69         Transfer `obj` (:class:`QObject`) instance from this thread to the 
    70         target `thread` (a :class:`QThread`). 
    71  
    72         """ 
    73         assert obj.thread() is self 
    74         assert QThread.currentThread() is self 
    75         obj.moveToThread(thread) 
    76  
    77  
    78 class _TaskRunnable(QRunnable): 
    79     """ 
    80     A QRunnable for running a :class:`Task` by a :class:`ThreadExecuter`. 
    81     """ 
    82     def __init__(self, future, task, args, kwargs): 
    83         QRunnable.__init__(self) 
    84         self.future = future 
    85         self.task = task 
    86         self.args = args 
    87         self.kwargs = kwargs 
    88         self.eventLoop = None 
    89  
    90     def run(self): 
    91         """ 
    92         Reimplemented from `QRunnable.run` 
    93         """ 
    94         self.eventLoop = QEventLoop() 
    95         self.eventLoop.processEvents() 
    96  
    97         # Move the task to the current thread so it's events, signals, slots 
    98         # are triggered from this thread. 
    99         assert isinstance(self.task.thread(), _TaskDepotThread) 
    100         QMetaObject.invokeMethod( 
    101             self.task.thread(), "transfer", Qt.BlockingQueuedConnection, 
    102             Q_ARG(object, self.task), 
    103             Q_ARG(object, QThread.currentThread()) 
    104         ) 
    105  
    106         self.eventLoop.processEvents() 
    107  
    108         # Schedule task.run from the event loop. 
    109         self.task.start() 
    110  
    111         # Quit the loop and exit when task finishes or is cancelled. 
    112         # TODO: If the task encounters an critical error it might not emit 
    113         # these signals and this Runnable will never complete. 
    114         self.task.finished.connect(self.eventLoop.quit) 
    115         self.task.cancelled.connect(self.eventLoop.quit) 
    116         self.eventLoop.exec_() 
    117  
    118  
    119 class _Runnable(QRunnable): 
    120     """ 
    121     A QRunnable for running plain functions by a :class:`ThreadExecuter`. 
    122     """ 
    123     def __init__(self, future, func, args, kwargs): 
    124         QRunnable.__init__(self) 
    125         self.future = future 
    126         self.func = func 
    127         self.args = args 
    128         self.kwargs = kwargs 
    129  
    130     def run(self): 
    131         """ 
    132         Reimplemented from QRunnable.run 
    133         """ 
    134         try: 
    135             if not self.future.set_running_or_notify_cancel(): 
    136                 # Was cancelled 
    137                 return 
    138             try: 
    139                 result = self.func(*self.args, **self.kwargs) 
    140             except BaseException, ex: 
    141                 self.future.set_exception(ex) 
    142             else: 
    143                 self.future.set_result(result) 
    144         except BaseException: 
    145             _log.critical("Exception in worker thread.", exc_info=True) 
    146  
    147  
    148 class ThreadExecutor(QObject): 
    149     """ 
    150     ThreadExceuter object class provides an interface for running tasks 
    151     in a thread pool. 
    152  
    153     :param QObject parent: 
    154         Executor's parent instance. 
    155  
    156     :param QThreadPool threadPool: 
    157         Thread pool to be used by the instance of the Executor. If `None` 
    158         then ``QThreadPool.globalInstance()`` will be used. 
    159  
    160     """ 
    161     def __init__(self, parent=None, threadPool=None): 
    162         QObject.__init__(self, parent) 
    163         if threadPool is None: 
    164             threadPool = QThreadPool.globalInstance() 
    165         self._threadPool = threadPool 
    166         self._depot_thread = None 
    167  
    168     def _get_depot_thread(self): 
    169         if self._depot_thread is None: 
    170             self._depot_thread = _TaskDepotThread() 
    171             self._depot_thread.start() 
    172  
    173         return self._depot_thread 
    174  
    175     def submit(self, func, *args, **kwargs): 
    176         """ 
    177         Schedule the `func(*args, **kwargs)` to be executed and return an 
    178         :class:`Future` instance representing the result of the computation. 
    179  
    180         """ 
    181         if isinstance(func, Task): 
    182             if func.thread() is not QThread.currentThread(): 
    183                 raise ValueError("Can only submit Tasks from it's own thread.") 
    184  
    185             if func.parent() is not None: 
    186                 raise ValueError("Can not submit Tasks with a parent.") 
    187  
    188             func.moveToThread(self._get_depot_thread()) 
    189             assert func.thread() is self._get_depot_thread() 
    190             # Use the Task's own Future object 
    191             f = func.future() 
    192             runnable = _TaskRunnable(f, func, args, kwargs) 
    193         else: 
    194             f = Future() 
    195             runnable = _Runnable(f, func, args, kwargs) 
    196         self._threadPool.start(runnable) 
    197  
    198         return f 
    199  
    200     def map(self, func, *iterables): 
    201         futures = [self.submit(func, *args) for args in zip(*iterables)] 
    202  
    203         for f in futures: 
    204             yield f.result() 
    205  
    206     def shutdown(self, wait=True): 
    207         """ 
    208         Shutdown the executor and free all resources. If `wait` is True then 
    209         wait until all pending futures are executed or cancelled. 
    210         """ 
    211         if self._depot_thread is not None: 
    212             QMetaObject.invokeMethod( 
    213                 self._depot_thread, "quit", Qt.AutoConnection) 
    214  
    215         if wait: 
    216             self._threadPool.waitForDone() 
    217             if self._depot_thread: 
    218                 self._depot_thread.wait() 
    219                 self._depot_thread = None 
    220  
    221  
    222 class ExecuteCallEvent(QEvent): 
    223     """ 
    224     Represents an function call from the event loop (used by :class:`Task` 
    225     to schedule the :func:`Task.run` method to be invoked) 
    226  
    227     """ 
    228     ExecuteCall = QEvent.registerEventType() 
    229  
    230     def __init__(self): 
    231         QEvent.__init__(self, ExecuteCallEvent.ExecuteCall) 
    232  
    233  
    234 class Task(QObject): 
    235     """ 
    236     """ 
    237     started = Signal() 
    238     finished = Signal() 
    239     cancelled = Signal() 
    240     resultReady = Signal(object) 
    241     exceptionReady = Signal(Exception) 
    242  
    243     def __init__(self, parent=None, function=None): 
    244         QObject.__init__(self, parent) 
    245         self.function = function 
    246  
    247         self._future = Future() 
    248  
    249     def run(self): 
    250         if self.function is None: 
    251             raise NotImplementedError 
    252         else: 
    253             return self.function() 
    254  
    255     def start(self): 
    256         QCoreApplication.postEvent(self, ExecuteCallEvent()) 
    257  
    258     def future(self): 
    259         return self._future 
    260  
    261     def result(self, timeout=None): 
    262         return self._future.result(timeout) 
    263  
    264     def _execute(self): 
    265         try: 
    266             if not self._future.set_running_or_notify_cancel(): 
    267                 self.cancelled.emit() 
    268                 return 
    269  
    270             self.started.emit() 
    271             try: 
    272                 result = self.run() 
    273             except BaseException, ex: 
    274                 self._future.set_exception(ex) 
    275                 self.exceptionReady.emit(ex) 
    276             else: 
    277                 self._future.set_result(result) 
    278                 self.resultReady.emit(result) 
    279  
    280             self.finished.emit() 
    281         except BaseException: 
    282             _log.critical("Exception in Task", exc_info=True) 
    283  
    284     def customEvent(self, event): 
    285         if event.type() == ExecuteCallEvent.ExecuteCall: 
    286             self._execute() 
    287         else: 
    288             QObject.customEvent(self, event) 
    289  
    290  
    291 def futures_iter(futures): 
    292     for f in futures: 
    293         yield f.result() 
    294  
    295  
    296 class TimeoutError(Exception): 
    297     pass 
    298  
    299  
    300 class CancelledError(Exception): 
    301     pass 
    302  
    303  
    304 class Future(object): 
    305     """ 
    306     A :class:`Future` class represents a result of an asynchronous 
    307     computation. 
    308  
    309     """ 
    310     Pending, Canceled, Running, Finished = 1, 2, 4, 8 
    311  
    312     def __init__(self): 
    313         self._watchers = [] 
    314         self._state = Future.Pending 
    315         self._condition = threading.Condition() 
    316         self._result = None 
    317         self._exception = None 
    318  
    319     def _set_state(self, state): 
    320         if self._state != state: 
    321             self._state = state 
    322             for w in self._watchers: 
    323                 w(self, state) 
    324  
    325     def cancel(self): 
    326         """ 
    327         Attempt to cancel the the call. Return `False` if the call is 
    328         already in progress and cannot be canceled, otherwise return `True`. 
    329  
    330         """ 
    331         with self._condition: 
    332             if self._state in [Future.Running, Future.Finished]: 
    333                 return False 
    334             elif self._state == Future.Canceled: 
    335                 return True 
    336             else: 
    337                 self._state = Future.Canceled 
    338                 self._condition.notify_all() 
    339  
    340         return True 
    341  
    342     def cancelled(self): 
    343         """ 
    344         Return `True` if call was successfully cancelled. 
    345         """ 
    346         with self._condition: 
    347             return self._state == Future.Canceled 
    348  
    349     def done(self): 
    350         """ 
    351         Return `True` if the call was successfully cancelled or finished 
    352         running. 
    353  
    354         """ 
    355         with self._condition: 
    356             return self._state in [Future.Canceled, Future.Finished] 
    357  
    358     def running(self): 
    359         """ 
    360         Return True if the call is currently being executed. 
    361         """ 
    362         with self._condition: 
    363             return self._state == Future.Running 
    364  
    365     def _get_result(self): 
    366         if self._exception: 
    367             raise self._exception 
    368         else: 
    369             return self._result 
    370  
    371     def result(self, timeout=None): 
    372         """ 
    373         Return the result of the :class:`Futures` computation. If `timeout` 
    374         is `None` the call will block until either the computation finished 
    375         or is cancelled. 
    376         """ 
    377         with self._condition: 
    378             if self._state == Future.Finished: 
    379                 return self._get_result() 
    380             elif self._state == Future.Canceled: 
    381                 raise CancelledError() 
    382  
    383             self._condition.wait(timeout) 
    384  
    385             if self._state == Future.Finished: 
    386                 return self._get_result() 
    387             elif self._state == Future.Canceled: 
    388                 raise CancelledError() 
    389             else: 
    390                 raise TimeoutError() 
    391  
    392     def exception(self, timeout=None): 
    393         """ 
    394         Return the exception instance (if any) resulting from the execution 
    395         of the :class:`Future`. Can raise a :class:`CancelledError` if the 
    396         computation was cancelled. 
    397  
    398         """ 
    399         with self._condition: 
    400             if self._state == Future.Finished: 
    401                 return self._exception 
    402             elif self._state == Future.Canceled: 
    403                 raise CancelledError() 
    404  
    405             self._condition.wait(timeout) 
    406  
    407             if self._state == Future.Finished: 
    408                 return self._exception 
    409             elif self._state == Future.Canceled: 
    410                 raise CancelledError() 
    411             else: 
    412                 raise TimeoutError() 
    413  
    414     def set_result(self, result): 
    415         """ 
    416         Set the result of the computation (called by the worker thread). 
    417         """ 
    418         with self._condition: 
    419             self._result = result 
    420             self._state = Future.Finished 
    421             self._condition.notify_all() 
    422  
    423     def set_exception(self, exception): 
    424         """ 
    425         Set the exception instance that was raised by the computation 
    426         (called by the worker thread). 
    427  
    428         """ 
    429         with self._condition: 
    430             self._exception = exception 
    431             self._state = Future.Finished 
    432             self._condition.notify_all() 
    433  
    434     def set_running_or_notify_cancel(self): 
    435         with self._condition: 
    436             if self._state == Future.Canceled: 
    437                 return False 
    438             elif self._state == Future.Pending: 
    439                 self._state = Future.Running 
    440                 return True 
    441             else: 
    442                 raise Exception() 
    443  
    444  
    445 class StateChangedEvent(QEvent): 
    446     """ 
    447     Represents a change in the internal state of a :class:`Future`. 
    448     """ 
    449     StateChanged = QEvent.registerEventType() 
    450  
    451     def __init__(self, state): 
    452         QEvent.__init__(self, StateChangedEvent.StateChanged) 
    453         self._state = state 
    454  
    455     def state(self): 
    456         """ 
    457         Return the new state (Future.Pending, Future.Cancelled, ...). 
    458         """ 
    459         return self._state 
    460  
    461  
    462 class FutureWatcher(QObject): 
    463     """ 
    464     A `FutureWatcher` class provides a convenient interface to the 
    465     :class:`Future` instance using Qt's signals. 
    466  
    467     :param :class:`Future` future: 
    468         A :class:`Future` instance to watch. 
    469     :param :class:`QObject` parent: 
    470         Object's parent instance. 
    471  
    472     """ 
    473     #: The future was cancelled. 
    474     cancelled = Signal() 
    475  
    476     #: The future has finished. 
    477     finished = Signal() 
    478  
    479     #: The future has started computation. 
    480     started = Signal() 
    481  
    482     def __init__(self, future, parent=None): 
    483         QObject.__init__(self, parent) 
    484         self._future = future 
    485  
    486         self._future._watchers.append(self._stateChanged) 
    487  
    488     def isCancelled(self): 
    489         """ 
    490         Was the future cancelled. 
    491         """ 
    492         return self._future.cancelled() 
    493  
    494     def isDone(self): 
    495         """ 
    496         Is the future done (was cancelled or has finished). 
    497         """ 
    498         return self._future.done() 
    499  
    500     def isRunning(self): 
    501         """ 
    502         Is the future running (i.e. has started). 
    503         """ 
    504         return self._future.running() 
    505  
    506     def isStarted(self): 
    507         """ 
    508         Has the future computation started. 
    509         """ 
    510         return self._future.running() 
    511  
    512     def result(self): 
    513         """ 
    514         Return the result of the computation. 
    515         """ 
    516         return self._future.result() 
    517  
    518     def exception(self): 
    519         """ 
    520         Return the exception instance or `None` if no exception was raised. 
    521         """ 
    522         return self._future.exception() 
    523  
    524     def customEvent(self, event): 
    525         """ 
    526         Reimplemented from `QObject.customEvent`. 
    527         """ 
    528         if event.type() == StateChangedEvent.StateChanged: 
    529             if event.state() == Future.Canceled: 
    530                 self.cancelled.emit() 
    531             elif event.state() == Future.Running: 
    532                 self.started.emit() 
    533             elif event.state() == Future.Finished: 
    534                 self.finished.emit() 
    535             return 
    536  
    537         return QObject.customEvent(self, event) 
    538  
    539     def _stateChanged(self, future, state): 
    540         """ 
    541         The `future` state has changed (called by :class:`Future`). 
    542         """ 
    543         ev = StateChangedEvent(state) 
    544  
    545         if self.thread() is QThread.currentThread(): 
    546             QCoreApplication.sendEvent(self, ev) 
    547         else: 
    548             QCoreApplication.postEvent(self, ev) 
    549  
    550  
    551 class methodinvoke(object): 
    552     """ 
    553     Create an QObject method wrapper that invokes the method asynchronously 
    554     in the object's own thread. 
    555  
    556     :param obj: 
    557         A QObject instance. 
    558     :param str method: 
    559         The method name. 
    560     :param tuple arg_types: 
    561         A tuple of positional argument types. 
    562  
    563     """ 
    564     def __init__(self, obj, method, arg_types=()): 
    565         self.obj = obj 
    566         self.method = method 
    567         self.arg_types = tuple(arg_types) 
    568  
    569     def __call__(self, *args): 
    570         args = [Q_ARG(atype, arg) for atype, arg in zip(self.arg_types, args)] 
    571         QMetaObject.invokeMethod( 
    572             self.obj, self.method, Qt.QueuedConnection, 
    573             *args 
    574         ) 
    575  
    576  
    577 try: 
    578     import unittest2 as unittest 
    579 except ImportError: 
    580     import unittest 
    581  
    582  
    583 class TestFutures(unittest.TestCase): 
    584  
    585     def test_futures(self): 
    586         f = Future() 
    587         self.assertEqual(f.done(),  False) 
    588         self.assertEqual(f.running(),  False) 
    589  
    590         self.assertTrue(f.cancel()) 
    591         self.assertTrue(f.cancelled()) 
    592  
    593         with self.assertRaises(CancelledError): 
    594             f.result() 
    595  
    596         with self.assertRaises(CancelledError): 
    597             f.exception() 
    598  
    599         f = Future() 
    600         f.set_running_or_notify_cancel() 
    601  
    602         with self.assertRaises(TimeoutError): 
    603             f.result(0.1) 
    604  
    605         with self.assertRaises(TimeoutError): 
    606             f.exception(0.1) 
    607  
    608         f = Future() 
    609         f.set_running_or_notify_cancel() 
    610         f.set_result("result") 
    611  
    612         self.assertEqual(f.result(), "result") 
    613         self.assertEqual(f.exception(), None) 
    614  
    615         f = Future() 
    616         f.set_running_or_notify_cancel() 
    617  
    618         f.set_exception(Exception("foo")) 
    619  
    620         with self.assertRaises(Exception): 
    621             f.result() 
    622  
    623  
    624 class TestExecutor(unittest.TestCase): 
    625  
    626     def setUp(self): 
    627         self.app = QCoreApplication([]) 
    628  
    629     def test_executor(self): 
    630         executor = ThreadExecutor() 
    631         f1 = executor.submit(pow, 100, 100) 
    632  
    633         f2 = executor.submit(lambda: 1 / 0) 
    634  
    635         f3 = executor.submit(QThread.currentThread) 
    636  
    637         self.assertTrue(f1.result(), pow(100, 100)) 
    638  
    639         with self.assertRaises(ZeroDivisionError): 
    640             f2.result() 
    641  
    642         self.assertIsInstance(f2.exception(), ZeroDivisionError) 
    643  
    644         self.assertIsNot(f3.result(), QThread.currentThread()) 
    645  
    646     def test_methodinvoke(self): 
    647         executor = ThreadExecutor() 
    648         state = [None, None] 
    649  
    650         class StateSetter(QObject): 
    651             @Slot(object) 
    652             def set_state(self, value): 
    653                 state[0] = value 
    654                 state[1] = QThread.currentThread() 
    655  
    656         def func(callback): 
    657             callback(QThread.currentThread()) 
    658  
    659         obj = StateSetter() 
    660         f1 = executor.submit(func, methodinvoke(obj, "set_state", (object,))) 
    661         f1.result() 
    662  
    663         # So invoked method can be called 
    664         QCoreApplication.processEvents() 
    665  
    666         self.assertIs(state[1], QThread.currentThread(), 
    667                       "set_state was called from the wrong thread") 
    668  
    669         self.assertIsNot(state[0], QThread.currentThread(), 
    670                          "set_state was invoked in the main thread") 
    671  
    672         executor.shutdown(wait=True) 
    673  
    674     def test_executor_map(self): 
    675         executor = ThreadExecutor() 
    676  
    677         r = executor.map(pow, range(1000), range(1000)) 
    678  
    679         results = list(r) 
    680  
    681         self.assertTrue(len(results) == 1000) 
    682  
    683  
    684 class TestFutureWatcher(unittest.TestCase): 
    685     def setUp(self): 
    686         self.app = QCoreApplication([]) 
    687  
    688     def test_watcher(self): 
    689         executor = ThreadExecutor() 
    690         f = executor.submit(QThread.currentThread) 
    691         watcher = FutureWatcher(f) 
    692  
    693         if f.cancel(): 
    694             self.assertTrue(watcher.isCancelled()) 
    695  
    696         executor.shutdown() 
    697  
    698  
    699 class TestTask(unittest.TestCase): 
    700     def setUp(self): 
    701         self.app = QCoreApplication([]) 
    702  
    703     def test_task(self): 
    704         results = [] 
    705  
    706         task = Task(function=QThread.currentThread) 
    707         task.resultReady.connect(results.append) 
    708  
    709         task.start() 
    710         self.app.processEvents() 
    711  
    712         self.assertSequenceEqual(results, [QThread.currentThread()]) 
    713  
    714         results = [] 
    715  
    716         thread = QThread() 
    717         thread.start() 
    718  
    719         task = Task(function=QThread.currentThread) 
    720  
    721         task.moveToThread(thread) 
    722  
    723         self.assertIsNot(task.thread(), QThread.currentThread()) 
    724         self.assertIs(task.thread(), thread) 
    725  
    726         task.resultReady.connect(results.append, Qt.DirectConnection) 
    727         task.start() 
    728  
    729         f = task.future() 
    730  
    731         self.assertIsNot(f.result(3), QThread.currentThread()) 
    732  
    733         self.assertIs(f.result(3), results[-1]) 
    734  
    735     def test_executor(self): 
    736         executor = ThreadExecutor() 
    737  
    738         f = executor.submit(QThread.currentThread) 
    739  
    740         self.assertIsNot(f.result(3), QThread.currentThread()) 
    741  
    742         f = executor.submit(lambda: 1 / 0) 
    743  
    744         with self.assertRaises(ZeroDivisionError): 
    745             f.result() 
    746  
    747         results = [] 
    748         task = Task(function=QThread.currentThread) 
    749         task.resultReady.connect(results.append, Qt.DirectConnection) 
    750  
    751         f = executor.submit(task) 
    752  
    753         self.assertIsNot(f.result(3), QThread.currentThread()) 
    754  
    755         executor.shutdown() 
    756  
    757  
    758 ############################################ 
    759 # DEPRECATED (not to mention extremely ugly) 
    760 ############################################ 
    761  
     11 
     12     
     13from OWWidget import * 
    76214 
    76315class AsyncCall(QObject): 
    764     """ 
    765     A wrapper class for async function calls using Qt's signals for 
    766     communication between threads 
     16    """ A wrapper class for async function calls using 
     17    Qt's signals for communication with GUI thread 
    76718 
    76819    Arguments: 
     
    77324           (for this to work `thread` argument must be `None`) 
    77425        - `parent`: parent object (should be None for most cases) 
    775  
     26         
    77627    Signals: 
    77728        - `starting()` 
    778         - `finished(QString)` - emitted when finished with an argument 'OK' 
    779           on success or repr(ex) on error. 
    780         - `finished(PyQt_PyObject, QString)` - same as above but also 
    781           pass self as an argument. 
    782         - `unhandledException(PyQt_PyObject)` - emitted on error 
    783           with `sys.exc_info()` argument. 
    784         - `resultReady(PyQt_PyObject)` - emitted on success with 
    785           function call result as argument. 
    786  
     29        - `finished(QString)` - emited when finished with an argument 'OK' on success or repr(ex) on error 
     30        - `finished(PyQt_PyObject, QString)` - same as above but also pass self as an argument  
     31        - `unhandledException(PyQt_PyObject)` - emited on error with `sys.exc_info()` argument 
     32        - `resultReady(PyQt_PyObject)` - emited on success with function call result as argument 
    78733    """ 
    788  
    789     def __init__(self, func=None, args=(), kwargs={}, thread=None, 
    790                  threadPool=None, parent=None): 
     34    def __init__(self, func=None, args=(), kwargs={}, thread=None, threadPool=None, parent=None): 
    79135        QObject.__init__(self, parent) 
    79236        self.func = func 
     
    79438        self._kwargs = kwargs 
    79539        self.threadPool = None 
    796  
     40         
    79741        self._connected = True 
    79842        self._cancelRequested = False 
    79943        self._started = False 
    80044        self._cancelled = False 
    801  
     45         
    80246        if thread is not None: 
    80347            self.moveToThread(thread) 
     
    80650                threadPool = QThreadPool.globalInstance() 
    80751            self.threadPool = threadPool 
    808             self._runnable = _RunnableAsyncCall(self) 
     52            self._runnable = RunnableAsyncCall(self) 
    80953            self.threadPool.start(self._runnable) 
    81054            self._connected = False 
    81155            return 
    812  
    813         self.connect(self, SIGNAL("_async_start()"), self.execute, 
    814                      Qt.QueuedConnection) 
     56             
     57        self.connect(self, SIGNAL("_async_start()"), self.execute, Qt.QueuedConnection) 
     58         
     59 
    81560 
    81661    @pyqtSignature("execute()") 
     
    82469            self.emit(SIGNAL("finished(QString)"), QString("Cancelled")) 
    82570            return 
    826  
    82771        self._started = True 
    82872        self.emit(SIGNAL("starting()")) 
    82973        try: 
    830             self.result = self.func(*self._args, **self._kwargs) 
     74            self.result  = self.func(*self._args, **self._kwargs) 
    83175        except Exception, ex: 
    832             print >> sys.stderr, "Exception in thread ", \ 
    833                   QThread.currentThread(), " while calling ", self.func 
     76            print >> sys.stderr, "Exception in thread ", QThread.currentThread(), " while calling ", self.func 
    83477            self.emit(SIGNAL("finished(QString)"), QString(repr(ex))) 
    835             self.emit(SIGNAL("finished(PyQt_PyObject, QString)"), 
    836                       self, QString(repr(ex))) 
    837             self.emit(SIGNAL("unhandledException(PyQt_PyObject)"), 
    838                       sys.exc_info()) 
     78            self.emit(SIGNAL("finished(PyQt_PyObject, QString)"), self, QString(repr(ex))) 
     79            self.emit(SIGNAL("unhandledException(PyQt_PyObject)"), sys.exc_info()) 
    83980 
    84081            self._exc_info = sys.exc_info() 
     
    84384 
    84485        self.emit(SIGNAL("finished(QString)"), QString("Ok")) 
    845         self.emit(SIGNAL("finished(PyQt_PyObject, QString)"), 
    846                   self, QString("Ok")) 
    847         self.emit(SIGNAL("resultReady(PyQt_PyObject)"), 
    848                   self.result) 
     86        self.emit(SIGNAL("finished(PyQt_PyObject, QString)"), self, QString("Ok")) 
     87        self.emit(SIGNAL("resultReady(PyQt_PyObject)"), self.result) 
    84988        self._status = 0 
     89 
    85090 
    85191    def __call__(self, *args, **kwargs): 
     
    85595            self.func = partial(self.func, *self._args, **self._kwargs) 
    85696            self._args, self._kwargs = args, kwargs 
    857  
     97             
    85898        if not self._connected: 
    859             # delay until event loop initialized by _RunnableAsyncCall 
    860             QTimer.singleShot(50, self.__call__) 
     99            QTimer.singleShot(50, self.__call__) # delay until event loop initialized by RunnableAsyncCall 
    861100            return 
    862101        else: 
    863102            self.emit(SIGNAL("_async_start()")) 
     103 
    864104 
    865105    def apply_async(self, func, args, kwargs): 
     
    870110        self.__call__() 
    871111 
     112 
    872113    def poll(self): 
    873114        """ Return the state of execution. 
    874115        """ 
    875116        return getattr(self, "_status", None) 
    876  
     117     
     118     
    877119    def join(self, processEvents=True): 
    878120        """ Wait until the execution finishes. 
     
    882124            if processEvents and QThread.currentThread() is qApp.thread(): 
    883125                qApp.processEvents() 
    884  
     126                 
    885127    def get_result(self, processEvents=True): 
    886128        """ Block until the computation completes and return the call result. 
    887129        If the execution resulted in an exception, this method will re-raise 
    888         it. 
     130        it.  
    889131        """ 
    890132        self.join(processEvents=processEvents) 
    891         if self.poll() != 0: 
     133        if self.poll() != 0:  
    892134            # re-raise the error 
    893135            raise self._exc_info[0], self._exc_info[1] 
    894136        else: 
    895137            return self.result 
    896  
     138     
    897139    def emitAdvance(self, count=1): 
    898140        self.emit(SIGNAL("advance()")) 
    899141        self.emit(SIGNAL("advance(int)"), count) 
    900  
     142         
     143         
    901144    def emitProgressChanged(self, value): 
    902145        self.emit(SIGNAL("progressChanged(float)"), value) 
    903  
     146         
     147     
    904148    @pyqtSignature("moveToAndExecute(PyQt_PyObject)") 
    905149    def moveToAndExecute(self, thread): 
    906150        self.moveToThread(thread) 
    907  
    908         self.connect(self, SIGNAL("_async_start()"), self.execute, 
    909                      Qt.QueuedConnection) 
    910  
     151         
     152        self.connect(self, SIGNAL("_async_start()"), self.execute, Qt.QueuedConnection) 
     153         
    911154        self.emit(SIGNAL("_async_start()")) 
    912  
     155         
     156         
    913157    @pyqtSignature("moveToAndInit(PyQt_PyObject)") 
    914158    def moveToAndInit(self, thread): 
    915159        self.moveToThread(thread) 
    916  
    917         self.connect(self, SIGNAL("_async_start()"), self.execute, 
    918                      Qt.QueuedConnection) 
     160         
     161        self.connect(self, SIGNAL("_async_start()"), self.execute, Qt.QueuedConnection) 
    919162        self._connected = True 
    920  
     163         
    921164 
    922165class WorkerThread(QThread): 
     
    925168    def run(self): 
    926169        self.exec_() 
    927  
    928  
    929 class _RunnableTask(QRunnable): 
     170         
     171         
     172class RunnableTask(QRunnable): 
    930173    """ Wrapper for an AsyncCall 
    931174    """ 
     
    934177        self.setAutoDelete(False) 
    935178        self._call = call 
    936  
     179         
    937180    def run(self): 
    938181        if isinstance(self._call, AsyncCall): 
    939182            self.eventLoop = QEventLoop() 
    940183            self.eventLoop.processEvents() 
    941             QObject.connect(self._call, SIGNAL("finished(QString)"), 
    942                             lambda str: self.eventLoop.quit()) 
    943             QMetaObject.invokeMethod(self._call, "moveToAndInit", 
    944                                      Qt.QueuedConnection, 
    945                                      Q_ARG("PyQt_PyObject", 
    946                                            QThread.currentThread())) 
     184            QObject.connect(self._call, SIGNAL("finished(QString)"), lambda str: self.eventLoop.quit()) 
     185            QMetaObject.invokeMethod(self._call, "moveToAndInit", Qt.QueuedConnection, Q_ARG("PyQt_PyObject", QThread.currentThread())) 
    947186            self.eventLoop.processEvents() 
    948187            self.eventLoop.exec_() 
    949188        else: 
    950189            self._return = self._call() 
    951  
    952  
    953 class _RunnableAsyncCall(_RunnableTask): 
     190             
     191             
     192class RunnableAsyncCall(RunnableTask): 
    954193    def run(self): 
    955194        self.eventLoop = QEventLoop() 
    956195        self.eventLoop.processEvents() 
    957         QObject.connect(self._call, SIGNAL("finished(QString)"), 
    958                         lambda str: self.eventLoop.quit()) 
    959         QMetaObject.invokeMethod(self._call, "moveToAndInit", 
    960                                  Qt.QueuedConnection, 
    961                                  Q_ARG("PyQt_PyObject", 
    962                                        QThread.currentThread())) 
     196        QObject.connect(self._call, SIGNAL("finished(QString)"), lambda str: self.eventLoop.quit()) 
     197        QMetaObject.invokeMethod(self._call, "moveToAndInit", Qt.QueuedConnection, Q_ARG("PyQt_PyObject", QThread.currentThread())) 
    963198        self.eventLoop.processEvents() 
    964199        self.eventLoop.exec_() 
    965200 
    966  
    967 def createTask(call, args=(), kwargs={}, onResult=None, onStarted=None, 
    968                onFinished=None, onError=None, thread=None, threadPool=None): 
     201def createTask(call, args=(), kwargs={}, onResult=None, onStarted=None, onFinished=None, onError=None, thread=None, threadPool=None): 
    969202    async = AsyncCall(thread=thread, threadPool=threadPool) 
    970203    if onResult is not None: 
    971         async.connect(async, SIGNAL("resultReady(PyQt_PyObject)"), onResult, 
    972                       Qt.QueuedConnection) 
     204        async.connect(async, SIGNAL("resultReady(PyQt_PyObject)"), onResult, Qt.QueuedConnection) 
    973205    if onStarted is not None: 
    974         async.connect(async, SIGNAL("starting()"), onStarted, 
    975                       Qt.QueuedConnection) 
     206        async.connect(async, SIGNAL("starting()"), onStarted, Qt.QueuedConnection) 
    976207    if onFinished is not None: 
    977         async.connect(async, SIGNAL("finished(QString)"), onFinished, 
    978                       Qt.QueuedConnection) 
     208        async.connect(async, SIGNAL("finished(QString)"), onFinished, Qt.QueuedConnection) 
    979209    if onError is not None: 
    980         async.connect(async, SIGNAL("unhandledException(PyQt_PyObject)"), 
    981                       onError, Qt.QueuedConnection) 
     210        async.connect(async, SIGNAL("unhandledException(PyQt_PyObject)"), onError, Qt.QueuedConnection) 
    982211    async.apply_async(call, args, kwargs) 
    983212    return async 
    984  
    985  
     213         
     214from functools import partial 
     215         
     216class ProgressBar(QObject): 
     217    """ A thread safe progress callback using Qt's signal mechanism 
     218    to deliver progress updates to the GUI thread. Make sure this object instance 
     219    is created in the GUI thread or is a child of an object from the GUI thread 
     220    """ 
     221     
     222    def __init__(self, widget, iterations, parent=None): 
     223        QObject.__init__(self, parent) 
     224        assert (qApp.thread() is self.thread()) 
     225        self.iter = iterations 
     226        self.widget = widget 
     227        self.count = 0 
     228        self.widget.progressBarInit() 
     229         
     230    def advance(self, count=1): 
     231        self.count += count 
     232        value = int(self.count*100/self.iter) 
     233        QMetaObject.invokeMethod(self, "queuedInvoke", Qt.QueuedConnection, Q_ARG("PyQt_PyObject", partial(self.widget.progressBarSet, value))) 
     234 
     235    def finish(self): 
     236        QMetaObject.invokeMethod(self, "queuedInvoke", Qt.QueuedConnection, Q_ARG("PyQt_PyObject", self.widget.progressBarFinished)) 
     237         
     238    def progressBarSet(self, value): 
     239        QMetaObject.invokeMethod(self, "queuedInvoke", Qt.QueuedConnection, Q_ARG("PyQt_PyObject", partial(self.widget.progressBarSet, value))) 
     240     
     241    @pyqtSignature("queuedInvoke(PyQt_PyObject)") 
     242    def queuedInvoke(self, func): 
     243        func() 
     244         
     245         
    986246class synchronized(object): 
    987247    def __init__(self, object, mode=QMutex.Recursive): 
     
    989249            object._mutex = QMutex(mode) 
    990250        self.mutex = object._mutex 
    991  
     251         
    992252    def __enter__(self): 
    993253        self.mutex.lock() 
    994254        return self 
    995  
     255     
    996256    def __exit__(self, exc_type=None, exc_value=None, tb=None): 
    997257        self.mutex.unlock() 
     258 
     259_global_thread_pools = {} 
     260         
     261         
     262def threadPool(self, class_="global", private=False, maxCount=None): 
     263    with synchronized(threadPool): 
     264        if private: 
     265            pools = self._private_thread_pools 
     266        else: 
     267            pools = _global_thread_pools 
     268             
     269        if class_ not in pools: 
     270            if class_ == "global": 
     271                instance = QThreadPool.globalInstance() 
     272            else: 
     273                instance = QThreadPool() 
     274                instance.setMaxThreadCount(maxCount) 
     275            pools[class_] = instance 
     276        return pools[class_] 
     277     
     278OWBaseWidget.threadPool = threadPool 
     279         
     280 
     281"""\ 
     282A multiprocessing like API 
     283========================== 
     284 
     285Incomplette 
     286""" 
     287 
     288class Process(AsyncCall): 
     289    _process_id = 0 
     290    def __init__(group=None, target=None, name=None, args=(), kwargs={}): 
     291        self.worker = WorkerThread() 
     292        AsyncCall.__init__(self, thread=self.worker) 
     293         
     294        self.conenct(self, SIGANL("finished(QString)"), self.onFinished, Qt.QueuedConnection) 
     295        self.connect(self, SIGNAL("finished(QString)"), lambda:self.worker.quit(), Qt.QueuedConnection) 
     296        self.target = target 
     297        self.args = args 
     298        self.kwargs = kwargs 
     299        if name is None: 
     300            self.name = "Process-%i" % self._process_id 
     301            Process._process_id += 1 
     302        else: 
     303            self.name = name 
     304        self.exitcode = -1 
     305             
     306    def start(self): 
     307        self.worker.start() 
     308        self.async_call(self.run) 
     309 
     310    def run(self): 
     311        self._result = self.target(*self.args, **self.kwargs) 
     312          
     313    def join(self): 
     314        while self.poll() is None: 
     315            time.sleep(10) 
     316 
     317    def is_alive(self): 
     318        return self.poll() is None 
     319     
     320    def onFinished(self, string): 
     321        self.exitcode = self._status 
     322         
     323    def terminate(self): 
     324        self.worker.terminate() 
     325     
     326from Queue import Queue 
     327 
     328class Pool(QObject): 
     329    def __init__(self, processes=None): 
     330        if processes is None: 
     331            import multiprocessing 
     332            processes = multiprocessing.cpu_count() 
     333        self.processes = processes 
     334        self.pool = [Process() for i in range(processes)] 
     335        self._i = 0 
     336    def get_process(self): 
     337        self._i = (self._i + 1) % len(self.pool) 
     338        return self.pool[self._i] 
     339      
     340    def apply_async(func, args, kwargs): 
     341        process = self.get_process() 
     342        process.start() 
     343         
     344    def start(self, ): 
     345        pass 
     346     
  • Orange/OrangeWidgets/OWDatabasesUpdate.py

    r11484 r11437  
    55 
    66from datetime import datetime 
    7 from functools import partial 
    8 from collections import namedtuple 
    9  
    10 from PyQt4.QtCore import pyqtSignal as Signal, pyqtSlot as Slot 
     7 
     8import Orange 
    119 
    1210from Orange.utils import serverfiles, environ 
     
    1412 
    1513from OWWidget import * 
    16  
    17 from OWConcurrent import Task, ThreadExecutor, methodinvoke 
     14from OWConcurrent import * 
    1815 
    1916import OWGUIEx 
    20  
    21  
    22 #: Update file item states 
    23 AVAILABLE, CURRENT, OUTDATED, DEPRECATED = range(4) 
    24  
    25 _icons_dir = os.path.join(environ.canvas_install_dir, "icons") 
    26  
    27  
    28 def icon(name): 
    29     return QIcon(os.path.join(_icons_dir, name)) 
    3017 
    3118 
     
    3320    """Progress Bar with and `advance()` slot. 
    3421    """ 
    35     @Slot() 
     22    @pyqtSignature("advance()") 
    3623    def advance(self): 
    37         """ 
    38         Advance the progress bar by 1 
    39         """ 
    4024        self.setValue(self.value() + 1) 
    4125 
    4226 
    43 class UpdateOptionButton(QToolButton): 
    44     def event(self, event): 
    45         if event.type() == QEvent.Wheel: 
    46             # QAbstractButton automatically accepts all mouse events (in 
    47             # event method) for disabled buttons. This can prevent scrolling 
    48             # in a scroll area when a disabled button scrolls under the 
    49             # mouse. 
    50             event.ignore() 
    51             return False 
    52         else: 
    53             return QToolButton.event(self, event) 
     27class ProgressBarRedirect(QObject): 
     28    def __init__(self, parent, redirect): 
     29        QObject.__init__(self, parent) 
     30        self.redirect = redirect 
     31        self._delay = False 
     32 
     33    @pyqtSignature("advance()") 
     34    def advance(self): 
     35        # delay OWBaseWidget.progressBarSet call, because it calls 
     36        # qApp.processEvents which can result in 'event queue climbing' 
     37        # and max. recursion error if GUI thread gets another advance 
     38        # signal before it finishes with this one 
     39        if not self._delay: 
     40            try: 
     41                self._delay = True 
     42                self.redirect.advance() 
     43            finally: 
     44                self._delay = False 
     45        else: 
     46            QTimer.singleShot(10, self.advance) 
     47 
     48_icons_dir = os.path.join(environ.canvas_install_dir, "icons") 
     49 
     50 
     51def icon(name): 
     52    return QIcon(os.path.join(_icons_dir, name)) 
    5453 
    5554 
     
    5857    A Widget with download/update/remove options. 
    5958    """ 
    60     #: Install/update button was clicked 
    61     installClicked = Signal() 
    62     #: Remove button was clicked. 
    63     removeClicked = Signal() 
    64  
    65     def __init__(self, state=AVAILABLE, parent=None): 
    66         QWidget.__init__(self, parent) 
     59    def __init__(self, updateCallback, removeCallback, state, *args): 
     60        QWidget.__init__(self, *args) 
     61        self.updateCallback = updateCallback 
     62        self.removeCallback = removeCallback 
    6763        layout = QHBoxLayout() 
    6864        layout.setSpacing(1) 
    6965        layout.setContentsMargins(1, 1, 1, 1) 
    70         self.installButton = UpdateOptionButton(self) 
    71         self.installButton.setIcon(icon("update.png")) 
    72         self.installButton.setToolTip("Download") 
    73  
    74         self.removeButton = UpdateOptionButton(self) 
     66        self.updateButton = QToolButton(self) 
     67        self.updateButton.setIcon(icon("update.png")) 
     68        self.updateButton.setToolTip("Download") 
     69 
     70        self.removeButton = QToolButton(self) 
    7571        self.removeButton.setIcon(icon("delete.png")) 
    7672        self.removeButton.setToolTip("Remove from system") 
    7773 
    78         self.installButton.clicked.connect(self.installClicked) 
    79         self.removeButton.clicked.connect(self.removeClicked) 
    80  
    81         layout.addWidget(self.installButton) 
     74        self.connect(self.updateButton, SIGNAL("released()"), 
     75                     self.updateCallback) 
     76        self.connect(self.removeButton, SIGNAL("released()"), 
     77                     self.removeCallback) 
     78 
     79        self.setMaximumHeight(30) 
     80        layout.addWidget(self.updateButton) 
    8281        layout.addWidget(self.removeButton) 
    8382        self.setLayout(layout) 
    84  
    85         self.setMaximumHeight(30) 
    86  
    87         self.state = -1 
    88         self.setState(state) 
    89  
    90     def setState(self, state): 
    91         """ 
    92         Set the current update state for the widget (AVAILABLE, 
    93         CURRENT, OUTDATED or DEPRECTED). 
    94  
    95         """ 
    96         if self.state != state: 
    97             self.state = state 
    98             self._update() 
    99  
    100     def _update(self): 
    101         if self.state == AVAILABLE: 
    102             self.installButton.setIcon(icon("update.png")) 
    103             self.installButton.setToolTip("Download") 
    104             self.installButton.setEnabled(True) 
     83        self.SetState(state) 
     84 
     85    def SetState(self, state): 
     86        self.state = state 
     87        if state == 0: 
     88            self.updateButton.setIcon(icon("update1.png")) 
     89            self.updateButton.setToolTip("Update") 
     90            self.updateButton.setEnabled(False) 
     91            self.removeButton.setEnabled(True) 
     92        elif state == 1: 
     93            self.updateButton.setIcon(icon("update1.png")) 
     94            self.updateButton.setToolTip("Update") 
     95            self.updateButton.setEnabled(True) 
     96            self.removeButton.setEnabled(True) 
     97        elif state == 2: 
     98            self.updateButton.setIcon(icon("update.png")) 
     99            self.updateButton.setToolTip("Download") 
     100            self.updateButton.setEnabled(True) 
    105101            self.removeButton.setEnabled(False) 
    106         elif self.state == CURRENT: 
    107             self.installButton.setIcon(icon("update1.png")) 
    108             self.installButton.setToolTip("Update") 
    109             self.installButton.setEnabled(False) 
     102        elif state == 3: 
     103            self.updateButton.setIcon(icon("update.png")) 
     104            self.updateButton.setToolTip("") 
     105            self.updateButton.setEnabled(False) 
    110106            self.removeButton.setEnabled(True) 
    111         elif self.state == OUTDATED: 
    112             self.installButton.setIcon(icon("update1.png")) 
    113             self.installButton.setToolTip("Update") 
    114             self.installButton.setEnabled(True) 
    115             self.removeButton.setEnabled(True) 
    116         elif self.state == DEPRECATED: 
    117             self.installButton.setIcon(icon("update.png")) 
    118             self.installButton.setToolTip("") 
    119             self.installButton.setEnabled(False) 
    120             self.removeButton.setEnabled(True) 
    121         else: 
    122             raise ValueError("Invalid state %r" % self._state) 
     107        else: 
     108            raise ValueError("Invalid state %r" % state) 
    123109 
    124110 
    125111class UpdateTreeWidgetItem(QTreeWidgetItem): 
    126     """ 
    127     A QTreeWidgetItem for displaying an UpdateItem. 
    128  
    129     :param UpdateItem item: 
    130         The update item for display. 
    131  
    132     """ 
    133     STATE_STRINGS = {0: "not downloaded", 
    134                      1: "downloaded, current", 
    135                      2: "downloaded, needs update", 
    136                      3: "obsolete"} 
    137  
    138     #: A role for the state item data. 
    139     StateRole = OWGUI.OrangeUserRole.next() 
    140  
    141     # QTreeWidgetItem stores the DisplayRole and EditRole as the same role, 
    142     # so we can't use EditRole to store the actual item data, instead we use 
    143     # custom role. 
    144  
    145     #: A custom edit role for the item's data 
    146     EditRole2 = OWGUI.OrangeUserRole.next() 
    147  
    148     def __init__(self, item): 
    149         QTreeWidgetItem.__init__(self, type=QTreeWidgetItem.UserType) 
    150  
    151         self.item = None 
    152         self.setUpdateItem(item) 
    153  
    154     def setUpdateItem(self, item): 
    155         """ 
    156         Set the update item for display. 
    157  
    158         :param UpdateItem item: 
    159             The update item for display. 
    160  
    161         """ 
    162         self.item = item 
    163  
    164         self.setData(0, UpdateTreeWidgetItem.StateRole, item.state) 
    165  
    166         self.setData(1, Qt.DisplayRole, item.title) 
    167         self.setData(1, self.EditRole2, item.title) 
    168  
    169         self.setData(2, Qt.DisplayRole, sizeof_fmt(item.size)) 
    170         self.setData(2, self.EditRole2, item.size) 
    171  
    172         if item.latest is not None: 
    173             self.setData(3, Qt.DisplayRole, item.latest.date().isoformat()) 
    174             self.setData(3, self.EditRole2, item.latest) 
    175         else: 
    176             self.setData(3, Qt.DisplayRole, "N/A") 
    177             self.setData(3, self.EditRole2, datetime.datetime()) 
    178  
    179         self._updateToolTip() 
    180  
    181     def _updateToolTip(self): 
    182         state_str = self.STATE_STRINGS[self.item.state] 
    183         tooltip = ("State: %s\nTags: %s" % 
    184                    (state_str, 
    185                     ", ".join(tag for tag in self.item.tags 
    186                               if not tag.startswith("#")))) 
    187  
    188         if self.item.state in [CURRENT, OUTDATED, DEPRECATED]: 
     112    stateDict = {0: "up-to-date", 
     113                 1: "new version available", 
     114                 2: "not downloaded", 
     115                 3: "obsolete"} 
     116 
     117    def __init__(self, master, treeWidget, domain, filename, infoLocal, 
     118                 infoServer, *args): 
     119        dateServer = dateLocal = None 
     120        if infoServer: 
     121            dateServer = datetime.strptime( 
     122                infoServer["datetime"].split(".")[0], "%Y-%m-%d %H:%M:%S" 
     123            ) 
     124        if infoLocal: 
     125            dateLocal = datetime.strptime( 
     126                infoLocal["datetime"].split(".")[0], "%Y-%m-%d %H:%M:%S" 
     127            ) 
     128        if not infoLocal: 
     129            self.state = 2 
     130        elif not infoServer: 
     131            self.state = 3 
     132        else: 
     133            self.state = 0 if dateLocal >= dateServer else 1 
     134 
     135        title = infoServer["title"] if infoServer else (infoLocal["title"]) 
     136        tags = infoServer["tags"] if infoServer else infoLocal["tags"] 
     137        specialTags = dict([tuple(tag.split(":")) 
     138                            for tag in tags 
     139                            if tag.startswith("#") and ":" in tag]) 
     140        tags = ", ".join(tag for tag in tags if not tag.startswith("#")) 
     141        self.size = infoServer["size"] if infoServer else infoLocal["size"] 
     142 
     143        size = sizeof_fmt(float(self.size)) 
     144        state = self.stateDict[self.state] 
     145        if self.state == 1: 
     146            state += dateServer.strftime(" (%Y, %b, %d)") 
     147 
     148        QTreeWidgetItem.__init__(self, treeWidget, ["", title, size]) 
     149        if dateServer is not None: 
     150            self.setData(3, Qt.DisplayRole, 
     151                         dateServer.date().isoformat()) 
     152 
     153        self.updateWidget = UpdateOptionsWidget( 
     154            self.StartDownload, self.Remove, self.state, treeWidget 
     155        ) 
     156 
     157        self.treeWidget().setItemWidget(self, 0, self.updateWidget) 
     158        self.updateWidget.show() 
     159        self.master = master 
     160        self.title = title 
     161        self.tags = tags.split(", ") 
     162        self.specialTags = specialTags 
     163        self.domain = domain 
     164        self.filename = filename 
     165        self.UpdateToolTip() 
     166 
     167    def UpdateToolTip(self): 
     168        state = {0: "local, updated", 
     169                 1: "local, needs update", 
     170                 2: "on server, download for local use", 
     171                 3: "obsolete"} 
     172        tooltip = "State: %s\nTags: %s" % (state[self.state], 
     173                                           ", ".join(self.tags)) 
     174        if self.state != 2: 
    189175            tooltip += ("\nFile: %s" % 
    190                         serverfiles.localpath(self.item.domain, 
    191                                               self.item.filename)) 
    192         for i in range(1, 4): 
     176                        serverfiles.localpath(self.domain, self.filename)) 
     177        for i in range(1, 5): 
    193178            self.setToolTip(i, tooltip) 
    194179 
     180    def StartDownload(self): 
     181        self.updateWidget.removeButton.setEnabled(False) 
     182        self.updateWidget.updateButton.setEnabled(False) 
     183        self.setData(2, Qt.DisplayRole, QVariant("")) 
     184        serverFiles = serverfiles.ServerFiles( 
     185            access_code=self.master.accessCode if self.master.accessCode 
     186            else None 
     187        ) 
     188 
     189        pb = ItemProgressBar(self.treeWidget()) 
     190        pb.setRange(0, 100) 
     191        pb.setTextVisible(False) 
     192 
     193        self.task = AsyncCall(threadPool=QThreadPool.globalInstance()) 
     194 
     195        if not getattr(self.master, "_sum_progressBar", None): 
     196            self.master._sum_progressBar = OWGUI.ProgressBar(self.master, 0) 
     197            self.master._sum_progressBar.in_progress = 0 
     198        master_pb = self.master._sum_progressBar 
     199        master_pb.iter += 100 
     200        master_pb.in_progress += 1 
     201        self._progressBarRedirect = \ 
     202            ProgressBarRedirect(QThread.currentThread(), master_pb) 
     203        QObject.connect(self.task, 
     204                        SIGNAL("advance()"), 
     205                        pb.advance, 
     206                        Qt.QueuedConnection) 
     207        QObject.connect(self.task, 
     208                        SIGNAL("advance()"), 
     209                        self._progressBarRedirect.advance, 
     210                        Qt.QueuedConnection) 
     211        QObject.connect(self.task, 
     212                        SIGNAL("finished(QString)"), 
     213                        self.EndDownload, 
     214                        Qt.QueuedConnection) 
     215        self.treeWidget().setItemWidget(self, 2, pb) 
     216        pb.show() 
     217 
     218        self.task.apply_async(serverfiles.download, 
     219                              args=(self.domain, self.filename, serverFiles), 
     220                              kwargs=dict(callback=self.task.emitAdvance)) 
     221 
     222    def EndDownload(self, exitCode=0): 
     223        self.treeWidget().removeItemWidget(self, 2) 
     224        if str(exitCode) == "Ok": 
     225            self.state = 0 
     226            self.updateWidget.SetState(self.state) 
     227            self.setData(2, Qt.DisplayRole, 
     228                         QVariant(sizeof_fmt(float(self.size)))) 
     229            self.master.UpdateInfoLabel() 
     230            self.UpdateToolTip() 
     231        else: 
     232            self.updateWidget.SetState(1) 
     233            self.setData(2, Qt.DisplayRole, 
     234                         QVariant("Error occurred while downloading:" + 
     235                                  str(exitCode))) 
     236 
     237        master_pb = self.master._sum_progressBar 
     238 
     239        if master_pb and master_pb.in_progress == 1: 
     240            master_pb.finish() 
     241            self.master._sum_progressBar = None 
     242        elif master_pb: 
     243            master_pb.in_progress -= 1 
     244 
     245    def Remove(self): 
     246        serverfiles.remove(self.domain, self.filename) 
     247        self.state = 2 
     248        self.updateWidget.SetState(self.state) 
     249        self.master.UpdateInfoLabel() 
     250        self.UpdateToolTip() 
     251 
     252    def __contains__(self, item): 
     253        return any(item.lower() in tag.lower() 
     254                   for tag in self.tags + [self.title]) 
     255 
    195256    def __lt__(self, other): 
    196         widget = self.treeWidget() 
    197         column = widget.sortColumn() 
    198         if column == 0: 
    199             role = UpdateTreeWidgetItem.StateRole 
    200         else: 
    201             role = self.EditRole2 
    202  
    203         left = self.data(column, role).toPyObject() 
    204         right = other.data(column, role).toPyObject() 
    205         return left < right 
    206  
    207  
    208 class UpdateOptionsItemDelegate(QStyledItemDelegate): 
    209     """ 
    210     An item delegate for the updates tree widget. 
    211  
    212     .. note: Must be a child of a QTreeWidget. 
    213  
    214     """ 
     257        return getattr(self, "title", "") < getattr(other, "title", "") 
     258 
     259 
     260class UpdateItemDelegate(QItemDelegate): 
    215261    def sizeHint(self, option, index): 
    216         size = QStyledItemDelegate.sizeHint(self, option, index) 
     262        size = QItemDelegate.sizeHint(self, option, index) 
    217263        parent = self.parent() 
    218264        item = parent.itemFromIndex(index) 
     
    221267            size = QSize(size.width(), widget.sizeHint().height() / 2) 
    222268        return size 
    223  
    224  
    225 UpdateItem = namedtuple( 
    226     "UpdateItem", 
    227     ["domain", 
    228      "filename", 
    229      "state",  # Item state flag 
    230      "title",  # Item title (on server is available else local) 
    231      "size",  # Item size in bytes (on server if available else local) 
    232      "latest",  # Latest item date (on server), can be None 
    233      "local",  # Local item date, can be None 
    234      "tags",  # Item tags (on server if available else local) 
    235      "info_local", 
    236      "info_server"] 
    237 ) 
    238  
    239 ItemInfo = namedtuple( 
    240     "ItemInfo", 
    241     ["domain", 
    242      "filename", 
    243      "title", 
    244      "time",  # datetime.datetime 
    245      "size",  # size in bytes 
    246      "tags"] 
    247 ) 
    248  
    249  
    250 def UpdateItem_match(item, string): 
    251     """ 
    252     Return `True` if the `UpdateItem` item contains a string in tags 
    253     or in the title. 
    254  
    255     """ 
    256     string = string.lower() 
    257     return any(string.lower() in tag.lower() 
    258                for tag in item.tags + [item.title]) 
    259  
    260  
    261 def item_state(info_local, info_server): 
    262     """ 
    263     Return the item state (AVAILABLE, ...) based on it's local and server side 
    264     `ItemInfo` instances. 
    265  
    266     """ 
    267     if info_server is None: 
    268         return DEPRECATED 
    269  
    270     if info_local is None: 
    271         return AVAILABLE 
    272  
    273     if info_local.time < info_server.time: 
    274         return OUTDATED 
    275     else: 
    276         return CURRENT 
    277  
    278  
    279 DATE_FMT_1 = "%Y-%m-%d %H:%M:%S.%f" 
    280 DATE_FMT_2 = "%Y-%m-%d %H:%M:%S" 
    281  
    282  
    283 def info_dict_to_item_info(domain, filename, item_dict): 
    284     """ 
    285     Return an `ItemInfo` instance based on `item_dict` as returned by 
    286     ``serverfiles.info(domain, filename)`` 
    287  
    288     """ 
    289     time = item_dict["datetime"] 
    290     try: 
    291         time = datetime.strptime(time, DATE_FMT_1) 
    292     except ValueError: 
    293         time = datetime.strptime(time, DATE_FMT_2) 
    294  
    295     title = item_dict["title"] 
    296     if not title: 
    297         title = filename 
    298  
    299     size = int(item_dict["size"]) 
    300     tags = item_dict["tags"] 
    301     return ItemInfo(domain, filename, title, time, size, tags) 
    302  
    303  
    304 def update_item_from_info(domain, filename, info_server, info_local): 
    305     """ 
    306     Return a `UpdateItem` instance for `domain`, `fileanme` based on 
    307     the local and server side `ItemInfo` instances `info_server` and 
    308     `info_local`. 
    309  
    310     """ 
    311     latest, local, title, tags, size = None, None, None, None, None 
    312  
    313     if info_server is not None: 
    314         info_server = info_dict_to_item_info(domain, filename, info_server) 
    315         latest = info_server.time 
    316         tags = info_server.tags 
    317         title = info_server.title 
    318         size = info_server.size 
    319  
    320     if info_local is not None: 
    321         info_local = info_dict_to_item_info(domain, filename, info_local) 
    322         local = info_local.time 
    323  
    324         if info_server is None: 
    325             tags = info_local.tags 
    326             title = info_local.title 
    327             size = info_local.size 
    328  
    329     state = item_state(info_local, info_server) 
    330  
    331     return UpdateItem(domain, filename, state, title, size, latest, local, 
    332                       tags, info_server, info_local) 
    333  
    334  
    335 def join_info_list(domain, files_local, files_server): 
    336     filenames = set(files_local.keys()).union(files_server.keys()) 
    337     for filename in sorted(filenames): 
    338         info_server = files_server.get(filename, None) 
    339         info_local = files_local.get(filename, None) 
    340         yield update_item_from_info(domain, filename, info_server, info_local) 
    341  
    342  
    343 def join_info_dict(local, server): 
    344     domains = set(local.keys()).union(server.keys()) 
    345     for domain in sorted(domains): 
    346         files_local = local.get(domain, {}) 
    347         files_server = server.get(domain, {}) 
    348  
    349         for item in join_info_list(domain, files_local, files_server): 
    350             yield item 
    351  
    352  
    353 def special_tags(item): 
    354     """ 
    355     Return a dictionary of special tags in an UpdateItem instance (special 
    356     tags are the ones starting with #). 
    357  
    358     """ 
    359     return dict([tuple(tag.split(":")) for tag in item.tags 
    360                  if tag.startswith("#") and ":" in tag]) 
    361269 
    362270 
     
    377285                 searchString="", showAll=True, domains=None, 
    378286                 accessCode=""): 
    379         OWWidget.__init__(self, parent, signalManager, name, wantMainArea=False) 
     287        OWWidget.__init__(self, parent, signalManager, name) 
    380288        self.searchString = searchString 
    381289        self.accessCode = accessCode 
     
    383291        self.domains = domains 
    384292        self.serverFiles = serverfiles.ServerFiles() 
    385  
    386         box = OWGUI.widgetBox(self.controlArea, orientation="horizontal") 
     293        box = OWGUI.widgetBox(self.mainArea, orientation="horizontal") 
    387294 
    388295        self.lineEditFilter = \ 
     
    395302                                 callback=self.SearchUpdate) 
    396303 
    397         box = OWGUI.widgetBox(self.controlArea, "Files") 
     304        box = OWGUI.widgetBox(self.mainArea, "Files") 
    398305        self.filesView = QTreeWidget(self) 
    399306        self.filesView.setHeaderLabels(["Options", "Title", "Size", 
    400307                                        "Last Updated"]) 
    401308        self.filesView.setRootIsDecorated(False) 
    402         self.filesView.setUniformRowHeights(True) 
    403309        self.filesView.setSelectionMode(QAbstractItemView.NoSelection) 
    404310        self.filesView.setSortingEnabled(True) 
    405         self.filesView.sortItems(1, Qt.AscendingOrder) 
    406         self.filesView.setItemDelegateForColumn( 
    407             0, UpdateOptionsItemDelegate(self.filesView)) 
    408  
    409         QObject.connect(self.filesView.model(), 
    410                         SIGNAL("layoutChanged()"), 
    411                         self.SearchUpdate) 
     311        self.filesView.setItemDelegate(UpdateItemDelegate(self.filesView)) 
     312        self.connect(self.filesView.model(), 
     313                     SIGNAL("layoutChanged()"), 
     314                     self.SearchUpdate) 
    412315        box.layout().addWidget(self.filesView) 
    413316 
    414         box = OWGUI.widgetBox(self.controlArea, orientation="horizontal") 
     317        box = OWGUI.widgetBox(self.mainArea, orientation="horizontal") 
    415318        OWGUI.button(box, self, "Update all local files", 
    416319                     callback=self.UpdateAll, 
     
    419322                     callback=self.DownloadFiltered, 
    420323                     tooltip="Download all filtered files shown") 
    421         OWGUI.button(box, self, "Cancel", callback=self.Cancel, 
    422                      tooltip="Cancel scheduled downloads/updates.") 
    423324        OWGUI.rubber(box) 
    424325        OWGUI.lineEdit(box, self, "accessCode", "Access Code", 
     
    428329                                        callback=self.RetrieveFilesList) 
    429330        self.retryButton.hide() 
    430         box = OWGUI.widgetBox(self.controlArea, orientation="horizontal") 
     331        box = OWGUI.widgetBox(self.mainArea, orientation="horizontal") 
    431332        OWGUI.rubber(box) 
    432333        if wantCloseButton: 
     
    438339        self.infoLabel.setAlignment(Qt.AlignCenter) 
    439340 
    440         self.controlArea.layout().addWidget(self.infoLabel) 
     341        self.mainArea.layout().addWidget(self.infoLabel) 
    441342        self.infoLabel.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) 
    442343 
    443344        self.updateItems = [] 
     345        self.allTags = [] 
    444346 
    445347        self.resize(800, 600) 
    446348 
    447         self.progress = ProgressState(self, maximum=3) 
    448         self.progress.valueChanged.connect(self._updateProgress) 
    449         self.progress.rangeChanged.connect(self._updateProgress) 
    450         self.executor = ThreadExecutor( 
    451             threadPool=QThreadPool(maxThreadCount=2) 
    452         ) 
    453  
    454         task = Task(self, function=self.RetrieveFilesList) 
    455         task.exceptionReady.connect(self.HandleError) 
    456         task.start() 
    457  
    458         self._tasks = [] 
     349        QTimer.singleShot(50, self.RetrieveFilesList) 
    459350 
    460351    def RetrieveFilesList(self): 
    461         self.progress.setRange(0, 3) 
    462352        self.serverFiles = serverfiles.ServerFiles(access_code=self.accessCode) 
    463  
    464         task = Task(function=partial(retrieveFilesList, self.serverFiles, 
    465                                      self.domains, 
    466                                      methodinvoke(self.progress, "advance"))) 
    467  
    468         task.resultReady.connect(self.SetFilesList) 
    469         task.exceptionReady.connect(self.HandleError) 
    470  
    471         self.executor.submit(task) 
     353        self.pb = ProgressBar(self, 3) 
     354        self.async_retrieve = createTask(retrieveFilesList, 
     355                                         (self.serverFiles, self.domains, 
     356                                          self.pb.advance), 
     357                                         onResult=self.SetFilesList, 
     358                                         onError=self.HandleError) 
    472359 
    473360        self.setEnabled(False) 
    474361 
    475362    def SetFilesList(self, serverInfo): 
    476         """ 
    477         Set the files to show. 
    478         """ 
    479363        self.setEnabled(True) 
    480  
    481         domains = serverInfo.keys() 
    482         if not domains: 
    483             if self.domains: 
    484                 domains = self.domains 
    485             else: 
    486                 domains = serverfiles.listdomains() 
    487  
    488         localInfo = dict([(dom, serverfiles.allinfo(dom)) for dom in domains]) 
    489  
    490         all_tags = set() 
    491  
    492         self.filesView.clear() 
     364        domains = serverInfo.keys() or serverfiles.listdomains() 
     365        localInfo = dict([(dom, serverfiles.allinfo(dom)) 
     366                          for dom in domains]) 
     367        items = [] 
     368 
     369        self.allTags = set() 
     370        allTitles = set() 
    493371        self.updateItems = [] 
    494372 
    495         for item in join_info_dict(localInfo, serverInfo): 
    496             tree_item = UpdateTreeWidgetItem(item) 
    497             options_widget = UpdateOptionsWidget(item.state) 
    498             options_widget.item = item 
    499  
    500             # Connect the actions to the appropriate methods 
    501             options_widget.installClicked.connect( 
    502                 partial(self.SubmitDownloadTask, item.domain, item.filename) 
    503             ) 
    504             options_widget.removeClicked.connect( 
    505                 partial(self.SubmitRemoveTask, item.domain, item.filename) 
    506             ) 
    507  
    508             self.updateItems.append((item, tree_item, options_widget)) 
    509             all_tags.update(item.tags) 
    510  
    511         self.filesView.addTopLevelItems( 
    512             [tree_item for _, tree_item, _ in self.updateItems] 
    513         ) 
    514  
    515         for item, tree_item, options_widget in self.updateItems: 
    516             self.filesView.setItemWidget(tree_item, 0, options_widget) 
    517  
    518         self.progress.advance() 
    519  
     373        for domain in set(domains) - set(["test", "demo"]): 
     374            local = localInfo.get(domain, {}) 
     375            server = serverInfo.get(domain, {}) 
     376            files = sorted(set(server.keys() + local.keys())) 
     377            for filename in files: 
     378                infoServer = server.get(filename, None) 
     379                infoLocal = local.get(filename, None) 
     380 
     381                items.append((self.filesView, domain, filename, infoLocal, 
     382                              infoServer)) 
     383 
     384                displayInfo = infoServer if infoServer else infoLocal 
     385                self.allTags.update(displayInfo["tags"]) 
     386                allTitles.update(displayInfo["title"].split()) 
     387 
     388        for item in items: 
     389            self.updateItems.append(UpdateTreeWidgetItem(self, *item)) 
     390        self.pb.advance() 
    520391        for column in range(4): 
    521392            whint = self.filesView.sizeHintForColumn(column) 
     
    523394            self.filesView.setColumnWidth(column, width) 
    524395 
    525         self.lineEditFilter.setItems([hint for hint in sorted(all_tags) 
     396        self.lineEditFilter.setItems([hint for hint in sorted(self.allTags) 
    526397                                      if not hint.startswith("#")]) 
    527398        self.SearchUpdate() 
    528399        self.UpdateInfoLabel() 
    529  
    530         self.progress.setRange(0, 0) 
    531  
    532     def HandleError(self, exception): 
    533         if isinstance(exception, IOError): 
     400        self.pb.finish() 
     401 
     402    def HandleError(self, (exc_type, exc_value, tb)): 
     403        if exc_type >= IOError: 
    534404            self.error(0, 
    535405                       "Could not connect to server! Press the Retry " 
     
    537407            self.SetFilesList({}) 
    538408        else: 
    539             sys.excepthook(type(exception), exception.args, None) 
    540             self.progress.setRange(0, 0) 
     409            sys.excepthook(exc_type, exc_value, tb) 
     410            self.pb.finish() 
    541411            self.setEnabled(True) 
    542412 
    543413    def UpdateInfoLabel(self): 
    544         local = [item for item, _, _ in self.updateItems 
    545                  if item.state != AVAILABLE] 
    546         onServer = [item for item, _, _ in self.updateItems] 
    547  
    548         size = sum(float(special_tags(item).get("#uncompressed", item.size)) 
     414        local = [item for item in self.updateItems if item.state != 2] 
     415        onServer = [item for item in self.updateItems] 
     416        size = sum(float(item.specialTags.get("#uncompressed", item.size)) 
    549417                   for item in local) 
    550  
    551         sizeOnServer = sum(float(item.size) for item, _, _ in self.updateItems) 
     418        sizeOnServer = sum(float(item.size) for item in self.updateItems) 
    552419 
    553420        if self.showAll: 
     
    564431 
    565432    def UpdateAll(self): 
    566         for item, _, _ in self.updateItems: 
    567             if item.state == OUTDATED: 
    568                 self.SubmitDownloadTask(item.domain, item.filename) 
     433        for item in self.updateItems: 
     434            if item.state == 1: 
     435                item.StartDownload() 
    569436 
    570437    def DownloadFiltered(self): 
    571         # TODO: submit items in the order shown. 
    572         for item, tree_item, _ in self.updateItems: 
    573             if not tree_item.isHidden() and item.state in \ 
    574                     [AVAILABLE, OUTDATED]: 
    575                 self.SubmitDownloadTask(item.domain, item.filename) 
     438        for item in self.updateItems: 
     439            if not item.isHidden() and item.state != 0: 
     440                item.StartDownload() 
    576441 
    577442    def SearchUpdate(self, searchString=None): 
    578443        strings = unicode(self.lineEditFilter.text()).split() 
    579         for item, tree_item, _ in self.updateItems: 
    580             hide = not all(UpdateItem_match(item, string) 
    581                            for string in strings) 
    582             tree_item.setHidden(hide) 
    583  
    584     def SubmitDownloadTask(self, domain, filename): 
    585         """ 
    586         Submit the (domain, filename) to be downloaded/updated. 
    587         """ 
    588         index = self.updateItemIndex(domain, filename) 
    589         _, tree_item, opt_widget = self.updateItems[index] 
    590  
    591         if self.accessCode: 
    592             sf = serverfiles.ServerFiles(access_code=self.accessCode) 
    593         else: 
    594             sf = serverfiles.ServerFiles() 
    595  
    596         task = DownloadTask(domain, filename, sf) 
    597  
    598         self.executor.submit(task) 
    599  
    600         self.progress.adjustRange(0, 100) 
    601  
    602         pb = ItemProgressBar(self.filesView) 
    603         pb.setRange(0, 100) 
    604         pb.setTextVisible(False) 
    605  
    606         task.advanced.connect(pb.advance) 
    607         task.advanced.connect(self.progress.advance) 
    608         task.finished.connect(pb.hide) 
    609         task.finished.connect(self.onDownloadFinished, Qt.QueuedConnection) 
    610         task.exception.connect(self.onDownloadError, Qt.QueuedConnection) 
    611  
    612         self.filesView.setItemWidget(tree_item, 2, pb) 
    613  
    614         # Clear the text so it does not show behind the progress bar. 
    615         tree_item.setData(2, Qt.DisplayRole, "") 
    616         pb.show() 
    617  
    618         # Disable the options widget 
    619         opt_widget.setEnabled(False) 
    620         self._tasks.append(task) 
    621  
    622     def EndDownloadTask(self, task): 
    623         future = task.future() 
    624         index = self.updateItemIndex(task.domain, task.filename) 
    625         item, tree_item, opt_widget = self.updateItems[index] 
    626  
    627         self.filesView.removeItemWidget(tree_item, 2) 
    628         opt_widget.setEnabled(True) 
    629  
    630         if future.cancelled(): 
    631             # Restore the previous state 
    632             tree_item.setUpdateItem(item) 
    633             opt_widget.setState(item.state) 
    634  
    635         elif future.exception(): 
    636             tree_item.setUpdateItem(item) 
    637             opt_widget.setState(item.state) 
    638  
    639             # Show the exception string in the size column. 
    640             tree_item.setData(2, Qt.DisplayRole, 
    641                          QVariant("Error occurred while downloading:" + 
    642                                   str(future.exception()))) 
    643  
    644         else: 
    645             # get the new updated info dict and replace the the old item 
    646             info = serverfiles.info(item.domain, item.filename) 
    647             new_item = update_item_from_info(item.domain, item.filename, 
    648                                              info, info) 
    649  
    650             self.updateItems[index] = (new_item, tree_item, opt_widget) 
    651  
    652             tree_item.setUpdateItem(new_item) 
    653             opt_widget.setState(new_item.state) 
    654  
    655             self.UpdateInfoLabel() 
    656  
    657     def SubmitRemoveTask(self, domain, filename): 
    658         serverfiles.remove(domain, filename) 
    659         index = self.updateItemIndex(domain, filename) 
    660         item, tree_item, opt_widget = self.updateItems[index] 
    661  
    662         if item.info_server: 
    663             new_item = item._replace(state=AVAILABLE, local=None, 
    664                                       info_local=None) 
    665         else: 
    666             new_item = item._replace(local=None, info_local=None) 
    667             # Disable the options widget. No more actions can be performed 
    668             # for the item. 
    669             opt_widget.setEnabled(False) 
    670  
    671         tree_item.setUpdateItem(new_item) 
    672         opt_widget.setState(new_item.state) 
    673         self.updateItems[index] = (new_item, tree_item, opt_widget) 
    674  
    675         self.UpdateInfoLabel() 
    676  
    677     def Cancel(self): 
    678         """ 
    679         Cancel all pending update/download tasks (that have not yet started). 
    680         """ 
    681         for task in self._tasks: 
    682             task.future().cancel() 
    683  
    684     def onDeleteWidget(self): 
    685         self.Cancel() 
    686         self.executor.shutdown(wait=False) 
    687         OWBaseWidget.onDeleteWidget(self) 
    688  
    689     def onDownloadFinished(self): 
    690         assert QThread.currentThread() is self.thread() 
    691         for task in list(self._tasks): 
    692             future = task.future() 
    693             if future.done(): 
    694                 self.EndDownloadTask(task) 
    695                 self._tasks.remove(task) 
    696  
    697         if not self._tasks: 
    698             # Clear/reset the overall progress 
    699             self.progress.setRange(0, 0) 
    700  
    701     def onDownloadError(self, exc_info): 
    702         sys.excepthook(*exc_info) 
    703  
    704     def updateItemIndex(self, domain, filename): 
    705         for i, (item, _, _) in enumerate(self.updateItems): 
    706             if item.domain == domain and item.filename == filename: 
    707                 return i 
    708         raise ValueError("%r, %r not in update list" % (domain, filename)) 
    709  
    710     def _updateProgress(self, *args): 
    711         rmin, rmax = self.progress.range() 
    712         if rmin != rmax: 
    713             if self.progressBarValue <= 0: 
    714                 self.progressBarInit() 
    715  
    716             self.progressBarSet(self.progress.ratioCompleted() * 100, 
    717                                 processEventsFlags=None) 
    718         if rmin == rmax: 
    719             self.progressBarFinished() 
    720  
    721  
    722 class ProgressState(QObject): 
    723     valueChanged = Signal(int) 
    724     rangeChanged = Signal(int, int) 
    725     textChanged = Signal(str) 
    726     started = Signal() 
    727     finished = Signal() 
    728  
    729     def __init__(self, parent=None, minimum=0, maximum=0, text="", value=0): 
    730         QObject.__init__(self, parent) 
    731  
    732         self._minimum = minimum 
    733         self._maximum = max(maximum, minimum) 
    734         self._text = text 
    735         self._value = value 
    736  
    737     @Slot(int, int) 
    738     def setRange(self, minimum, maximum): 
    739         maximum = max(maximum, minimum) 
    740  
    741         if self._minimum != minimum or self._maximum != maximum: 
    742             self._minimum = minimum 
    743             self._maximum = maximum 
    744             self.rangeChanged.emit(minimum, maximum) 
    745  
    746             # Adjust the value to fit in the range 
    747             newvalue = min(max(self._value, minimum), maximum) 
    748             if newvalue != self._value: 
    749                 self.setValue(newvalue) 
    750  
    751     def range(self): 
    752         return self._minimum, self._maximum 
    753  
    754     @Slot(int) 
    755     def setValue(self, value): 
    756         if self._value != value and value >= self._minimum and \ 
    757                 value <= self._maximum: 
    758             self._value = value 
    759             self.valueChanged.emit(value) 
    760  
    761     def value(self): 
    762         return self._value 
    763  
    764     @Slot(str) 
    765     def setText(self, text): 
    766         if self._text != text: 
    767             self._text = text 
    768             self.textChanged.emit(text) 
    769  
    770     def text(self): 
    771         return self._text 
    772  
    773     @Slot() 
    774     @Slot(int) 
    775     def advance(self, value=1): 
    776         self.setValue(self._value + value) 
    777  
    778     def adjustRange(self, dmin, dmax): 
    779         self.setRange(self._minimum + dmin, self._maximum + dmax) 
    780  
    781     def ratioCompleted(self): 
    782         span = self._maximum - self._minimum 
    783         if span < 1e-3: 
    784             return 0.0 
    785  
    786         return min(max(float(self._value - self._minimum) / span, 0.0), 1.0) 
    787  
    788  
    789 class DownloadTask(Task): 
    790     advanced = Signal() 
    791     exception = Signal(tuple) 
    792  
    793     def __init__(self, domain, filename, serverfiles, parent=None): 
    794         Task.__init__(self, parent) 
    795         self.filename = filename 
    796         self.domain = domain 
    797         self.serverfiles = serverfiles 
    798         self._interrupt = False 
    799  
    800     def interrupt(self): 
    801         """ 
    802         Interrupt the download. 
    803         """ 
    804         self._interrupt = True 
    805  
    806     def _advance(self): 
    807         self.advanced.emit() 
    808         if self._interrupt: 
    809             raise KeyboardInterrupt 
    810  
    811     def run(self): 
    812         try: 
    813             serverfiles.download(self.domain, self.filename, self.serverfiles, 
    814                                  callback=self._advance) 
    815         except Exception: 
    816             self.exception.emit(sys.exc_info()) 
     444        tags = set() 
     445        for item in self.updateItems: 
     446            hide = not all(str(string) in item for string in strings) 
     447            item.setHidden(hide) 
     448            if not hide: 
     449                tags.update(item.tags) 
    817450 
    818451 
  • Orange/OrangeWidgets/__init__.py

    r11474 r11264  
    99    from . import ( 
    1010        Associate, Classify, Data, Evaluate, Regression, 
    11         Unsupervised, Visualize, Prototypes, VisualizeQt 
     11        Unsupervised, Visualize, Prototypes 
    1212    ) 
    1313    dist = pkg_resources.get_distribution("Orange") 
    1414    for pkg in [Data, Visualize, Classify, Regression, Evaluate, Unsupervised, 
    15                 Associate, Prototypes, VisualizeQt ]: 
     15                Associate, Prototypes]: 
    1616        discovery.process_category_package(pkg, distribution=dist) 
    1717 
  • docs/reference/rst/Orange.data.discretization.rst

    r11477 r10393  
    7171.. autoclass:: DiscretizeTable(features=None, discretize_class=False, method=EqualFreq(n=3), clean=True) 
    7272 
    73 .. A chapter on feature subset selection in Orange 
     73.. A chapter on `feature subset selection <../ofb/o_fss.htm>`_ in Orange 
    7474   for Beginners tutorial shows the use of DiscretizedLearner. Other 
    7575   discretization classes from core Orange are listed in chapter on 
    76    categorization of the same tutorial. -> should put in classification/wrappers 
     76   `categorization <../ofb/o_categorization.htm>`_ of the same tutorial. -> should put in classification/wrappers 
    7777 
    7878.. [FayyadIrani1993] UM Fayyad and KB Irani. Multi-interval discretization of continuous valued 
  • docs/widgets/rst/visualize/polyviz.rst

    r11477 r11359  
    5252See the documentation on :ref:`Radviz` for details on various aspects 
    5353controlled by the :obj:`Settings` tab. The utility of VizRank, an intelligent 
    54 visualization technique, using brown-selected.tab data set is 
     54visualization technique, using `brown-selected.tab 
     55<http://orange.biolab.si/doc/datasets/brown-selected.tab>`_ data set is 
    5556illustrated with a snapshot below. 
    5657 
  • docs/widgets/rst/visualize/scatterplot.rst

    r11477 r11422  
    7373optimization is to find those scatterplot projections, where instances with 
    7474different class labels are well separated. For example, for a data set  
    75 brown-selected.tab 
     75`brown-selected.tab <http://orange.biolab.si/doc/datasets/brown-selected.tab>`_ 
    7676(comes with Orange installation) the two attributes that best separate 
    7777instances of different class are displayed in the snapshot below, where we have 
     
    148148outliers. The idea is that the outliers are those data instances, which are 
    149149incorrectly classified in many of the top visualizations. For example, the 
    150 class of the 33-rd instance in brown-selected.tab should be Resp, 
     150class of the 33-rd instance in `brown-selected.tab 
     151<http://orange.biolab.si/doc/datasets/brown-selected.tab>`_ should be Resp, 
    151152but this instance is quite often misclassified as Ribo. The snapshot below 
    152153shows one particular visualization displaying why such misclassification 
Note: See TracChangeset for help on using the changeset viewer.