source: orange/Orange/OrangeCanvas/orngSignalManager.py @ 10821:7bcb690af230

Revision 10821:7bcb690af230, 30.0 KB checked in by Ales Erjavec <ales.erjavec@…>, 2 years ago (diff)

Added resolveSignal function.

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