Repository: biolab/orange3

Issue:
[ENH] Paint data should store its state when workflow is saved
##### Orange version:

3.3.6
##### Expected behavior:

When Orange workflow is saved, it should also save the data painted in the Paint Data widget
##### Actual behavior:

If I paint the data using Paint Data widget and save the workflow, the painted data is not saved. That is, when I close Orange and open the saved workflow, Paint Data initializes with the empty data set. 
##### Steps to reproduce the behavior

Paint the data in the Paint Data widget, store the workflow, close Orange, open the workflow. The data set in the Paint Data widget is empty.



Discussion:

Resolving patch:
diff --git a/Orange/widgets/data/owpaintdata.py b/Orange/widgets/data/owpaintdata.py
--- a/Orange/widgets/data/owpaintdata.py
+++ b/Orange/widgets/data/owpaintdata.py
@@ -774,12 +774,14 @@ class OWPaintData(widget.OWWidget):
     brushRadius = Setting(75)
     density = Setting(7)
 
+    data = Setting(None, schema_only=True)
+
     graph_name = "plot"
 
     def __init__(self):
         super().__init__()
 
-        self.data = self.input_data = None
+        self.input_data = None
         self.input_classes = []
         self.input_has_attr2 = True
         self.current_tool = None
@@ -799,12 +801,14 @@ def __init__(self):
         self.class_model.rowsInserted.connect(self._class_count_changed)
         self.class_model.rowsRemoved.connect(self._class_count_changed)
 
-        self.data = np.zeros((0, 3))
+        if self.data is None:
+            self.data = np.zeros((0, 3))
         self.colors = colorpalette.ColorPaletteGenerator(
             len(colorpalette.DefaultRGBColors))
         self.tools_cache = {}
 
         self._init_ui()
+        self.commit()
 
     def _init_ui(self):
         namesBox = gui.vBox(self.controlArea, "Names")
@@ -893,6 +897,7 @@ def _init_ui(self):
         redo.setShortcut(QtGui.QKeySequence.Redo)
 
         self.addActions([undo, redo])
+        self.undo_stack.indexChanged.connect(lambda _: self.invalidate())
 
         gui.separator(tBox)
         indBox = gui.indentedBox(tBox, sep=8)
@@ -1104,7 +1109,6 @@ def set_current_tool(self, tool):
 
         if tool not in self.tools_cache:
             newtool = tool(self, self.plot)
-            newtool.editingFinished.connect(self.invalidate)
             self.tools_cache[tool] = newtool
             newtool.issueCommand.connect(self._add_command)
 
diff --git a/Orange/widgets/settings.py b/Orange/widgets/settings.py
--- a/Orange/widgets/settings.py
+++ b/Orange/widgets/settings.py
@@ -55,6 +55,9 @@ class Setting:
     # Settings are automatically persisted to disk
     packable = True
 
+    # Setting is only persisted to schema (default value does not change)
+    schema_only = False
+
     def __new__(cls, default, *args, **kwargs):
         """A misleading docstring for providing type hints for Settings
 
@@ -482,6 +485,9 @@ def update_defaults(self, widget):
         widget : OWWidget
         """
         self.defaults = self.provider.pack(widget)
+        for name, setting in self.known_settings.items():
+            if setting.schema_only:
+                self.defaults.pop(name, None)
         self.write_defaults()
 
     def fast_save(self, widget, name, value):
@@ -495,7 +501,9 @@ def fast_save(self, widget, name, value):
 
         """
         if name in self.known_settings:
-            self.known_settings[name].default = value
+            setting = self.known_settings[name]
+            if not setting.schema_only:
+                setting.default = value
 
     def reset_settings(self, instance):
         """Reset widget settings to defaults


Test patch:
diff --git a/Orange/widgets/tests/test_settings_handler.py b/Orange/widgets/tests/test_settings_handler.py
--- a/Orange/widgets/tests/test_settings_handler.py
+++ b/Orange/widgets/tests/test_settings_handler.py
@@ -191,6 +191,28 @@ def test_fast_save_siblings_spill(self):
         self.assertEqual(widget_mk2.component.int_setting, 42,
                          "spils defaults into sibling classes")
 
+    def test_schema_only_settings(self):
+        handler = SettingsHandler()
+        handler.read_defaults = lambda: None
+        handler.bind(SimpleWidget)
+
+        # fast_save should not update defaults
+        widget = SimpleWidget()
+        handler.fast_save(widget, 'schema_only_setting', 5)
+        self.assertEqual(
+            handler.known_settings['schema_only_setting'].default, None)
+
+        # update_defaults should not update defaults
+        widget.schema_only_setting = 5
+        handler.update_defaults(widget)
+        self.assertEqual(
+            handler.known_settings['schema_only_setting'].default, None)
+
+        # pack_data should pack setting
+        widget.schema_only_setting = 5
+        data = handler.pack_data(widget)
+        self.assertEqual(data['schema_only_setting'], 5)
+
 
 class Component:
     int_setting = Setting(42)
@@ -198,6 +220,7 @@ class Component:
 
 class SimpleWidget:
     setting = Setting(42)
+    schema_only_setting = Setting(None, schema_only=True)
     non_setting = 5
 
     component = SettingProvider(Component)