diff --git a/Orange/widgets/settings.py b/Orange/widgets/settings.py index 69299a46aea..3df5e5b6839 100644 --- a/Orange/widgets/settings.py +++ b/Orange/widgets/settings.py @@ -787,17 +787,6 @@ def close_context(self, widget): if widget.current_context is None: return - # Clear schema-only settings when *closing* the context - # FIXME: Sadly, schema-only settings aren't cleared when non-contextual - # SettingsHandler is used. Example: widget has non-contextual - # SettingsHandler, user loads schema that includes widget, user - # passes new data to widget => widget's schema-only settings still - # have previous values because equivalent of below reset was never - # called (i.e. `close_context()`). - for name, setting in self.known_settings.items(): - if setting.schema_only: - setattr(widget, name, setting.default) - self.settings_from_widget(widget) widget.current_context = None diff --git a/Orange/widgets/tests/test_context_handler.py b/Orange/widgets/tests/test_context_handler.py index 0b492c7962d..a3c94f54dce 100644 --- a/Orange/widgets/tests/test_context_handler.py +++ b/Orange/widgets/tests/test_context_handler.py @@ -15,6 +15,7 @@ class SimpleWidget: settings_version = 1 setting = Setting(42) + schema_only_setting = Setting(None, schema_only=True) context_setting = ContextSetting(42) @@ -181,6 +182,17 @@ def test_write_defaults_stores_version(self): for c in contexts: self.assertEqual(c.values.get("__version__", 0xBAD), 1) + def test_close_context(self): + handler = ContextHandler() + handler.bind(SimpleWidget) + widget = SimpleWidget() + widget.storeSpecificSettings = Mock() + handler.initialize(widget) + widget.schema_only_setting = 0xD06F00D + widget.current_context = handler.new_context() + handler.close_context(widget) + self.assertEqual(widget.schema_only_setting, 0xD06F00D) + class TestSettingsPrinter(TestCase): def test_formats_contexts(self): diff --git a/Orange/widgets/visualize/owlinearprojection.py b/Orange/widgets/visualize/owlinearprojection.py index 2922b0f41ad..4c23c67718c 100644 --- a/Orange/widgets/visualize/owlinearprojection.py +++ b/Orange/widgets/visualize/owlinearprojection.py @@ -236,6 +236,9 @@ def __init__(self): self.__legend = None self.__selection_item = None self.__replot_requested = False + #: Remember the saved state to restore + self.__pending_selection_restore = self.selection_indices + self.selection_indices = None box = gui.vBox(self.controlArea, "Axes") @@ -425,6 +428,7 @@ def sizeHint(self): def clear(self): self.data = None + self.selection_indices = None self._subset_mask = None self._selection_mask = None self.varmodel_selected[:] = [] @@ -515,8 +519,9 @@ def set_data(self, data): if set(selected_keys).issubset(set(state.keys())): pass - if self.selection_indices is not None: - self.select_indices(self.selection_indices) + if self.__pending_selection_restore is not None: + self.select_indices(self.__pending_selection_restore) + self.__pending_selection_restore = None # update the defaults state (the encoded state must contain # all variables in the input domain) @@ -942,6 +947,7 @@ def select_indices(self, indices, modifiers=Qt.NoModifier): self._selection_mask[indices] = True self._on_color_change() + self.selection_indices = numpy.flatnonzero(self._selection_mask).tolist() self.commit() def commit(self): @@ -952,7 +958,6 @@ def commit(self): if len(indices) > 0: subset = self.data[indices] - self.selection_indices = indices self.Outputs.selected_data.send(subset) self.Outputs.annotated_data.send(create_annotated_table(self.data, indices)) diff --git a/Orange/widgets/visualize/owscatterplot.py b/Orange/widgets/visualize/owscatterplot.py index 9c20a0be917..f3159881924 100644 --- a/Orange/widgets/visualize/owscatterplot.py +++ b/Orange/widgets/visualize/owscatterplot.py @@ -109,6 +109,8 @@ class Outputs: attr_x = ContextSetting(None) attr_y = ContextSetting(None) + + #: Serialized selection state to be restored selection_group = Setting(None, schema_only=True) graph = SettingProvider(OWScatterPlotGraph) @@ -142,6 +144,9 @@ def __init__(self): self.attribute_selection_list = None # list of Orange.data.Variable self.__timer = QTimer(self, interval=1200) self.__timer.timeout.connect(self.add_data) + #: Remember the saved state to restore + self.__pending_selection_restore = self.selection_group + self.selection_group = None common_options = dict( labelWidth=50, orientation=Qt.Horizontal, sendSelectedValue=True, @@ -332,14 +337,16 @@ def handleNewSignals(self): self.update_graph() self.cb_class_density.setEnabled(self.graph.can_draw_density()) self.cb_reg_line.setEnabled(self.graph.can_draw_regresssion_line()) - self.apply_selection() + if self.data is not None and self.__pending_selection_restore is not None: + self.apply_selection(self.__pending_selection_restore) + self.__pending_selection_restore = None self.unconditional_commit() - def apply_selection(self): - """Apply selection saved in workflow.""" - if self.data is not None and self.selection_group is not None: + def apply_selection(self, selection): + """Apply `selection` to the current plot.""" + if self.data is not None: self.graph.selection = np.zeros(len(self.data), dtype=np.uint8) - self.selection_group = [x for x in self.selection_group if x[0] < len(self.data)] + self.selection_group = [x for x in selection if x[0] < len(self.data)] selection_array = np.array(self.selection_group).T self.graph.selection[selection_array[0]] = selection_array[1] self.graph.update_colors(keep_colors=True) @@ -389,6 +396,19 @@ def update_graph(self, reset_view=True, **_): self.graph.update_data(self.attr_x, self.attr_y, reset_view) def selection_changed(self): + + # Store current selection in a setting that is stored in workflow + if isinstance(self.data, SqlTable): + selection = None + elif self.data is not None: + selection = self.graph.get_selection() + else: + selection = None + if selection is not None and len(selection): + self.selection_group = list(zip(selection, self.graph.selection[selection])) + else: + self.selection_group = None + self.commit() def send_data(self): @@ -410,12 +430,6 @@ def _get_annotated(): self.Outputs.annotated_data.send(_get_annotated()) self.Outputs.selected_data.send(_get_selected()) - # Store current selection in a setting that is stored in workflow - if len(selection): - self.selection_group = list(zip(selection, graph.selection[selection])) - else: - self.selection_group = None - def send_features(self): features = [attr for attr in [self.attr_x, self.attr_y] if attr] self.Outputs.features.send(features or None) diff --git a/Orange/widgets/visualize/tests/test_owscatterplot.py b/Orange/widgets/visualize/tests/test_owscatterplot.py index 03d3bf3f24d..70cfc12f0eb 100644 --- a/Orange/widgets/visualize/tests/test_owscatterplot.py +++ b/Orange/widgets/visualize/tests/test_owscatterplot.py @@ -250,7 +250,10 @@ def test_saving_selection(self): def test_points_selection(self): # Opening widget with saved selection should restore it - self.widget.selection_group = [(i, 1) for i in range(50)] + self.widget = self.create_widget( + OWScatterPlot, stored_settings={ + "selection_group": [(i, 1) for i in range(50)]} + ) self.send_signal(self.widget.Inputs.data, self.data) # iris selected_data = self.get_output(self.widget.Outputs.selected_data) self.assertEqual(len(selected_data), 50) @@ -269,7 +272,10 @@ def test_migrate_selection(self): def test_invalid_points_selection(self): # if selection contains rows that are not present in the current # dataset, widget should select what can be selected. - self.widget.selection_group = [(i, 1) for i in range(50)] + self.widget = self.create_widget( + OWScatterPlot, stored_settings={ + "selection_group": [(i, 1) for i in range(50)]} + ) self.send_signal(self.widget.Inputs.data, self.data[:10]) selected_data = self.get_output(self.widget.Outputs.selected_data) self.assertEqual(len(selected_data), 10)