source: orange/Orange/OrangeCanvas/scheme/widgetsscheme.py @ 11722:bb3212606f80

Revision 11722:bb3212606f80, 25.4 KB checked in by Ales Erjavec <ales.erjavec@…>, 6 months ago (diff)

Fixed errors in Scheme/SignalManager shutdown/delete process.

(fixes #1331)

RevLine 
[11269]1"""
2Widgets Scheme
3==============
4
5A Scheme for Orange Widgets Scheme (.ows).
6
7This is a subclass of the general :class:`Scheme`. It is responsible for
8the construction and management of OWBaseWidget instances corresponding
9to the scheme nodes, as well as delegating the signal propagation to a
10companion :class:`WidgetsSignalManager` class.
11
12.. autoclass:: WidgetsScheme
13   :bases:
14
15.. autoclass:: WidgetsSignalManager
16  :bases:
17
18"""
[11563]19import sys
[11135]20import logging
[11101]21
[11269]22import sip
[11639]23from PyQt4.QtGui import (
24    QShortcut, QKeySequence, QWhatsThisClickedEvent, QWidget
25)
26
27from PyQt4.QtCore import Qt, QObject, QCoreApplication, QEvent, SIGNAL
28from PyQt4.QtCore import pyqtSignal as Signal
[11101]29
[11269]30from .signalmanager import SignalManager, compress_signals, can_enable_dynamic
31from .scheme import Scheme, SchemeNode
[11614]32from .node import UserMessage
[11639]33from ..utils import name_lookup
[11297]34from ..resources import icon_loader
[11101]35
36log = logging.getLogger(__name__)
37
38
39class WidgetsScheme(Scheme):
[11269]40    """
41    A Scheme containing Orange Widgets managed with a `WidgetsSignalManager`
[11101]42    instance.
[11297]43
44    Extends the base `Scheme` class to handle the lifetime
45    (creation/deletion, etc.) of `OWBaseWidget` instances corresponding to
46    the nodes in the scheme. It also delegates the interwidget signal
47    propagation to an instance of `WidgetsSignalManager`.
[11101]48
49    """
50    def __init__(self, parent=None, title=None, description=None):
51        Scheme.__init__(self, parent, title, description)
52
[11269]53        self.signal_manager = WidgetsSignalManager(self)
[11722]54        self.widget_manager = WidgetManager()
[11639]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        """
76        changed = False
77        for node in self.nodes:
78            widget = self.widget_for_node(node)
[11659]79            settings = widget.getSettings(alsoContexts=True)
[11639]80            if settings != node.properties:
81                node.properties = settings
82                changed = True
83        log.debug("Scheme node properties sync (changed: %s)", changed)
84        return changed
85
86
87class WidgetManager(QObject):
88    """
89    OWWidget instance manager class.
90
91    This class handles the lifetime of OWWidget instances in a
92    :class:`WidgetsScheme`.
93
94    """
95    #: A new OWWidget was created and added by the manager.
96    widget_for_node_added = Signal(SchemeNode, QWidget)
97
98    #: An OWWidget was removed, hidden and will be deleted when appropriate.
99    widget_for_node_removed = Signal(SchemeNode, QWidget)
100
101    #: Widget processing state flags:
102    #:   * InputUpdate - signal manager is updating/setting the
103    #:     widget's inputs
104    #:   * BlockingUpdate - widget has entered a blocking state
[11640]105    #:   * ProcessingUpdate - widget has entered processing state
106    InputUpdate, BlockingUpdate, ProcessingUpdate = 1, 2, 4
[11639]107
[11722]108    def __init__(self, parent=None):
[11639]109        QObject.__init__(self, parent)
110        self.__scheme = None
111        self.__signal_manager = None
112        self.__widgets = []
113        self.__widget_for_node = {}
114        self.__node_for_widget = {}
115
116        # Widgets that were 'removed' from the scheme but were at
117        # the time in an input update loop and could not be deleted
118        # immediately
119        self.__delay_delete = set()
120
121        # processing state flags for all nodes (including the ones
122        # in __delay_delete).
123        self.__widget_processing_state = {}
124
125        # Tracks the widget in the update loop by the SignalManager
126        self.__updating_widget = None
127
128    def set_scheme(self, scheme):
129        """
130        Set the :class:`WidgetsScheme` instance to manage.
131        """
132        self.__scheme = scheme
133        self.__signal_manager = scheme.findChild(SignalManager)
134
135        self.__signal_manager.processingStarted[SchemeNode].connect(
[11411]136            self.__on_processing_started
137        )
[11639]138        self.__signal_manager.processingFinished[SchemeNode].connect(
[11411]139            self.__on_processing_finished
140        )
[11639]141        scheme.node_added.connect(self.add_widget_for_node)
142        scheme.node_removed.connect(self.remove_widget_for_node)
143        scheme.installEventFilter(self)
[11101]144
[11639]145    def scheme(self):
[11269]146        """
[11639]147        Return the scheme instance on which this manager is installed.
148        """
149        return self.__scheme
[11269]150
[11639]151    def signal_manager(self):
[11269]152        """
[11639]153        Return the signal manager in use on the :func:`scheme`.
154        """
155        return self.__signal_manager
[11269]156
[11639]157    def widget_for_node(self, node):
158        """
159        Return the OWWidget instance for the scheme node.
160        """
161        return self.__widget_for_node[node]
162
163    def node_for_widget(self, widget):
164        """
165        Return the SchemeNode instance for the OWWidget.
166
167        Raise a KeyError if the widget does not map to a node in the scheme.
168        """
169        return self.__node_for_widget[widget]
170
171    def add_widget_for_node(self, node):
172        """
173        Create a new OWWidget instance for the corresponding scheme node.
174        """
[11101]175        widget = self.create_widget_instance(node)
[11134]176
[11639]177        self.__widgets.append(widget)
178        self.__widget_for_node[node] = widget
179        self.__node_for_widget[widget] = node
[11101]180
[11639]181        self.widget_for_node_added.emit(node, widget)
[11269]182
[11639]183    def remove_widget_for_node(self, node):
184        """
185        Remove the OWWidget instance for node.
186        """
187        widget = self.widget_for_node(node)
[11269]188
[11639]189        self.__widgets.remove(widget)
190        del self.__widget_for_node[node]
191        del self.__node_for_widget[widget]
[11101]192
[11639]193        self.widget_for_node_removed.emit(node, widget)
194
195        self._delete_widget(widget)
196
197    def _delete_widget(self, widget):
198        """
199        Delete the OWBaseWidget instance.
200        """
[11540]201        widget.close()
202
[11101]203        # Save settings to user global settings.
[11486]204        if not widget._settingsFromSchema:
205            widget.saveSettings()
[11101]206
207        # Notify the widget it will be deleted.
208        widget.onDeleteWidget()
209
[11639]210        if self.__widget_processing_state[widget] != 0:
211            # If the widget is in an update loop and/or blocking we
212            # delay the scheduled deletion until the widget is done.
213            self.__delay_delete.add(widget)
214        else:
215            widget.deleteLater()
[11101]216
217    def create_widget_instance(self, node):
[11269]218        """
[11639]219        Create a OWWidget instance for the node.
[11269]220        """
[11101]221        desc = node.description
222        klass = name_lookup(desc.qualified_name)
223
224        log.info("Creating %r instance.", klass)
225        widget = klass.__new__(
226            klass,
[11639]227            _owInfo=True,
228            _owWarning=True,
229            _owError=True,
230            _owShowStatus=True,
231            _useContexts=True,
[11101]232            _category=desc.category,
233            _settingsFromSchema=node.properties
234        )
235
[11639]236        # Init the node/widget mapping and state before calling __init__
[11269]237        # Some OWWidgets might already send data in the constructor
[11639]238        # (should this be forbidden? Raise a warning?) triggering the signal
239        # manager which would request the widget => node mapping or state
240        self.__widget_for_node[node] = widget
241        self.__node_for_widget[widget] = node
242        self.__widget_processing_state[widget] = 0
[11297]243
[11639]244        widget.__init__(None, self.signal_manager())
[11101]245        widget.setCaption(node.title)
246        widget.widgetInfo = desc
247
[11297]248        widget.setWindowIcon(
249            icon_loader.from_description(desc).get(desc.icon)
250        )
251
[11101]252        widget.setVisible(node.properties.get("visible", False))
253
254        node.title_changed.connect(widget.setCaption)
[11297]255
[11640]256        # Widget's info/warning/error messages.
257        widget.widgetStateChanged.connect(self.__on_widget_state_changed)
258
259        # Widget's progress bar value state.
[11101]260        widget.progressBarValueChanged.connect(node.set_progress)
[11640]261
262        # Widget processing state (progressBarInit/Finished)
263        # and the blocking state.
264        widget.processingStateChanged.connect(
265            self.__on_processing_state_changed
266        )
[11297]267        self.connect(widget,
268                     SIGNAL("blockingStateChanged(bool)"),
[11639]269                     self.__on_blocking_state_changed)
[11101]270
[11296]271        # Install a help shortcut on the widget
272        help_shortcut = QShortcut(QKeySequence("F1"), widget)
273        help_shortcut.activated.connect(self.__on_help_request)
[11639]274
[11101]275        return widget
276
[11639]277    def node_processing_state(self, node):
[11222]278        """
[11639]279        Return the processing state flags for the node.
280
281        Same as `manager.node_processing_state(manger.widget_for_node(node))`
282
283        """
284        widget = self.widget_for_node(node)
285        return self.__widget_processing_state[widget]
286
287    def widget_processing_state(self, widget):
288        """
289        Return the processing state flags for the widget.
290
291        The state is an bitwise or of `InputUpdate` and `BlockingUpdate`.
292
293        """
294        return self.__widget_processing_state[widget]
295
296    def eventFilter(self, receiver, event):
[11722]297        if event.type() == QEvent.Close and receiver is self.__scheme:
[11639]298            self.signal_manager().stop()
299
300            # Notify the widget instances.
301            for widget in self.__widget_for_node.values():
302                widget.close()
303
304                if not widget._settingsFromSchema:
305                    # First save global settings if necessary.
306                    widget.saveSettings()
307
308                widget.onDeleteWidget()
309
310            event.accept()
311            return True
312
313        return QObject.eventFilter(self, receiver, event)
314
315    def __on_help_request(self):
316        """
317        Help shortcut was pressed. We send a `QWhatsThisClickedEvent` to
318        the scheme and hope someone responds to it.
319
320        """
321        # Sender is the QShortcut, and parent the OWBaseWidget
322        widget = self.sender().parent()
323        try:
324            node = self.node_for_widget(widget)
325        except KeyError:
326            pass
327        else:
328            url = "help://search?id={0}".format(node.description.id)
329            event = QWhatsThisClickedEvent(url)
330            QCoreApplication.sendEvent(self.scheme(), event)
[11222]331
[11614]332    def __on_widget_state_changed(self, message_type, message_id,
333                                  message_value):
334        """
335        The OWBaseWidget info/warning/error state has changed.
336
337        message_type is one of "Info", "Warning" or "Error" string depending
338        of which method (information, warning, error) was called. message_id
339        is the first int argument if supplied, and message_value the message
340        text.
341
342        """
343        widget = self.sender()
[11639]344        try:
345            node = self.node_for_widget(widget)
346        except KeyError:
347            pass
348        else:
[11614]349            message_type = str(message_type)
350            if message_type == "Info":
351                contents = widget.widgetStateToHtml(True, False, False)
352                level = UserMessage.Info
353            elif message_type == "Warning":
354                contents = widget.widgetStateToHtml(False, True, False)
355                level = UserMessage.Warning
356            elif message_type == "Error":
357                contents = widget.widgetStateToHtml(False, False, True)
358                level = UserMessage.Error
359            else:
360                raise ValueError("Invalid message_type: %r" % message_type)
361
362            if not contents:
363                contents = None
364
365            message = UserMessage(contents, severity=level,
366                                  message_id=message_type,
[11639]367                                  data={"content-type": "text/html"})
[11614]368            node.set_state_message(message)
369
[11640]370    def __on_processing_state_changed(self, state):
371        """
372        A widget processing state has changed (progressBarInit/Finished)
373        """
374        widget = self.sender()
375        try:
376            node = self.node_for_widget(widget)
377        except KeyError:
378            return
379
380        if state:
381            self.__widget_processing_state[widget] |= self.ProcessingUpdate
382        else:
383            self.__widget_processing_state[widget] &= ~self.ProcessingUpdate
384        self.__update_node_processing_state(node)
385
[11639]386    def __on_processing_started(self, node):
[11101]387        """
[11639]388        Signal manager entered the input update loop for the node.
[11487]389        """
[11639]390        widget = self.widget_for_node(node)
391        # Remember the widget instance. The node and the node->widget mapping
392        # can be removed between this and __on_processing_finished.
393        self.__updating_widget = widget
394        self.__widget_processing_state[widget] |= self.InputUpdate
395        self.__update_node_processing_state(node)
[11411]396
397    def __on_processing_finished(self, node):
[11639]398        """
399        Signal manager exited the input update loop for the node.
400        """
401        widget = self.__updating_widget
402        self.__widget_processing_state[widget] &= ~self.InputUpdate
403
404        if widget in self.__node_for_widget:
405            self.__update_node_processing_state(node)
406        elif widget in self.__delay_delete:
407            self.__try_delete(widget)
408        else:
409            raise ValueError("%r is not managed" % widget)
410
411        self.__updating_widget = None
412
413    def __on_blocking_state_changed(self, state):
414        """
415        OWWidget blocking state has changed.
416        """
417        if not state:
418            # schedule an update pass.
419            self.signal_manager()._update()
420
421        widget = self.sender()
422        if state:
423            self.__widget_processing_state[widget] |= self.BlockingUpdate
424        else:
425            self.__widget_processing_state[widget] &= ~self.BlockingUpdate
426
427        if widget in self.__node_for_widget:
428            node = self.node_for_widget(widget)
429            self.__update_node_processing_state(node)
430
431        elif widget in self.__delay_delete:
432            self.__try_delete(widget)
433
434    def __update_node_processing_state(self, node):
435        """
436        Update the `node.processing_state` to reflect the widget state.
437        """
438        state = self.node_processing_state(node)
439        node.set_processing_state(1 if state else 0)
440
441    def __try_delete(self, widget):
442        if self.__widget_processing_state[widget] == 0:
443            self.__delay_delete.remove(widget)
444            widget.deleteLater()
445            del self.__widget_processing_state[widget]
[11411]446
[11269]447
448class WidgetsSignalManager(SignalManager):
[11639]449    """
450    A signal manager for a WidgetsScheme.
451    """
[11269]452    def __init__(self, scheme):
453        SignalManager.__init__(self, scheme)
454
[11465]455        scheme.installEventFilter(self)
[11639]456
[11465]457        self.freezing = 0
[11269]458
[11465]459        self.__scheme_deleted = False
[11639]460
[11465]461        scheme.destroyed.connect(self.__on_scheme_destroyed)
[11639]462        scheme.node_added.connect(self.on_node_added)
463        scheme.node_removed.connect(self.on_node_removed)
464        scheme.link_added.connect(self.link_added)
465        scheme.link_removed.connect(self.link_removed)
[11269]466
[11639]467    def send(self, widget, channelname, value, signal_id):
[11269]468        """
469        send method compatible with OWBaseWidget.
470        """
471        scheme = self.scheme()
[11639]472        try:
473            node = scheme.node_for_widget(widget)
474        except KeyError:
475            # The Node/Widget was already removed from the scheme.
476            log.debug("Node for %r is not in the scheme.", widget)
[11470]477            return
478
[11269]479        try:
480            channel = node.output_channel(channelname)
481        except ValueError:
482            log.error("%r is not valid signal name for %r",
483                      channelname, node.description.name)
484            return
485
[11639]486        # Expand the signal_id with the unique widget id and the
487        # channel name. This is needed for OWBaseWidget's input
488        # handlers (Multiple flag).
489        signal_id = (widget.widgetId, channelname, signal_id)
490
491        SignalManager.send(self, node, channel, value, signal_id)
[11269]492
493    def is_blocking(self, node):
[11639]494        return self.scheme().widget_manager.node_processing_state(node) != 0
[11269]495
496    def send_to_node(self, node, signals):
497        """
[11639]498        Implementation of `SignalManager.send_to_node`.
499
500        Deliver input signals to an OWBaseWidget instance.
[11269]501
502        """
[11639]503        widget = self.scheme().widget_for_node(node)
[11269]504        self.process_signals_for_widget(node, widget, signals)
505
506    def compress_signals(self, signals):
[11639]507        """
508        Reimplemented from :func:`SignalManager.compress_signals`.
509        """
[11269]510        return compress_signals(signals)
511
512    def process_signals_for_widget(self, node, widget, signals):
513        """
[11639]514        Process new signals for the OWBaseWidget.
[11269]515        """
516        # This replaces the old OWBaseWidget.processSignals method
517
518        if sip.isdeleted(widget):
519            log.critical("Widget %r was deleted. Cannot process signals",
520                         widget)
521            return
522
523        if widget.processingHandler:
524            widget.processingHandler(widget, 1)
525
526        app = QCoreApplication.instance()
527
528        for signal in signals:
529            link = signal.link
530            value = signal.value
531
532            # Check and update the dynamic link state
533            if link.is_dynamic():
534                link.dynamic_enabled = can_enable_dynamic(link, value)
535                if not link.dynamic_enabled:
536                    # Send None instead
537                    value = None
538
539            handler = link.sink_channel.handler
540            if handler.startswith("self."):
541                handler = handler.split(".", 1)[1]
542
543            handler = getattr(widget, handler)
544
545            if link.sink_channel.single:
546                args = (value,)
547            else:
[11639]548                args = (value, signal.id)
[11269]549
550            log.debug("Process signals: calling %s.%s (from %s with id:%s)",
551                      type(widget).__name__, handler.__name__, link, signal.id)
552
553            app.setOverrideCursor(Qt.WaitCursor)
554            try:
555                handler(*args)
556            except Exception:
[11563]557                sys.excepthook(*sys.exc_info())
558                log.exception("Error calling '%s' of '%s'",
559                              handler.__name__, node.title)
[11269]560            finally:
561                app.restoreOverrideCursor()
562
563        app.setOverrideCursor(Qt.WaitCursor)
564        try:
565            widget.handleNewSignals()
566        except Exception:
[11563]567            sys.excepthook(*sys.exc_info())
568            log.exception("Error calling 'handleNewSignals()' of '%s'",
569                          node.title)
[11269]570        finally:
571            app.restoreOverrideCursor()
572
573        if widget.processingHandler:
[11465]574            widget.processingHandler(widget, 0)
[11269]575
576    def scheduleSignalProcessing(self, widget=None):
577        """
578        Back compatibility with old orngSignalManager.
579        """
580        self._update()
581
582    def processNewSignals(self, widget=None):
583        """
584        Back compatibility with old orngSignalManager.
585
586        .. todo:: The old signal manager would update immediately, but
587                  this only schedules the update. Is this a problem?
588
589        """
590        self._update()
591
592    def addEvent(self, strValue, object=None, eventVerbosity=1):
593        """
594        Back compatibility with old orngSignalManager module's logging.
595        """
596        if not isinstance(strValue, basestring):
597            info = str(strValue)
598        else:
599            info = strValue
600
601        if object is not None:
602            info += ". Token type = %s. Value = %s" % \
603                    (str(type(object)), str(object)[:100])
604
605        if eventVerbosity > 0:
606            log.debug(info)
607        else:
608            log.info(info)
609
610    def getLinks(self, widgetFrom=None, widgetTo=None,
611                 signalNameFrom=None, signalNameTo=None):
612        """
613        Back compatibility with old orngSignalManager. Some widget look if
614        they have any output connections, so this is still needed, but should
615        be deprecated in the future.
616
617        """
618        scheme = self.scheme()
619
620        source_node = sink_node = None
621
622        if widgetFrom is not None:
623            source_node = scheme.node_for_widget[widgetFrom]
624        if widgetTo is not None:
625            sink_node = scheme.node_for_widget[widgetTo]
626
627        candidates = scheme.find_links(source_node=source_node,
628                                       sink_node=sink_node)
629
630        def signallink(link):
631            """
632            Construct SignalLink from an SchemeLink.
633            """
[11639]634            w1 = scheme.widget_for_node(link.source_node)
635            w2 = scheme.widget_for_node(link.sink_node)
[11269]636
[11297]637            # Input/OutputSignal are reused from description. Interface
638            # is almost the same as it was in orngSignalManager
[11269]639            return SignalLink(w1, link.source_channel,
640                              w2, link.sink_channel,
641                              link.enabled)
642
643        links = []
644        for link in candidates:
645            if (signalNameFrom is None or \
646                    link.source_channel.name == signalNameFrom) and \
647                    (signalNameTo is None or \
648                     link.sink_channel.name == signalNameTo):
649
650                links.append(signallink(link))
651        return links
652
653    def setFreeze(self, freeze, startWidget=None):
654        """
655        Freeze/unfreeze signal processing. If freeze >= 1 no signal will be
656        processed until freeze is set back to 0.
657
658        """
659        self.freezing = max(freeze, 0)
660        if freeze > 0:
661            log.debug("Freezing signal processing (value:%r set by %r)",
662                      freeze, startWidget)
663        elif freeze == 0:
664            log.debug("Unfreezing signal processing (cleared by %r)",
665                      startWidget)
666
667        if self._input_queue:
668            self._update()
669
670    def freeze(self, widget=None):
671        """
672        Return a context manager that freezes the signal processing.
673        """
674        manager = self
675
676        class freezer(object):
677            def __enter__(self):
678                self.push()
679                return self
680
681            def __exit__(self, *args):
682                self.pop()
683
684            def push(self):
685                manager.setFreeze(manager.freezing + 1, widget)
686
687            def pop(self):
688                manager.setFreeze(manager.freezing - 1, widget)
689
690        return freezer()
691
692    def event(self, event):
693        if event.type() == QEvent.UpdateRequest:
694            if self.freezing > 0:
695                log.debug("received UpdateRequest while signal processing "
696                          "is frozen")
697                event.setAccepted(False)
698                return False
699
[11465]700            if self.__scheme_deleted:
701                log.debug("Scheme has been/is being deleted. No more "
702                          "signals will be delivered to any nodes.")
703                event.setAccepted(True)
704                return True
705        # Retain a reference to the scheme until the 'process_queued' finishes
706        # in SignalManager.event.
707        scheme = self.scheme()
[11269]708        return SignalManager.event(self, event)
709
[11465]710    def eventFilter(self, receiver, event):
[11722]711        if event.type() == QEvent.DeferredDelete and receiver is self.scheme():
712            try:
713                state = self.runtime_state()
714            except AttributeError:
715                # If the scheme (which is a parent of this object) is
716                # already being deleted the SignalManager can also be in
717                # the process of destruction (noticeable by its __dict__
718                # being empty). There is nothing really to do in this
719                # case.
720                state = None
721
722            if state == SignalManager.Processing:
[11470]723                log.info("Deferring a 'DeferredDelete' event for the Scheme "
724                         "instance until SignalManager exits the current "
725                         "update loop.")
[11465]726                event.setAccepted(False)
727                self.processingFinished.connect(self.scheme().deleteLater)
728                self.__scheme_deleted = True
729                return True
730
731        return SignalManager.eventFilter(self, receiver, event)
732
733    def __on_scheme_destroyed(self, obj):
734        self.__scheme_deleted = True
735
[11269]736
737class SignalLink(object):
738    """
[11465]739    Back compatibility with old orngSignalManager, do not use.
[11269]740    """
741    def __init__(self, widgetFrom, outputSignal, widgetTo, inputSignal,
742                 enabled=True, dynamic=False):
743        self.widgetFrom = widgetFrom
744        self.widgetTo = widgetTo
745
746        self.outputSignal = outputSignal
747        self.inputSignal = inputSignal
748
749        self.dynamic = dynamic
750
751        self.enabled = enabled
752
753        self.signalNameFrom = self.outputSignal.name
754        self.signalNameTo = self.inputSignal.name
755
756    def canEnableDynamic(self, obj):
757        """
758        Can dynamic signal link be enabled for `obj`?
759        """
760        return isinstance(obj, name_lookup(self.inputSignal.type))
761
762
763class SignalWrapper(object):
764    """
765    Signal (actually slot) wrapper used by OWBaseWidget.connect overload.
766    This disables (freezes) the widget's signal manager when slots are
[11297]767    invoked from GUI signals. Not sure if this is still needed, could instead
768    just set the blocking flag on the widget itself.
[11269]769
770    """
771    def __init__(self, widget, method):
772        self.widget = widget
773        self.method = method
774
[11297]775    def __call__(self, *args):
[11269]776        manager = self.widget.signalManager
[11297]777        if manager:
778            with manager.freeze(self.method):
779                self.method(*args)
780        else:
781            # Might be running stand alone without a manager.
782            self.method(*args)
Note: See TracBrowser for help on using the repository browser.