source: orange/Orange/OrangeCanvas/orngSignalManager.py @ 10996:a7d40db57958

Revision 10996:a7d40db57958, 30.1 KB checked in by Ales Erjavec <ales.erjavec@…>, 18 months ago (diff)

Fixed an error with undefined attributes when using old style channel flags.

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