source: orange/Orange/OrangeCanvas/orngSignalManager.py @ 11461:a7acfd5945f6

Revision 11461:a7acfd5945f6, 30.2 KB checked in by Ales Erjavec <ales.erjavec@…>, 12 months ago (diff)

Fixed handling of blocking widget states in orngSignalManager.

RevLine 
[8042]1# Author: Gregor Leban (gregor.leban@fri.uni-lj.si)
2# Description:
3#    manager, that handles correct processing of widget signals
4#
5
6import sys, os, time
[10821]7import copy
[8042]8import logging
9import logging.handlers
10
[10821]11import __builtin__
12
[11461]13from collections import deque
14
[8042]15import orange
[10651]16from Orange.utils import debugging
[8042]17
18Single = 2
19Multiple = 4
20
21Default = 8
22NonDefault = 16
23
[10996]24Explicit = 32  # Explicit - only connected if specifically requested or the only possibility
[9548]25
[10996]26Dynamic = 64  # Dynamic output signal
[8042]27
28
29class InputSignal(object):
[10996]30    def __init__(self, name, signalType, handler,
31                 parameters=Single + NonDefault,
32                 oldParam=0):
[8042]33        self.name = name
34        self.type = signalType
35        self.handler = handler
36
[10821]37        if type(parameters) == str:
[8042]38            parameters = eval(parameters)   # parameters are stored as strings
39        # if we have the old definition of parameters then transform them
[10821]40        if parameters in [0, 1]:
[8042]41            self.single = parameters
42            self.default = not oldParam
[10996]43            self.explicit = 0
[8042]44            return
45
[10821]46        if not (parameters & Single or parameters & Multiple):
47            parameters += Single
48        if not (parameters & Default or parameters & NonDefault):
49            parameters += NonDefault
[10996]50
[8042]51        self.single = parameters & Single
52        self.default = parameters & Default
[9548]53        self.explicit = parameters & Explicit
[10996]54
55
[8042]56class OutputSignal(object):
[10996]57    def __init__(self, name, signalType, parameters=Single + NonDefault):
[8042]58        self.name = name
59        self.type = signalType
60
[10996]61        if type(parameters) == str:
62            parameters = eval(parameters)
63
64        if parameters in [0, 1]:  # old definition of parameters
[8042]65            self.default = not parameters
[10996]66            self.single = 1
67            self.explicit = 0
68            self.dynamic = 0
[8042]69            return
70
[10996]71        if not (parameters & Default or parameters & NonDefault):
72            parameters += NonDefault
73
[8042]74        self.single = parameters & Single
75        self.default = parameters & Default
[9548]76        self.explicit = parameters & Explicit
[10996]77
[8042]78        self.dynamic = parameters & Dynamic
79        if self.dynamic and self.single:
80            print "Output signal can not be Multiple and Dynamic"
81            self.dynamic = 0
[10996]82
83
[8042]84def canConnect(output, input, dynamic=False):
85    ret = issubclass(output.type, input.type)
86    if output.dynamic and dynamic:
87        ret = ret or issubclass(input.type,output.type)
88    return ret
89
90
[10821]91def rgetattr(obj, name):
92    while "." in name:
93        first, name = name.split(".", 1)
94        obj = getattr(obj, first)
95    return getattr(obj, name)
96
97
98def resolveSignal(signal, globals={}):
99    """If `signal.type` is a string (used by orngRegistry) return
100    the signal copy with the resolved `type`, else return the signal
101    unchanged.
102    """
103    if isinstance(signal.type, basestring):
104        type_name = signal.type
105        if "." in type_name:
106            module, name = type_name.split(".", 1)
107            if module in globals:
108                #  module and type are imported in the globals
109                module = globals[module]
110                sig_type = rgetattr(module, name)
111            else:
112                module, name = type_name.rsplit(".", 1)
113                module = __import__(module, fromlist=[name], globals=globals)
114                sig_type = getattr(module, name)
115        else:
116            if type_name in globals:
117                sig_type = globals[type_name]
118            elif hasattr(__builtin__, type_name):
119                sig_type = getattr(__builtin__, type_name)
120            else:
121                raise NameError(type_name)
122
123        signal = copy.copy(signal)
124        signal.type = sig_type
125    return signal
126
[8042]127class SignalLink(object):
128    def __init__(self, widgetFrom, outputSignal, widgetTo, inputSignal, enabled=True):
129        self.widgetFrom = widgetFrom
130        self.widgetTo = widgetTo
131       
132        self.outputSignal = outputSignal
133        self.inputSignal = inputSignal
134       
135        if issubclass(outputSignal.type, inputSignal.type):
136            self.dynamic = False
137        else: 
138            self.dynamic = outputSignal.dynamic
139       
140        self.enabled = enabled
141   
142        self.signalNameFrom = self.outputSignal.name
143        self.signalNameTo = self.inputSignal.name
144       
145   
146    def canEnableDynamic(self, obj):
147        """ Can dynamic signal link be enabled for `obj`?
148        """
149        return isinstance(obj, self.inputSignal.type)
150   
151   
152# class that allows to process only one signal at a time
153class SignalWrapper(object):
154    def __init__(self, widget, method):
155        self.widget = widget
156        self.method = method
157
158    def __call__(self, *k):
159        manager = self.widget.signalManager
160        if not manager:
161            manager = signalManager
162       
163        manager.signalProcessingInProgress += 1
164        try:
165            self.method(*k)
166        finally:
167            manager.signalProcessingInProgress -= 1
168            if not manager.signalProcessingInProgress:
169                manager.processNewSignals(self.widget) 
170
171
172class SignalManager(object):
173    widgets = []    # topologically sorted list of widgets
174    links = {}      # dicionary. keys: widgetFrom, values: [SignalLink, ...]
175    freezing = 0            # do we want to process new signal immediately
176    signalProcessingInProgress = 0 # this is set to 1 when manager is propagating new signal values
177
178    def __init__(self, *args):
179        self.debugFile = None
[9873]180        self.verbosity = debugging.orngVerbosity
[8042]181        self.stderr = sys.stderr
182        self._seenExceptions = {}
183        self.widgetQueue = []
184        self.asyncProcessingEnabled = False
185       
[10580]186        from Orange.utils import environ
[8042]187        if not hasattr(self, "log"):
188            SignalManager.log = logging.getLogger("SignalManager")
[9873]189            self.logFileName = os.path.join(environ.canvas_settings_dir,
190                "signalManager.log")
[9212]191            try:
192                self.log.addHandler(logging.handlers.RotatingFileHandler(self.logFileName, maxBytes=2**20, backupCount=2))
193            except:
194                pass
[8042]195            self.log.setLevel(logging.INFO)
196           
197        self.log.info("Signal Manager started")
198       
199        self.stdout = sys.stdout
200       
201        class err(object):
202            def write(myself, str):
203                self.log.error(str[:-1] if str.endswith("\n") else str)
204            def flush(myself):
205                pass
206        self.myerr = err()
207           
[9873]208        if debugging.orngDebuggingEnabled:
209            self.debugHandler = logging.FileHandler(debugging.orngDebuggingFileName, mode="wb")
[8042]210            self.log.addHandler(self.debugHandler)
[9873]211            self.log.setLevel(logging.DEBUG if debugging.orngVerbosity > 0
212            else logging.INFO)
[8042]213            sys.excepthook = self.exceptionHandler
214            sys.stderr = self.myerr
215
216    def setDebugMode(self, debugMode = 0, debugFileName = "signalManagerOutput.txt", verbosity = 1):
217        self.verbosity = verbosity
218
219        if debugMode:
220            handler = logging.FileHandler(debugFileName, "wb")
221            self.log.addHandler(handler)
222           
223            sys.excepthook = self.exceptionHandler
224                   
225            sys.stderr = self.myerr
226
227    # ----------------------------------------------------------
228    # ----------------------------------------------------------
229    # DEBUGGING FUNCTION
230
231    def closeDebugFile(self):
232        sys.stderr = self.stderr
233
234    def addEvent(self, strValue, object = None, eventVerbosity = 1):
235        info = str(strValue)
236        if isinstance(object, orange.ExampleTable):
237            name = " " + getattr(object, "name", "")
238            info += ". Token type = ExampleTable" + name + ". len = " + str(len(object))
239        elif type(object) == list:
240            info += ". Token type = %s. Value = %s" % (str(type(object)), str(object[:10]))
241        elif object != None:
242            info += ". Token type = %s. Value = %s" % (str(type(object)), str(object)[:100])
243        if eventVerbosity > 0:
244            self.log.debug(info)
245        else:
246            self.log.info(info)
247
248
249    def exceptionSeen(self, type, value, tracebackInfo):
250        import traceback, os
251        shortEStr = "".join(traceback.format_exception(type, value, tracebackInfo))[-2:]
252        return self._seenExceptions.has_key(shortEStr)
253
254    def exceptionHandler(self, type, value, tracebackInfo):
255        import traceback, os, StringIO
256
257        # every exception show only once
258        shortEStr = "".join(traceback.format_exception(type, value, tracebackInfo))[-2:]
259        if self._seenExceptions.has_key(shortEStr):
260            return
261        self._seenExceptions[shortEStr] = 1
262       
263        list = traceback.extract_tb(tracebackInfo, 10)
264        space = "\t"
265        totalSpace = space
266        message = StringIO.StringIO()
267        message.write("Unhandled exception of type %s\n" % ( str(type)))
268        message.write("Traceback:\n")
269
270        for i, (file, line, funct, code) in enumerate(list):
271            if not code:
272                continue
273            message.write(totalSpace + "File: " + os.path.split(file)[1] + " in line %4d\n" %(line))
274            message.write(totalSpace + "Function name: %s\n" % (funct))
275            message.write(totalSpace + "Code: " + code + "\n")
276            totalSpace += space
277
278        message.write(totalSpace[:-1] + "Exception type: " + str(type) + "\n")
279        message.write(totalSpace[:-1] + "Exception value: " + str(value)+ "\n")
280        self.log.error(message.getvalue())
281#        message.flush()
282
283    # ----------------------------------------------------------
284    # ----------------------------------------------------------
285
286    # freeze/unfreeze signal processing. If freeze=1 no signal will be processed until freeze is set back to 0
287    def setFreeze(self, freeze, startWidget = None):
288        """ Freeze/unfreeze signal processing. If freeze=1 no signal will be
289        processed until freeze is set back to 0
290       
291        """
292        self.freezing = max(freeze, 0)
293        if freeze > 0:
294            self.addEvent("Freezing signal processing (%s)" % str(freeze), startWidget)
295        elif freeze == 0:
296            self.addEvent("Unfreezing signal processing", startWidget)
297        else:
298            self.addEvent("Invalid freeze value! (by %s)", startWidget, eventVerbosity=0)
299           
300        if self.freezing == 0 and self.widgets != []:
301            self.processNewSignals(self.widgets[0]) # always start processing from the first
302#            if startWidget:
303#                self.processNewSignals(startWidget)
304#            else:
305#                self.processNewSignals(self.widgets[0])
306
307    def addWidget(self, widget):
308        """ Add `widget` to the `widgets` list
309        """
310       
311        self.addEvent("Added widget " + widget.captionTitle, eventVerbosity = 2)
312
313        if widget not in self.widgets:
314            self.widgets.append(widget)
315#            widget.connect(widget, SIGNAL("blockingStateChanged(bool)"), self.onStateChanged)
316
317    def removeWidget(self, widget):
318        """ Remove widget from the `widgets` list
319        """
320#        if self.verbosity >= 2:
321        self.addEvent("Remove widget " + widget.captionTitle, eventVerbosity = 2)
322        self.widgets.remove(widget)
323        if widget in self.links:
324            del self.links[widget]
325
326    def getLinks(self, widgetFrom=None, widgetTo=None, signalNameFrom=None, signalNameTo=None):
327        """ Return a list of matching SignalLinks
328        """
329        links = []
330        if widgetFrom is None:
331            widgets = self.widgets # search all widgets
332        else:
333            widgets = [widgetFrom]
334        for w in widgets:
335            for link in self.links.get(w, []):
336                if (widgetFrom is None or widgetFrom is link.widgetFrom) and \
337                   (widgetTo is None or widgetTo is link.widgetTo) and \
338                   (signalNameFrom is None or signalNameFrom == link.signalNameFrom) and \
339                   (signalNameTo is None or signalNameTo == link.signalNameTo):
340                        links.append(link)
341                   
342        return links
343
344    def getLinkWidgetsIn(self, widget, signalName):
345        """ Return a list of widgets that connect to `widget`'s input `signalName`
346        """
347        links = self.getLinks(None, widget, None, signalName)
348        return [link.widgetFrom for link in links]
349
350
351    def getLinkWidgetsOut(self, widget, signalName):
352        """ Return a list of widgets that connect to `widget`'s output `signalName`
353        """
354        links = self.getLinks(widget, None, signalName, None)
355        return [link.widgetTo for link in links]
356   
357   
358   
359    def canConnect(self, widgetFrom, widgetTo, dynamic=True):
360        # TODO: This should be retrieved from orngRegistry.WidgetDescription
361        outsignals = [OutputSignal(*tt) for tt in widgetFrom.outputs]
362        insignals = [InputSignal(*tt) for tt in widgetTo.inputs]
363       
364        return any(canConnect(out, in_, dynamic) for out in outsignals for in_ in insignals)
365       
366   
367    def proposePossibleLinks(self, widgetFrom, widgetTo, dynamic=True):
368        """ Return a ordered list of (OutputSignal, InputSignal, weight) tuples that
369        can connect both widgets
370        """
371        outSignals = [OutputSignal(*tt) for tt in widgetFrom.outputs]
372        inSignals = [InputSignal(*tt) for tt in widgetTo.inputs]
373       
374        # Get signals that are Single links and already connected to input widget
375        links = self.getLinks(None, widgetTo)
376        alreadyConnected = [link.signalNameTo for link in links if link.inputSignal.single]
377       
378        def weight(outS, inS):
[9548]379            if outS.explicit or inS.explicit:
380                # Zero weight for explicit signals
381                weight = 0
382            else:
383                check = [not outS.dynamic, inS.name not in alreadyConnected, bool(inS.default), bool(outS.default)] #Dynamic signals are lasts
384                weights = [2**i for i in range(len(check), 0, -1)]
385                weight = sum([w for w, c in zip(weights, check) if c])
386            return weight
[8042]387       
388        possibleLinks = []
389        for outS in outSignals:
390            for inS in inSignals:
391                if canConnect(outS, inS, dynamic):
392                    possibleLinks.append((outS, inS, weight(outS, inS)))
393       
394        return sorted(possibleLinks, key=lambda link: link[-1], reverse=True)
395       
396   
397    def inputSignal(self, widget, name):
398        for tt in widget.inputs:
399            if tt[0] == name:
400                return InputSignal(*tt)
401           
402    def outputSignal(self, widget, name):
403        for tt in widget.outputs:
404            if tt[0] == name:
405                return OutputSignal(*tt)
406           
407
408    def addLink(self, widgetFrom, widgetTo, signalNameFrom, signalNameTo, enabled):
409        self.addEvent("Add link from " + widgetFrom.captionTitle + " to " + widgetTo.captionTitle, eventVerbosity = 2)
410
411        ## would this link create a cycle
412        if self.existsPath(widgetTo, widgetFrom): 
413            return 0
414        # check if signal names still exist
415        found = 0
[9548]416        output_names = [t[0] for t in widgetFrom.outputs]
417        found = signalNameFrom in output_names
418       
419        if not found:
420            if signalNameFrom in _CHANNEL_NAME_MAP and \
421                    _CHANNEL_NAME_MAP[signalNameFrom] in output_names:
422                self.addEvent("Widget changed its output signal  %r name. Changed to %r." % (signalNameFrom, _CHANNEL_NAME_MAP[signalNameFrom]),
423                              eventVerbosity=1)
424                signalNameFrom = _CHANNEL_NAME_MAP[signalNameFrom]
425                found = 1
426               
[8042]427        if not found:
428            print "Error. Widget %s changed its output signals. It does not have signal %s anymore." % (str(getattr(widgetFrom, "captionTitle", "")), signalNameFrom)
429            return 0
430
431        found = 0
[9548]432        input_names = [t[0] for t in widgetTo.inputs]
433        found = signalNameTo in input_names
434       
435        if not found:
436            if signalNameTo in _CHANNEL_NAME_MAP and \
437                    _CHANNEL_NAME_MAP[signalNameTo] in input_names:
438                self.addEvent("Widget changed its input signal  %r name. Changed to %r." % (signalNameFrom, _CHANNEL_NAME_MAP[signalNameTo]),
439                              eventVerbosity=1)
440                signalNameTo = _CHANNEL_NAME_MAP[signalNameTo]
441                found = 1
442               
[8042]443        if not found:
444            print "Error. Widget %s changed its input signals. It does not have signal %s anymore." % (str(getattr(widgetTo, "captionTitle", "")), signalNameTo)
445            return 0
446
447        if self.links.has_key(widgetFrom):
448            if self.getLinks(widgetFrom, widgetTo, signalNameFrom, signalNameTo):
449                print "connection ", widgetFrom, " to ", widgetTo, " alread exists. Error!!"
450                return
451
452        link = SignalLink(widgetFrom, self.outputSignal(widgetFrom, signalNameFrom),
453                          widgetTo, self.inputSignal(widgetTo, signalNameTo), enabled=enabled)
454        self.links[widgetFrom] = self.links.get(widgetFrom, []) + [link]
455
456        widgetTo.addInputConnection(widgetFrom, signalNameTo)
457
458        # if there is no key for the signalNameFrom, create it and set its id=None and data = None
459        if not widgetFrom.linksOut.has_key(signalNameFrom):
460            widgetFrom.linksOut[signalNameFrom] = {None:None}
461
462        # if channel is enabled, send data through it
463        if enabled:
464            self.pushAllOnLink(link)
465           
466        # reorder widgets if necessary
467        if self.widgets.index(widgetFrom) > self.widgets.index(widgetTo):
468            self.fixTopologicalOrdering()
469#            self.widgets.remove(widgetTo)
470#            self.widgets.append(widgetTo)   # appent the widget at the end of the list
471#            self.fixPositionOfDescendants(widgetTo)
472           
473        return 1
474
475    # fix position of descendants of widget so that the order of widgets in self.widgets is consistent with the schema
476    def fixPositionOfDescendants(self, widget):
477        for link in self.links.get(widget, []):
478            widgetTo = link.widgetTo
479            self.widgets.remove(widgetTo)
480            self.widgets.append(widgetTo)
481            self.fixPositionOfDescendants(widgetTo)
482           
483    def fixTopologicalOrdering(self):
484        """ fix the widgets topological ordering
485        """
486        order = []
487        visited = set()
488        queue = sorted([w for w in self.widgets if not self.getLinks(None, w)]) 
489        while queue:
490            w = queue.pop(0)
491            order.append(w)
492            visited.add(w)
493            linked = set([link.widgetTo for link in self.getLinks(w)])
494            queue.extend(sorted(linked.difference(queue)))
495        self.widgets[:] = order
496           
497
498    def findSignals(self, widgetFrom, widgetTo):
499        """ Return a list of (outputName, inputName) for links between widgets
500        """
501        links = self.getLinks(widgetFrom, widgetTo)
502        return [(link.signalNameFrom, link.signalNameTo) for link in links]
503   
504
505    def isSignalEnabled(self, widgetFrom, widgetTo, signalNameFrom, signalNameTo):
506        """ Is signal enabled
507        """
508        links = self.getLinks(widgetFrom, widgetTo, signalNameFrom, signalNameTo)
509        if links:
510            return links[0].enabled
511        else:
512            return False
513       
514
515    def removeLink(self, widgetFrom, widgetTo, signalNameFrom, signalNameTo):
516        """ Remove link
517        """
518        self.addEvent("Remove link from " + widgetFrom.captionTitle + " to " + widgetTo.captionTitle, eventVerbosity = 2)
519
520        # no need to update topology, just remove the link
521        if self.links.has_key(widgetFrom):
522            links = self.getLinks(widgetFrom, widgetTo, signalNameFrom, signalNameTo)
523            if len(links) != 1:
524                print "Error removing a link with none or more then one entries"
525                return
526               
527            link = links[0]
528            self.purgeLink(link)
529           
530            self.links[widgetFrom].remove(link)
531            if not self.freezing and not self.signalProcessingInProgress: 
532                self.processNewSignals(widgetFrom)
533        widgetTo.removeInputConnection(widgetFrom, signalNameTo)
534
535
536    # ############################################
537    # ENABLE OR DISABLE LINK CONNECTION
538
539    def setLinkEnabled(self, widgetFrom, widgetTo, enabled, justSend = False):
540        """ Set `enabled` state for links between widgets.
541        """
542        for link in self.getLinks(widgetFrom, widgetTo):
543            if not justSend:
544                link.enabled = enabled
545            if enabled:
546                self.pushAllOnLink(link)
547               
548        if enabled:
549            self.processNewSignals(widgetTo)
550
551
552    def getLinkEnabled(self, widgetFrom, widgetTo):
553        """ Is any link between widgets enabled
554        """
555        return any(link.enabled for link in self.getLinks(widgetFrom, widgetTo))
556
557
558    # widget widgetFrom sends signal with name signalName and value value
559    def send(self, widgetFrom, signalNameFrom, value, id):
560        """ Send signal `signalNameFrom` from `widgetFrom` with `value` and `id`
561        """
562        # add all target widgets new value and mark them as dirty
563        # if not freezed -> process dirty widgets
564        self.addEvent("Send data from " + widgetFrom.captionTitle + ". Signal = " + signalNameFrom, value, eventVerbosity = 2)
565
566        if not self.links.has_key(widgetFrom):
567            return
568       
569        for link in self.getLinks(widgetFrom, None, signalNameFrom, None):
570            self.pushToLink(link, value, id)
571
572        if not self.freezing and not self.signalProcessingInProgress:
573            self.processNewSignals(widgetFrom)
574
575    # when a new link is created, we have to
576    def sendOnNewLink(self, widgetFrom, widgetTo, signals):
577        for (signalNameFrom, signalNameTo) in signals:
578            for link in self.getLinks(widgetFrom, widgetTo, signalNameFrom, signalNameTo):
579                self.pushAllOnLink(link)
580
581
582    def pushAllOnLink(self, link):
583        """ Send all data on link
584        """
585        for key in link.widgetFrom.linksOut[link.signalNameFrom].keys():
586            self.pushToLink(link, link.widgetFrom.linksOut[link.signalNameFrom][key], key)
587
588
589    def purgeLink(self, link):
590        """ Clear all data on link (i.e. send None for all keys)
591        """
592        for key in link.widgetFrom.linksOut[link.signalNameFrom].keys():
593            self.pushToLink(link, None, key)
594           
595    def pushToLink(self, link, value, id):
596        """ Send value with id on link
597        """
598        if link.enabled:
599            if link.dynamic:
600                dyn_enable = link.canEnableDynamic(value)
601                self.setDynamicLinkEnabled(link, dyn_enable)
602                if not dyn_enable:
603                    value = None
604            link.widgetTo.updateNewSignalData(link.widgetFrom, link.signalNameTo, 
605                                          value, id, link.signalNameFrom)
606
607    def processNewSignals(self, firstWidget=None):
608        """ Process new signals starting from `firstWidget`
609        """
610       
611        if len(self.widgets) == 0 or self.signalProcessingInProgress or self.freezing:
612            return
613
614        if firstWidget not in self.widgets or self.widgetQueue:
615            firstWidget = self.widgets[0]   # if some window that is not a widget started some processing we have to process new signals from the first widget
616           
617        self.addEvent("Process new signals starting from " + firstWidget.captionTitle, eventVerbosity = 2)
618
619        skipWidgets = self.getBlockedWidgets() # Widgets that are blocking
620       
[11461]621        self.addEvent("Blocked widgets %s." %
622                      [w.captionTitle for w in skipWidgets],
623                      eventVerbosity=2)
[8042]624        # start propagating
625        self.signalProcessingInProgress = 1
626       
627        index = self.widgets.index(firstWidget)
628        for i in range(index, len(self.widgets)):
629            if self.widgets[i] in skipWidgets:
630                continue
631               
632            while self.widgets[i] in self.widgetQueue:
633                self.widgetQueue.remove(self.widgets[i])
634            if self.widgets[i].needProcessing:
635                self.addEvent("Processing " + self.widgets[i].captionTitle)
636                try:
637                    self.widgets[i].processSignals()
638                except Exception:
639                    type, val, traceback = sys.exc_info()
640                    sys.excepthook(type, val, traceback)  # we pretend that we handled the exception, so that it doesn't crash canvas
641                   
642                if self.widgets[i].isBlocking():
643                    if not self.asyncProcessingEnabled:
644                        self.addEvent("Widget %s blocked during signal processing. Aborting." % self.widgets[i].captionTitle)
645                        break
646                    else:
647                        self.addEvent("Widget %s blocked during signal processing." % self.widgets[i].captionTitle)           
648                   
649                    # If during signal processing the widget changed state to
650                    # blocking we skip all of its descendants
651                    skipWidgets.update(self.widgetDescendants(self.widgets[i]))
652            if self.freezing:
653                self.addEvent("Signals frozen during processing of " + self.widgets[i].captionTitle + ". Aborting.")
654                break
655       
656        # we finished propagating
657        self.signalProcessingInProgress = 0
658       
659        if self.widgetQueue:
660            # if there are still some widgets on queue
661            self.processNewSignals(None)
662       
663    def scheduleSignalProcessing(self, widget=None):
664        self.widgetQueue.append(widget)
665        self.processNewSignals(widget)
666
667
668    def existsPath(self, widgetFrom, widgetTo):
669        """ Is there a path between `widgetFrom` and `widgetTo`
670        """
671        # is there a direct link
672        if not self.links.has_key(widgetFrom):
673            return 0
674
675        for link in self.links[widgetFrom]:
676            if link.widgetTo == widgetTo:
677                return 1
678
679        # is there a nondirect link
680        for link in self.links[widgetFrom]:
681            if self.existsPath(link.widgetTo, widgetTo):
682                return 1
683
684        # there is no link...
685        return 0
686
687    def widgetDescendants(self, widget):
[11461]688        """ Return all widget descendants of `widget`.
689
690        .. note::
691            Includes only widgets which are reachable by *enabled* links.
692
[8042]693        """
[11461]694        queue = deque([widget])
695        descendants = set()
696
697        while queue:
698            w = queue.popleft()
699            linked = [link.widgetTo for link in self.links.get(widget, [])
700                      if link.enabled]
[8042]701            for w in linked:
[11461]702                if w not in descendants:
703                    queue.append(w)
704                    descendants.add(w)
705
706        return list(descendants)
707
[8042]708    def isWidgetBlocked(self, widget):
709        """ Is this widget or any of its up-stream connected widgets blocked.
710        """
711        if widget.isBlocking():
712            return True
713        else:
[11461]714            widgets = [link.widgetFrom
715                       for link in self.getLinks(None, widget, None, None)
716                       if link.enabled]
717            return any(self.isWidgetBlocked(w) for w in widgets)
718
[8042]719    def getBlockedWidgets(self):
720        """ Return a set of all widgets that are blocked.
721        """
722        blocked = set()
723        for w in self.widgets:
724            if w not in blocked and w.isBlocking():
[11461]725                blocked.update(set([w]).union(set(self.widgetDescendants(w))))
[8042]726        return blocked
[11461]727
[8042]728    def freeze(self, widget=None):
729        """ Return a context manager that freezes the signal processing
730        """
731        signalManager = self
732        class freezer(object):
733            def __enter__(self):
734                self.push()
735                return self
736           
737            def __exit__(self, *args):
738                self.pop()
739               
740            def push(self):
741                signalManager.setFreeze(signalManager.freezing + 1)
742               
743            def pop(self):
744                signalManager.setFreeze(signalManager.freezing - 1, widget)
745               
746        return freezer()
747   
748    def setDynamicLinkEnabled(self, link, enabled):
749        import PyQt4.QtCore as QtCore
750        link.widgetFrom.emit(QtCore.SIGNAL("dynamicLinkEnabledChanged(PyQt_PyObject, bool)"), link, enabled)
751       
[9548]752
753# Channel renames.
754
755_CHANNEL_NAME_MAP = \
756    {'Additional Tables': 'Additional Data',
757     'Attribute Definitions': 'Feature Definitions',
758     'Attribute List': 'Features',
759     'Attribute Pair': 'Interacting Features',
760     'Attribute Selection List': 'Features',
761     'Attribute Statistics': 'Feature Statistics',
762     'Attribute selection': 'Features',
763     'Attributes': 'Features',
764     'Choosen Tree': 'Selected Tree',
765     'Covered Examples': 'Covered Data',
766     'Data Instances': 'Data',
767     'Data Table': 'Data',
768     'Distance Matrix': 'Distances',
769     'Distance matrix': 'Distances',
[9549]770     'Distances btw. Instances': 'Distances',
[9548]771     'Example Subset': 'Data Subset',
772     'Example Table': 'Data',
773     'Examples': 'Data',
774     'Examples A': 'Data A',
775     'Examples B': 'Data B',
[9549]776     'Examples with Z-scores': 'Data with z-score',
[9548]777     'Graph with ExampleTable': 'Graph with Data',
778     'Input Data': 'Data',
779     'Input Table': 'Data',
780     'Instances': 'Data',
781     'Items Distance Matrix': 'Distances',
782     'Items Subset': 'Item Subset',
[9549]783     'Items to Mark': 'Marked Items',
[9548]784     'KNN Classifier': 'kNN Classifier',
785     'Marked Examples': 'Marked Data',
[9605]786     'Matching Examples': 'Matching Data',
[9549]787     'Merged Examples A+B': 'Merged Data A+B',
788     'Merged Examples B+A': 'Merged Data B+A',
[9548]789     'Mismatching Examples': 'Mismatched Data',
[9605]790     'Non-Matching Examples': 'Unmatched Data',
[9548]791     'Output Data': 'Data',
792     'Output Table': 'Data',
793     'Preprocessed Example Table': 'Preprocessed Data',
794     'Primary Table': 'Primary Data',
795     'Reduced Example Table': 'Reduced Data',
796     'Remaining Examples': 'Remaining Data',
797     'SOMMap': 'SOM',
798     'Sample': 'Data Sample',
799     'Selected Attributes List': 'Selected Features',
800     'Selected Examples': 'Selected Data',
801     'Selected Instances': 'Selected Data',
802     'Selected Items Distance Matrix': 'Distance Matrix',
803     'Shuffled Data Table': 'Shuffled Data',
804     'Train Data': 'Training Data',
805     'Training data': 'Data',
806     'Unselected Examples': 'Other Data',
807     'Unselected Items': 'Other Items',
808     }   
[8042]809
810# create a global instance of signal manager
811globalSignalManager = SignalManager()
[9548]812
Note: See TracBrowser for help on using the repository browser.