Skip to content

Commit f7cee7e

Browse files
authored
Merge pull request #7231 from janezd/create-class-non-context-settings
[FIX] Create Class: Remove context settings in favour of schema-only non-context
2 parents c23dd1b + 252d75b commit f7cee7e

3 files changed

Lines changed: 69 additions & 38 deletions

File tree

Orange/widgets/data/owcreateclass.py

Lines changed: 30 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
import numpy as np
77

8-
from AnyQt.QtWidgets import QFrame, QGridLayout, QLabel, QLineEdit, \
8+
from AnyQt.QtWidgets import QLayout, QFrame, QGridLayout, QLabel, QLineEdit, \
99
QSizePolicy, QWidget, QScrollArea
1010
from AnyQt.QtCore import Qt, QTimer
1111

@@ -14,7 +14,7 @@
1414
from Orange.statistics.util import bincount
1515
from Orange.preprocess.transformation import Transformation, Lookup
1616
from Orange.widgets import gui, widget
17-
from Orange.widgets.settings import DomainContextHandler, ContextSetting
17+
from Orange.widgets.settings import DomainContextHandler, ContextSetting, Setting
1818
from Orange.widgets.utils.itemmodels import DomainModel
1919
from Orange.widgets.utils.localization import pl
2020
from Orange.widgets.utils.widgetpreview import WidgetPreview
@@ -233,12 +233,14 @@ class Outputs:
233233
MAX_RULES_AREA_HEIGHT = 360
234234

235235
settingsHandler = DomainContextHandler()
236-
attribute = ContextSetting(None)
237-
class_name = ContextSetting("class")
238-
rules = ContextSetting({})
239-
match_beginning = ContextSetting(False)
240-
case_sensitive = ContextSetting(False)
241-
regular_expressions = ContextSetting(False)
236+
attribute = ContextSetting(None, schema_only=True)
237+
class_name = Setting("class", schema_only=True)
238+
rules = Setting({}, schema_only=True)
239+
match_beginning = Setting(False, schema_only=True)
240+
case_sensitive = Setting(False, schema_only=True)
241+
regular_expressions = Setting(False, schema_only=True)
242+
243+
settings_version = 2
242244

243245
TRANSFORMERS = {StringVariable: ValueFromStringSubstring,
244246
DiscreteVariable: ValueFromDiscreteSubstring}
@@ -277,27 +279,28 @@ def __init__(self):
277279
# once the new row's height has been applied
278280
self._scroll_to_bottom_pending = False
279281

280-
gui.lineEdit(
282+
le = gui.lineEdit(
281283
self.controlArea, self, "class_name",
282284
orientation=Qt.Horizontal, box="New Class Name")
285+
le.setStyleSheet("QLineEdit { padding-left: 4px; }")
283286

284-
variable_select_box = gui.vBox(self.controlArea, "Match by Substring")
287+
variable_select_box = gui.vBox(self.controlArea, box="Source column and patterns")
285288

286289
combo = gui.comboBox(
287-
variable_select_box, self, "attribute", label="From column:",
288-
orientation=Qt.Horizontal, searchable=True,
290+
variable_select_box, self, "attribute", searchable=True,
289291
callback=self.update_rules,
290292
model=DomainModel(valid_types=(StringVariable, DiscreteVariable)))
291293
# Don't use setSizePolicy keyword argument here: it applies to box,
292294
# not the combo
293295
combo.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Preferred)
294296

295297
patternbox = gui.vBox(variable_select_box)
298+
patternbox.layout().setSpacing(0)
296299
#: QWidget: the box that contains the remove buttons, line edits and
297300
# count labels. The lines are added and removed dynamically.
298301
self.rules_box = rules_box = QGridLayout()
299302
rules_box.setSpacing(4)
300-
rules_box.setContentsMargins(4, 4, 4, 4)
303+
rules_box.setContentsMargins(4, 4, 4, 0)
301304
self.rules_box.setColumnMinimumWidth(1, 70)
302305
self.rules_box.setColumnMinimumWidth(0, 10)
303306
self.rules_box.setColumnStretch(0, 1)
@@ -342,6 +345,7 @@ def __init__(self):
342345
gui.button(self.buttonsArea, self, "Apply", callback=self.apply)
343346

344347
self.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Maximum)
348+
self.layout().setSizeConstraint(QLayout.SetFixedSize)
345349

346350
@property
347351
def active_rules(self):
@@ -363,7 +367,6 @@ def rules_to_edits(self):
363367
def set_data(self, data):
364368
"""Input data signal handler."""
365369
self.closeContext()
366-
self.rules = {}
367370
self.data = data
368371
model = self.controls.attribute.model()
369372
model.set_domain(data.domain if data is not None else None)
@@ -723,6 +726,19 @@ def _count_part():
723726
self.report_items("Output", [("Class name", self.class_name)])
724727
self.report_raw(f"<ol>{output}</ol>")
725728

729+
@classmethod
730+
def migrate_settings(cls, settings, version):
731+
if version < 2:
732+
contexts = settings.pop("context_settings", [])
733+
if contexts:
734+
context = contexts[0]
735+
settings.update(
736+
{name: context.values.pop(name)[0]
737+
for name in ("class_name", "rules", "match_beginning",
738+
"case_sensitive", "regular_expressions")})
739+
context.values["__version__"] = 2
740+
settings["context_settings"] = [context] # selected attribute
741+
726742

727743
if __name__ == "__main__": # pragma: no cover
728744
WidgetPreview(OWCreateClass).run(Table("zoo"))

Orange/widgets/data/tests/test_owcreateclass.py

Lines changed: 29 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
import numpy as np
77

8+
from orangewidget.settings import Context
89
from Orange.data import Table, StringVariable, DiscreteVariable, Domain
910
from Orange.widgets.data.owcreateclass import (
1011
OWCreateClass,
@@ -503,7 +504,7 @@ def _check_thal(self):
503504
np.testing.assert_equal(classes[fixed], 1)
504505
self.assertTrue(np.all(np.isnan(classes[~(reversable | fixed)])))
505506

506-
def test_flow_and_context_handling(self):
507+
def test_flow(self):
507508
widget = self.widget
508509
self.send_signal(self.widget.Inputs.data, self.heart)
509510
self._test_default_rules()
@@ -545,27 +546,6 @@ def test_flow_and_context_handling(self):
545546
self._set_attr(thal)
546547
self._check_thal()
547548

548-
prev_rules = widget.rules
549-
self.send_signal(self.widget.Inputs.data, self.zoo)
550-
self.assertIsNot(widget.rules, prev_rules)
551-
552-
self.send_signal(self.widget.Inputs.data, self.heart)
553-
self._check_thal()
554-
555-
# Check that sending None as data does not ruin the context, and that
556-
# the empty context does not match the true one later
557-
self.send_signal(self.widget.Inputs.data, None)
558-
self.assertIsNot(widget.rules, prev_rules)
559-
560-
self.send_signal(self.widget.Inputs.data, self.heart)
561-
self._check_thal()
562-
563-
self.send_signal(self.widget.Inputs.data, self.no_attributes)
564-
self.assertIsNot(widget.rules, prev_rules)
565-
566-
self.send_signal(self.widget.Inputs.data, self.heart)
567-
self._check_thal()
568-
569549
def test_add_remove_lines(self):
570550
widget = self.widget
571551
self.send_signal(self.widget.Inputs.data, self.heart)
@@ -690,6 +670,33 @@ def test_same_class(self):
690670
self.get_output(widget2.Outputs.data, widget=widget2).domain.class_var
691671
)
692672

673+
def test_migrate_settings_1_2(self):
674+
settings = {"__version__": 1, "context_settings": [Context(
675+
values= {
676+
'attribute': ('eggs', 101), # not a default selection
677+
'case_sensitive': (True, -2),
678+
'class_name': ('myclass', -2),
679+
'match_beginning': (True, -2),
680+
'regular_expressions': (True, -2),
681+
'rules': ({'type': [['cam', 'am'], ['cer', 'er'], ['', '']],
682+
'eggs': [['de', 'e1'], ['', '']]},
683+
-2),
684+
'__version__': 1},
685+
attributes = {'hair': 1, 'feathers': 1, 'eggs': 1, 'type': 1},
686+
metas = {'name': 3})]}
687+
w = self.create_widget(OWCreateClass, stored_settings=settings)
688+
self.send_signal(w.Inputs.data, self.zoo, widget=w)
689+
self.assertEqual(w.attribute, self.zoo.domain["eggs"])
690+
self.assertEqual(w.active_rules, [['de', 'e1'], ['', '']])
691+
self.assertEqual(w.class_name, "myclass")
692+
self.assertTrue(w.case_sensitive)
693+
self.assertTrue(w.match_beginning)
694+
self.assertTrue(w.regular_expressions)
695+
696+
w.attribute = self.zoo.domain["type"]
697+
self.assertEqual(w.active_rules, [['cam', 'am'], ['cer', 'er'], ['', '']])
698+
699+
693700

694701
if __name__ == "__main__":
695702
unittest.main()

i18n/si/msgs.jaml

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4881,9 +4881,9 @@ widgets/data/owcreateclass.py:
48814881
def `__init__`:
48824882
class_name: false
48834883
New Class Name: Ime novega razreda
4884-
Match by Substring: Vzorci in razredi
4884+
'QLineEdit { padding-left: 4px; }': false
4885+
Source column and patterns: Izvorni stolpec in vzorci
48854886
attribute: false
4886-
From column:: Iz stolpca:
48874887
Name: Ime
48884888
Substring: Vzorec
48894889
Count: Primerov
@@ -4934,6 +4934,14 @@ widgets/data/owcreateclass.py:
49344934
Output: Izhod
49354935
Class name: Ime razreda
49364936
<ol>{output}</ol>: false
4937+
def `migrate_settings`:
4938+
context_settings: false
4939+
class_name: false
4940+
rules: false
4941+
match_beginning: false
4942+
case_sensitive: false
4943+
regular_expressions: false
4944+
__version__: false
49374945
__main__: false
49384946
zoo: false
49394947
widgets/data/owcreateinstance.py:

0 commit comments

Comments
 (0)