Ignore:
Location:
Orange/OrangeCanvas
Files:
3 edited

Legend:

Unmodified
Added
Removed
  • Orange/OrangeCanvas/document/schemeedit.py

    r11639 r11616  
    12361236    def __onNodeActivate(self, item): 
    12371237        node = self.__scene.node_for_item(item) 
    1238         widget = self.scheme().widget_for_node(node) 
     1238        widget = self.scheme().widget_for_node[node] 
    12391239        widget.show() 
    12401240        widget.raise_() 
  • Orange/OrangeCanvas/scheme/signalmanager.py

    r11639 r11467  
    169169 
    170170    def on_node_removed(self, node): 
    171         # remove all pending input signals for node so we don't get 
    172         # stale references in process_node. 
    173         # NOTE: This does not remove output signals for this node. In 
    174         # particular the final 'None' will be delivered to the sink 
    175         # nodes even after the source node is no longer in the scheme. 
     171        # remove all pending input signals for node so we don't get stale 
     172        # references in process_node 
     173        # NOTE: This does not remove output signals this node. In particular 
     174        # the final 'None' values might be left on the queue. 
    176175        log.info("Node %r removed. Removing pending signals.", 
    177176                 node.title) 
  • Orange/OrangeCanvas/scheme/widgetsscheme.py

    r11641 r11623  
    2121 
    2222import sip 
    23 from PyQt4.QtGui import ( 
    24     QShortcut, QKeySequence, QWhatsThisClickedEvent, QWidget 
    25 ) 
    26  
    27 from PyQt4.QtCore import Qt, QObject, QCoreApplication, QEvent, SIGNAL 
    28 from PyQt4.QtCore import pyqtSignal as Signal 
     23from PyQt4.QtGui import QShortcut, QKeySequence, QWhatsThisClickedEvent 
     24from PyQt4.QtCore import Qt, QCoreApplication, QEvent, SIGNAL 
    2925 
    3026from .signalmanager import SignalManager, compress_signals, can_enable_dynamic 
    3127from .scheme import Scheme, SchemeNode 
    3228from .node import UserMessage 
    33 from ..utils import name_lookup 
     29from ..utils import name_lookup, check_arg, check_type 
    3430from ..resources import icon_loader 
     31from ..config import rc 
    3532 
    3633log = logging.getLogger(__name__) 
     
    5148        Scheme.__init__(self, parent, title, description) 
    5249 
     50        self.widgets = [] 
     51        self.widget_for_node = {} 
     52        self.node_for_widget = {} 
    5353        self.signal_manager = WidgetsSignalManager(self) 
    54         self.widget_manager = WidgetManager(self) 
    55         self.widget_manager.set_scheme(self) 
    56  
    57     def widget_for_node(self, node): 
    58         """ 
    59         Return the OWWidget instance for a `node` 
    60         """ 
    61         return self.widget_manager.widget_for_node(node) 
    62  
    63     def node_for_widget(self, widget): 
    64         """ 
    65         Return the SchemeNode instance for the `widget`. 
    66         """ 
    67         return self.widget_manager.node_for_widget(widget) 
    68  
    69     def sync_node_properties(self): 
    70         """ 
    71         Sync the widget settings/properties with the SchemeNode.properties. 
    72         Return True if there were any changes in the properties (i.e. if the 
    73         new node.properties differ from the old value) and False otherwise. 
    74  
    75         .. note:: this should hopefully be removed in the feature, when the 
    76             widget can notify a changed setting property. 
    77  
    78         """ 
    79         changed = False 
    80         for node in self.nodes: 
    81             widget = self.widget_for_node(node) 
    82             settings = widget.getSettings(alsoContexts=False) 
    83             if settings != node.properties: 
    84                 node.properties = settings 
    85                 changed = True 
    86         log.debug("Scheme node properties sync (changed: %s)", changed) 
    87         return changed 
    88  
    89     def save_to(self, stream, pretty=True, pickle_fallback=False): 
    90         """ 
    91         Reimplemented from :func:`Scheme.save_to`. 
    92         """ 
    93         self.sync_node_properties() 
    94         Scheme.save_to(self, stream, pretty, pickle_fallback) 
    95  
    96  
    97 class WidgetManager(QObject): 
    98     """ 
    99     OWWidget instance manager class. 
    100  
    101     This class handles the lifetime of OWWidget instances in a 
    102     :class:`WidgetsScheme`. 
    103  
    104     """ 
    105     #: A new OWWidget was created and added by the manager. 
    106     widget_for_node_added = Signal(SchemeNode, QWidget) 
    107  
    108     #: An OWWidget was removed, hidden and will be deleted when appropriate. 
    109     widget_for_node_removed = Signal(SchemeNode, QWidget) 
    110  
    111     #: Widget processing state flags: 
    112     #:   * InputUpdate - signal manager is updating/setting the 
    113     #:     widget's inputs 
    114     #:   * BlockingUpdate - widget has entered a blocking state 
    115     #:   * ProcessingUpdate - widget has entered processing state 
    116     InputUpdate, BlockingUpdate, ProcessingUpdate = 1, 2, 4 
    117  
    118     def __init__(self, parent): 
    119         QObject.__init__(self, parent) 
    120         self.__scheme = None 
    121         self.__signal_manager = None 
    122         self.__widgets = [] 
    123         self.__widget_for_node = {} 
    124         self.__node_for_widget = {} 
    125  
    126         # Widgets that were 'removed' from the scheme but were at 
    127         # the time in an input update loop and could not be deleted 
    128         # immediately 
    129         self.__delay_delete = set() 
    130  
    131         # processing state flags for all nodes (including the ones 
    132         # in __delay_delete). 
    133         self.__widget_processing_state = {} 
    134  
    135         # Tracks the widget in the update loop by the SignalManager 
    136         self.__updating_widget = None 
    137  
    138     def set_scheme(self, scheme): 
    139         """ 
    140         Set the :class:`WidgetsScheme` instance to manage. 
    141         """ 
    142         self.__scheme = scheme 
    143         self.__signal_manager = scheme.findChild(SignalManager) 
    144  
    145         self.__signal_manager.processingStarted[SchemeNode].connect( 
     54        self.signal_manager.processingStarted[SchemeNode].connect( 
    14655            self.__on_processing_started 
    14756        ) 
    148         self.__signal_manager.processingFinished[SchemeNode].connect( 
     57        self.signal_manager.processingFinished[SchemeNode].connect( 
    14958            self.__on_processing_finished 
    15059        ) 
    151         scheme.node_added.connect(self.add_widget_for_node) 
    152         scheme.node_removed.connect(self.remove_widget_for_node) 
    153         scheme.installEventFilter(self) 
    154  
    155     def scheme(self): 
    156         """ 
    157         Return the scheme instance on which this manager is installed. 
    158         """ 
    159         return self.__scheme 
    160  
    161     def signal_manager(self): 
    162         """ 
    163         Return the signal manager in use on the :func:`scheme`. 
    164         """ 
    165         return self.__signal_manager 
    166  
    167     def widget_for_node(self, node): 
    168         """ 
    169         Return the OWWidget instance for the scheme node. 
    170         """ 
    171         return self.__widget_for_node[node] 
    172  
    173     def node_for_widget(self, widget): 
    174         """ 
    175         Return the SchemeNode instance for the OWWidget. 
    176  
    177         Raise a KeyError if the widget does not map to a node in the scheme. 
    178         """ 
    179         return self.__node_for_widget[widget] 
    180  
    181     def add_widget_for_node(self, node): 
    182         """ 
    183         Create a new OWWidget instance for the corresponding scheme node. 
    184         """ 
     60 
     61    def add_node(self, node): 
     62        """ 
     63        Add a `SchemeNode` instance to the scheme and create/initialize the 
     64        OWBaseWidget instance for it. 
     65 
     66        """ 
     67        check_arg(node not in self.nodes, "Node already in scheme.") 
     68        check_type(node, SchemeNode) 
     69 
     70        # Create the widget before a call to Scheme.add_node in 
     71        # case someone connected to node_added already expects 
     72        # widget_for_node, etc. to be up to date. 
    18573        widget = self.create_widget_instance(node) 
    186  
    187         self.__widgets.append(widget) 
    188         self.__widget_for_node[node] = widget 
    189         self.__node_for_widget[widget] = node 
    190  
    191         self.widget_for_node_added.emit(node, widget) 
    192  
    193     def remove_widget_for_node(self, node): 
    194         """ 
    195         Remove the OWWidget instance for node. 
    196         """ 
    197         widget = self.widget_for_node(node) 
    198  
    199         self.__widgets.remove(widget) 
    200         del self.__widget_for_node[node] 
    201         del self.__node_for_widget[widget] 
    202  
    203         self.widget_for_node_removed.emit(node, widget) 
    204  
    205         self._delete_widget(widget) 
    206  
    207     def _delete_widget(self, widget): 
    208         """ 
    209         Delete the OWBaseWidget instance. 
    210         """ 
     74        Scheme.add_node(self, node) 
     75 
     76        self.widgets.append(widget) 
     77 
     78    def remove_node(self, node): 
     79        Scheme.remove_node(self, node) 
     80        widget = self.widget_for_node[node] 
     81 
     82        self.signal_manager.on_node_removed(node) 
     83 
     84        del self.widget_for_node[node] 
     85        del self.node_for_widget[widget] 
     86 
    21187        widget.close() 
    21288 
     
    21793        # Notify the widget it will be deleted. 
    21894        widget.onDeleteWidget() 
    219  
    220         if self.__widget_processing_state[widget] != 0: 
    221             # If the widget is in an update loop and/or blocking we 
    222             # delay the scheduled deletion until the widget is done. 
    223             self.__delay_delete.add(widget) 
    224         else: 
    225             widget.deleteLater() 
     95        # And schedule it for deletion. 
     96        widget.deleteLater() 
     97 
     98    def add_link(self, link): 
     99        Scheme.add_link(self, link) 
     100        self.signal_manager.link_added(link) 
     101 
     102    def remove_link(self, link): 
     103        Scheme.remove_link(self, link) 
     104        self.signal_manager.link_removed(link) 
    226105 
    227106    def create_widget_instance(self, node): 
    228107        """ 
    229         Create a OWWidget instance for the node. 
     108        Create a OWBaseWidget instance for the node. 
    230109        """ 
    231110        desc = node.description 
     
    235114        widget = klass.__new__( 
    236115            klass, 
    237             _owInfo=True, 
    238             _owWarning=True, 
    239             _owError=True, 
    240             _owShowStatus=True, 
    241             _useContexts=True, 
     116            _owInfo=rc.get("canvas.show-state-info", True), 
     117            _owWarning=rc.get("canvas.show-state-warning", True), 
     118            _owError=rc.get("canvas.show-state-error", True), 
     119            _owShowStatus=rc.get("OWWidget.show-status", True), 
     120            _useContexts=rc.get("OWWidget.use-contexts", True), 
    242121            _category=desc.category, 
    243122            _settingsFromSchema=node.properties 
    244123        ) 
    245124 
    246         # Init the node/widget mapping and state before calling __init__ 
     125        # Add the node/widget mapping s before calling __init__ 
    247126        # Some OWWidgets might already send data in the constructor 
    248         # (should this be forbidden? Raise a warning?) triggering the signal 
    249         # manager which would request the widget => node mapping or state 
    250         self.__widget_for_node[node] = widget 
    251         self.__node_for_widget[widget] = node 
    252         self.__widget_processing_state[widget] = 0 
    253  
    254         widget.__init__(None, self.signal_manager()) 
     127        # (should this be forbidden? Raise a warning?) 
     128        self.signal_manager.on_node_added(node) 
     129 
     130        self.widget_for_node[node] = widget 
     131        self.node_for_widget[widget] = node 
     132 
     133        widget.__init__(None, self.signal_manager) 
    255134        widget.setCaption(node.title) 
    256135        widget.widgetInfo = desc 
     
    264143        node.title_changed.connect(widget.setCaption) 
    265144 
    266         # Widget's info/warning/error messages. 
     145        # Bind widgets progress/processing state back to the node's properties 
     146        widget.progressBarValueChanged.connect(node.set_progress) 
     147        widget.processingStateChanged.connect(node.set_processing_state) 
    267148        widget.widgetStateChanged.connect(self.__on_widget_state_changed) 
    268  
    269         # Widget's progress bar value state. 
    270         widget.progressBarValueChanged.connect(node.set_progress) 
    271  
    272         # Widget processing state (progressBarInit/Finished) 
    273         # and the blocking state. 
    274         widget.processingStateChanged.connect( 
    275             self.__on_processing_state_changed 
    276         ) 
    277149        self.connect(widget, 
    278150                     SIGNAL("blockingStateChanged(bool)"), 
    279                      self.__on_blocking_state_changed) 
     151                     self.signal_manager._update) 
    280152 
    281153        # Install a help shortcut on the widget 
    282154        help_shortcut = QShortcut(QKeySequence("F1"), widget) 
    283155        help_shortcut.activated.connect(self.__on_help_request) 
    284  
    285156        return widget 
    286157 
    287     def node_processing_state(self, node): 
    288         """ 
    289         Return the processing state flags for the node. 
    290  
    291         Same as `manager.node_processing_state(manger.widget_for_node(node))` 
    292  
    293         """ 
    294         widget = self.widget_for_node(node) 
    295         return self.__widget_processing_state[widget] 
    296  
    297     def widget_processing_state(self, widget): 
    298         """ 
    299         Return the processing state flags for the widget. 
    300  
    301         The state is an bitwise or of `InputUpdate` and `BlockingUpdate`. 
    302  
    303         """ 
    304         return self.__widget_processing_state[widget] 
    305  
    306     def eventFilter(self, receiver, event): 
    307         if receiver is self.__scheme and event.type() == QEvent.Close: 
    308             self.signal_manager().stop() 
    309  
    310             # Notify the widget instances. 
    311             for widget in self.__widget_for_node.values(): 
    312                 widget.close() 
    313  
    314                 if not widget._settingsFromSchema: 
    315                     # First save global settings if necessary. 
    316                     widget.saveSettings() 
    317  
    318                 widget.onDeleteWidget() 
    319  
    320             event.accept() 
    321             return True 
    322  
    323         return QObject.eventFilter(self, receiver, event) 
    324  
    325     def __on_help_request(self): 
    326         """ 
    327         Help shortcut was pressed. We send a `QWhatsThisClickedEvent` to 
    328         the scheme and hope someone responds to it. 
    329  
    330         """ 
    331         # Sender is the QShortcut, and parent the OWBaseWidget 
    332         widget = self.sender().parent() 
    333         try: 
    334             node = self.node_for_widget(widget) 
    335         except KeyError: 
    336             pass 
    337         else: 
    338             url = "help://search?id={0}".format(node.description.id) 
    339             event = QWhatsThisClickedEvent(url) 
    340             QCoreApplication.sendEvent(self.scheme(), event) 
     158    def widget_settings(self): 
     159        """Return a list of dictionaries with widget settings. 
     160        """ 
     161        return [self.widget_for_node[node].getSettings(alsoContexts=False) 
     162                for node in self.nodes] 
    341163 
    342164    def __on_widget_state_changed(self, message_type, message_id, 
     
    352174        """ 
    353175        widget = self.sender() 
    354         try: 
    355             node = self.node_for_widget(widget) 
    356         except KeyError: 
    357             pass 
    358         else: 
     176        node = self.node_for_widget.get(widget) 
     177        if node is not None: 
    359178            message_type = str(message_type) 
    360179            if message_type == "Info": 
     
    375194            message = UserMessage(contents, severity=level, 
    376195                                  message_id=message_type, 
    377                                   data={"content-type": "text/html"}) 
     196                                  data={"contents-type": "text/html"}) 
    378197            node.set_state_message(message) 
    379198 
    380     def __on_processing_state_changed(self, state): 
    381         """ 
    382         A widget processing state has changed (progressBarInit/Finished) 
    383         """ 
    384         widget = self.sender() 
    385         try: 
    386             node = self.node_for_widget(widget) 
    387         except KeyError: 
    388             return 
    389  
    390         if state: 
    391             self.__widget_processing_state[widget] |= self.ProcessingUpdate 
     199    def sync_node_properties(self): 
     200        """Sync the widget settings/properties with the SchemeNode.properties. 
     201        Return True if there were any changes in the properties (i.e. if the 
     202        new node.properties differ from the old value) and False otherwise. 
     203 
     204        .. note:: this should hopefully be removed in the feature, when the 
     205            widget can notify a changed setting property. 
     206 
     207        """ 
     208        changed = False 
     209        for node in self.nodes: 
     210            widget = self.widget_for_node[node] 
     211            settings = widget.getSettings(alsoContexts=False) 
     212            if settings != node.properties: 
     213                node.properties = settings 
     214                changed = True 
     215        log.debug("Scheme node properties sync (changed: %s)", changed) 
     216        return changed 
     217 
     218    def save_to(self, stream, pretty=True, pickle_fallback=False): 
     219        self.sync_node_properties() 
     220        Scheme.save_to(self, stream, pretty, pickle_fallback) 
     221 
     222    def event(self, event): 
     223        """ 
     224        Reimplemented from `QObject.event`. 
     225 
     226        Responds to QEvent.Close event by stopping signal processing and 
     227        closing all widgets. 
     228 
     229        """ 
     230        if event.type() == QEvent.Close: 
     231            self.signal_manager.stop() 
     232 
     233            # Notify the widget instances. 
     234            for widget in self.widget_for_node.values(): 
     235                widget.close() 
     236 
     237                if not widget._settingsFromSchema: 
     238                    # First save global settings if necessary. 
     239                    widget.saveSettings() 
     240 
     241                widget.onDeleteWidget() 
     242 
     243            event.accept() 
     244            return True 
    392245        else: 
    393             self.__widget_processing_state[widget] &= ~self.ProcessingUpdate 
    394         self.__update_node_processing_state(node) 
     246            return Scheme.event(self, event) 
     247 
     248    def __on_help_request(self): 
     249        """ 
     250        Help shortcut was pressed. We send a `QWhatsThisClickedEvent` and 
     251        hope someone responds to it. 
     252 
     253        """ 
     254        # Sender is the QShortcut, and parent the OWBaseWidget 
     255        widget = self.sender().parent() 
     256        node = self.node_for_widget.get(widget) 
     257        if node: 
     258            url = "help://search?id={0}".format(node.description.id) 
     259            event = QWhatsThisClickedEvent(url) 
     260            QCoreApplication.sendEvent(self, event) 
    395261 
    396262    def __on_processing_started(self, node): 
    397         """ 
    398         Signal manager entered the input update loop for the node. 
    399         """ 
    400         widget = self.widget_for_node(node) 
    401         # Remember the widget instance. The node and the node->widget mapping 
    402         # can be removed between this and __on_processing_finished. 
    403         self.__updating_widget = widget 
    404         self.__widget_processing_state[widget] |= self.InputUpdate 
    405         self.__update_node_processing_state(node) 
     263        node.set_processing_state(1) 
    406264 
    407265    def __on_processing_finished(self, node): 
    408         """ 
    409         Signal manager exited the input update loop for the node. 
    410         """ 
    411         widget = self.__updating_widget 
    412         self.__widget_processing_state[widget] &= ~self.InputUpdate 
    413  
    414         if widget in self.__node_for_widget: 
    415             self.__update_node_processing_state(node) 
    416         elif widget in self.__delay_delete: 
    417             self.__try_delete(widget) 
    418         else: 
    419             raise ValueError("%r is not managed" % widget) 
    420  
    421         self.__updating_widget = None 
    422  
    423     def __on_blocking_state_changed(self, state): 
    424         """ 
    425         OWWidget blocking state has changed. 
    426         """ 
    427         if not state: 
    428             # schedule an update pass. 
    429             self.signal_manager()._update() 
    430  
    431         widget = self.sender() 
    432         if state: 
    433             self.__widget_processing_state[widget] |= self.BlockingUpdate 
    434         else: 
    435             self.__widget_processing_state[widget] &= ~self.BlockingUpdate 
    436  
    437         if widget in self.__node_for_widget: 
    438             node = self.node_for_widget(widget) 
    439             self.__update_node_processing_state(node) 
    440  
    441         elif widget in self.__delay_delete: 
    442             self.__try_delete(widget) 
    443  
    444     def __update_node_processing_state(self, node): 
    445         """ 
    446         Update the `node.processing_state` to reflect the widget state. 
    447         """ 
    448         state = self.node_processing_state(node) 
    449         node.set_processing_state(1 if state else 0) 
    450  
    451     def __try_delete(self, widget): 
    452         if self.__widget_processing_state[widget] == 0: 
    453             self.__delay_delete.remove(widget) 
    454             widget.deleteLater() 
    455             del self.__widget_processing_state[widget] 
     266        node.set_processing_state(0) 
    456267 
    457268 
    458269class WidgetsSignalManager(SignalManager): 
    459     """ 
    460     A signal manager for a WidgetsScheme. 
    461     """ 
    462270    def __init__(self, scheme): 
    463271        SignalManager.__init__(self, scheme) 
    464272 
    465273        scheme.installEventFilter(self) 
    466  
     274        # We keep a mapping from node->widget after the node/widget has been 
     275        # removed from the scheme until we also process all the outgoing signal 
     276        # updates. The reason is the old OWBaseWidget's MULTI channel protocol 
     277        # where the actual source widget instance is passed to the signal 
     278        # handler, and in the delayed update the mapping in `scheme()` is no 
     279        # longer available. 
     280        self._widget_backup = {} 
     281        self._widgets_to_delete = set() 
     282        self._active_node = None 
    467283        self.freezing = 0 
    468284 
    469285        self.__scheme_deleted = False 
    470  
    471286        scheme.destroyed.connect(self.__on_scheme_destroyed) 
    472         scheme.node_added.connect(self.on_node_added) 
    473         scheme.node_removed.connect(self.on_node_removed) 
    474         scheme.link_added.connect(self.link_added) 
    475         scheme.link_removed.connect(self.link_removed) 
    476  
    477     def send(self, widget, channelname, value, signal_id): 
     287 
     288    def on_node_removed(self, node): 
     289        widget = self.scheme().widget_for_node[node] 
     290 
     291        assert not self.scheme().find_links(sink_node=node), \ 
     292            "Node removed but still has input links" 
     293 
     294        signals = self.compress_signals(self.pending_input_signals(node)) 
     295        if not all(signal.value is None for signal in signals): 
     296            log.error("Non 'None' signals pending for a removed node %r", 
     297                         node.title) 
     298 
     299        SignalManager.on_node_removed(self, node) 
     300 
     301        if self.runtime_state() == SignalManager.Processing and \ 
     302                node is self._active_node or self.is_blocking(node): 
     303            # Delay the widget delete until it finishes. 
     304            # Keep a reference to the widget and install a filter. 
     305            self._widgets_to_delete.add(widget) 
     306            widget.installEventFilter(self) 
     307 
     308        # Store the node->widget mapping for possible delayed signal id. 
     309        # It will be removed in `process_queued` when all signals 
     310        # originating from this widget are delivered. 
     311        self._widget_backup[node] = widget 
     312 
     313    def send(self, widget, channelname, value, id): 
    478314        """ 
    479315        send method compatible with OWBaseWidget. 
    480316        """ 
    481317        scheme = self.scheme() 
    482         try: 
    483             node = scheme.node_for_widget(widget) 
    484         except KeyError: 
    485             # The Node/Widget was already removed from the scheme. 
    486             log.debug("Node for %r is not in the scheme.", widget) 
     318 
     319        if widget not in scheme.node_for_widget: 
     320            # The Node/Widget was already removed from the scheme 
    487321            return 
     322 
     323        node = scheme.node_for_widget[widget] 
    488324 
    489325        try: 
     
    494330            return 
    495331 
    496         # Expand the signal_id with the unique widget id and the 
    497         # channel name. This is needed for OWBaseWidget's input 
    498         # handlers (Multiple flag). 
    499         signal_id = (widget.widgetId, channelname, signal_id) 
    500  
    501         SignalManager.send(self, node, channel, value, signal_id) 
     332        SignalManager.send(self, node, channel, value, id) 
    502333 
    503334    def is_blocking(self, node): 
    504         return self.scheme().widget_manager.node_processing_state(node) != 0 
     335        return self.scheme().widget_for_node[node].isBlocking() 
    505336 
    506337    def send_to_node(self, node, signals): 
    507338        """ 
    508         Implementation of `SignalManager.send_to_node`. 
    509  
    510         Deliver input signals to an OWBaseWidget instance. 
    511  
    512         """ 
    513         widget = self.scheme().widget_for_node(node) 
     339        Implementation of `SignalManager.send_to_node`. Deliver data signals 
     340        to OWBaseWidget instance. 
     341 
     342        """ 
     343        if node in self.scheme().widget_for_node: 
     344            widget = self.scheme().widget_for_node[node] 
     345        else: 
     346            widget = self._widget_backup[node] 
     347 
     348        self._active_node = node 
    514349        self.process_signals_for_widget(node, widget, signals) 
     350        self._active_node = None 
     351 
     352        if widget in self._widgets_to_delete: 
     353            # If this node/widget was removed during the 
     354            # 'process_signals_for_widget' 
     355            self._widgets_to_delete.remove(widget) 
     356            widget.deleteLater() 
    515357 
    516358    def compress_signals(self, signals): 
    517         """ 
    518         Reimplemented from :func:`SignalManager.compress_signals`. 
    519         """ 
    520359        return compress_signals(signals) 
    521360 
     361    def process_queued(self, max_nodes=None): 
     362        SignalManager.process_queued(self, max_nodes=max_nodes) 
     363 
     364        # Remove node->widgets backup mapping no longer needed. 
     365        nodes_removed = set(self._widget_backup.keys()) 
     366        sources_remaining = set(signal.link.source_node for 
     367                                signal in self._input_queue) 
     368 
     369        nodes_to_remove = nodes_removed - sources_remaining 
     370        for node in nodes_to_remove: 
     371            del self._widget_backup[node] 
     372 
    522373    def process_signals_for_widget(self, node, widget, signals): 
    523374        """ 
    524         Process new signals for the OWBaseWidget. 
     375        Process new signals for a OWBaseWidget. 
    525376        """ 
    526377        # This replaces the old OWBaseWidget.processSignals method 
     
    534385            widget.processingHandler(widget, 1) 
    535386 
     387        scheme = self.scheme() 
    536388        app = QCoreApplication.instance() 
    537389 
     
    556408                args = (value,) 
    557409            else: 
    558                 args = (value, signal.id) 
     410                source_node = link.source_node 
     411                source_name = link.source_channel.name 
     412 
     413                if source_node in scheme.widget_for_node: 
     414                    source_widget = scheme.widget_for_node[source_node] 
     415                else: 
     416                    # Node is no longer in the scheme. 
     417                    source_widget = self._widget_backup[source_node] 
     418 
     419                # The old OWBaseWidget.processSignals sends the source widget 
     420                # instance along. 
     421                # TODO: Does any widget actually use it, or could it be 
     422                # removed (replaced with a unique id)? 
     423                args = (value, (source_widget, source_name, signal.id)) 
    559424 
    560425            log.debug("Process signals: calling %s.%s (from %s with id:%s)", 
     
    581446            app.restoreOverrideCursor() 
    582447 
     448        # TODO: Test if async processing works, then remove this 
     449        while widget.isBlocking(): 
     450            self.thread().msleep(50) 
     451            app.processEvents() 
     452 
    583453        if widget.processingHandler: 
    584454            widget.processingHandler(widget, 0) 
     
    642512            Construct SignalLink from an SchemeLink. 
    643513            """ 
    644             w1 = scheme.widget_for_node(link.source_node) 
    645             w2 = scheme.widget_for_node(link.sink_node) 
     514            w1 = scheme.widget_for_node[link.source_node] 
     515            w2 = scheme.widget_for_node[link.sink_node] 
    646516 
    647517            # Input/OutputSignal are reused from description. Interface 
     
    728598                self.__scheme_deleted = True 
    729599                return True 
     600        elif receiver in self._widgets_to_delete and \ 
     601                event.type() == QEvent.DeferredDelete: 
     602            if self._widget_backup.get(self._active_node, None) is receiver: 
     603                # The widget is still being updated. We need to keep it alive, 
     604                # it will be deleted in `send_to_node`. 
     605                log.info("Deferring a 'DeferredDelete' until widget exits " 
     606                         "the 'process_signals_for_widget'.") 
     607                event.setAccepted(False) 
     608                return True 
    730609 
    731610        return SignalManager.eventFilter(self, receiver, event) 
Note: See TracChangeset for help on using the changeset viewer.