From df1b49f72622b33758e0f32e8331505a5b3699b2 Mon Sep 17 00:00:00 2001 From: janezd Date: Thu, 4 Mar 2021 13:31:46 +0100 Subject: [PATCH 1/2] Add automated output summaries --- orangewidget/utils/signals.py | 44 +++++++++++++++++++++++++++++++++-- orangewidget/widget.py | 1 + 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/orangewidget/utils/signals.py b/orangewidget/utils/signals.py index 5cb76f7f1..e5ef44a1a 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): @@ -133,12 +139,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 +159,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 +277,26 @@ 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 output, or there is a default output. + - and that output has auto_summary left at default (None), + - and no other signal has auto_summar set to True, + set that signal's auto_summary to True, so that it's value will be + shown in the status. + """ + outputs = getmembers(cls.Outputs, Output) + if any(output.auto_summary for _, output in outputs): + return + for _, output in outputs: + if output.auto_summary is None: + output.auto_summary = \ + bool(output.flags & Default) or len(outputs) == 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 From 200dbfc02d0c269e184999a636d1d05dcba20427 Mon Sep 17 00:00:00 2001 From: janezd Date: Thu, 4 Mar 2021 14:36:17 +0100 Subject: [PATCH 2/2] Add automated input summaries --- orangewidget/utils/signals.py | 43 +++++++++++++++++++++++++---------- 1 file changed, 31 insertions(+), 12 deletions(-) diff --git a/orangewidget/utils/signals.py b/orangewidget/utils/signals.py index e5ef44a1a..a1bbc33b7 100644 --- a/orangewidget/utils/signals.py +++ b/orangewidget/utils/signals.py @@ -82,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): @@ -94,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): @@ -283,19 +300,21 @@ def set_default_auto_summary(cls): Set the default auto_summary; called from meta class. If - - there is only a single output, or there is a default output. - - and that output has auto_summary left at default (None), - - and no other signal has auto_summar set to True, + - 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. """ - outputs = getmembers(cls.Outputs, Output) - if any(output.auto_summary for _, output in outputs): - return - for _, output in outputs: - if output.auto_summary is None: - output.auto_summary = \ - bool(output.flags & Default) or len(outputs) == 1 + 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):