diff --git a/orangewidget/utils/signals.py b/orangewidget/utils/signals.py index 5cb76f7f1..a1bbc33b7 100644 --- a/orangewidget/utils/signals.py +++ b/orangewidget/utils/signals.py @@ -1,5 +1,6 @@ import copy import itertools +from functools import singledispatch from orangecanvas.registry.description import ( InputSignal, OutputSignal, Single, Multiple, Default, NonDefault, @@ -14,6 +15,11 @@ _counter = itertools.count() +@singledispatch +def summarize(_): + return None, None + + class _Signal: @staticmethod def get_flags(multiple, default, explicit, dynamic): @@ -76,11 +82,17 @@ def set_train_data(self, data): explicit (bool, optional): if set, this signal is only used when it is the only option or when explicitly connected in the dialog (default: `False`) + auto_summary (bool, optional): + decides whether this signal is used for auto_summary; only one signal + should set this to True. If left at default (`None`), the value is set + by `set_default_auto_summary`, which is called from the meta-class. """ def __init__(self, name, type, id=None, doc=None, replaces=None, *, - multiple=False, default=False, explicit=False): + multiple=False, default=False, explicit=False, + auto_summary=None): flags = self.get_flags(multiple, default, explicit, False) super().__init__(name, type, "", flags, id, doc, replaces or []) + self.auto_summary = auto_summary self._seq_id = next(_counter) def __call__(self, method): @@ -88,11 +100,22 @@ def __call__(self, method): Decorator that stores decorated method's name in the signal's `handler` attribute. The method is returned unchanged. """ + def set_summary(widget, value, *args, **kwargs): + if self.auto_summary: + info = widget.info + if value is None: + info.set_input_summary(info.NoInput) + else: + summary, details = summarize(value) + if summary is not None: + info.set_input_summary(summary, details) + return method(widget, value, *args, **kwargs) + if self.handler: raise ValueError("Input {} is already bound to method {}". format(self.name, self.handler)) self.handler = method.__name__ - return method + return set_summary class Output(OutputSignal, _Signal): @@ -133,12 +156,18 @@ class Outputs: of the declared type and that the output can be connected to any input signal which can accept a subtype of the declared output type (default: `True`) + auto_summary (bool, optional): + decides whether this signal is used for auto_summary; only one signal + should set this to True. If left at default (`None`), the value is set + by `set_default_auto_summary`, which is called from the meta-class. """ def __init__(self, name, type, id=None, doc=None, replaces=None, *, - default=False, explicit=False, dynamic=True): + default=False, explicit=False, dynamic=True, + auto_summary=None): flags = self.get_flags(False, default, explicit, dynamic) + self.auto_summary = auto_summary super().__init__(name, type, flags, id, doc, replaces or []) - self.widget = None + self.widget = None #: OWBaseWidget self._seq_id = next(_counter) def send(self, value, id=None): @@ -147,6 +176,14 @@ def send(self, value, id=None): signal_manager = self.widget.signalManager if signal_manager is not None: signal_manager.send(self.widget, self.name, value, id) + info = self.widget.info + if self.auto_summary: + if value is None: + info.set_output_summary(info.NoOutput) + else: + summary, details = summarize(value) + if summary is not None: + info.set_output_summary(summary, details) def invalidate(self): """Invalidate the current output value on the signal""" @@ -257,6 +294,28 @@ def get_signals(cls, direction, ignore_old_style=False): signals = [signal for _, signal in getmembers(signal_class, _Signal)] return list(sorted(signals, key=lambda s: s._seq_id)) + @classmethod + def set_default_auto_summary(cls): + """ + Set the default auto_summary; called from meta class. + + If + - there is only a single input/output, or there is a default signal, + - and that signal has auto_summary left at default (None), + - and no other signal has auto_summary set to True, + set that signal's auto_summary to True, so that it's value will be + shown in the status. + """ + for signal_cls, signal_type in ((cls.Inputs, Input), + (cls.Outputs, Output)): + signals = getmembers(signal_cls, signal_type) + if any(signal.auto_summary for _, signal in signals): + continue + for _, signal in signals: + if signal.auto_summary is None: + signal.auto_summary = \ + bool(signal.flags & Default) or len(signals) == 1 + class AttributeList(list): """Signal type for lists of attributes (variables)""" diff --git a/orangewidget/widget.py b/orangewidget/widget.py index 9180b8d80..9a0d86582 100644 --- a/orangewidget/widget.py +++ b/orangewidget/widget.py @@ -98,6 +98,7 @@ def __new__(mcs, name, bases, namespace, openclass=False, **kwargs): if not cls.name: # not a widget return cls cls.convert_signals() + cls.set_default_auto_summary() cls.settingsHandler = \ SettingsHandler.create(cls, template=cls.settingsHandler) return cls