Skip to content

Commit f047100

Browse files
authored
Merge pull request #5732 from ales-erjavec/edit-domain-indicate-duplicated-errors
[ENH] oweditdomain: Indicate variables in error state
2 parents 848fc18 + b24ab28 commit f047100

File tree

2 files changed

+108
-19
lines changed

2 files changed

+108
-19
lines changed

Orange/widgets/data/oweditdomain.py

Lines changed: 51 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,12 @@
2424
QStyledItemDelegate, QStyleOptionViewItem, QStyle, QSizePolicy,
2525
QDialogButtonBox, QPushButton, QCheckBox, QComboBox, QStackedLayout,
2626
QDialog, QRadioButton, QGridLayout, QLabel, QSpinBox, QDoubleSpinBox,
27-
QAbstractItemView, QMenu
27+
QAbstractItemView, QMenu, QToolTip
28+
)
29+
from AnyQt.QtGui import (
30+
QStandardItemModel, QStandardItem, QKeySequence, QIcon, QBrush, QPalette,
31+
QHelpEvent
2832
)
29-
from AnyQt.QtGui import QStandardItemModel, QStandardItem, QKeySequence, QIcon
3033
from AnyQt.QtCore import (
3134
Qt, QSize, QModelIndex, QAbstractItemModel, QPersistentModelIndex, QRect,
3235
QPoint,
@@ -1588,11 +1591,30 @@ def initStyleOption(self, option, index):
15881591
# mark as changed (maybe also change color, add text, ...)
15891592
option.font.setItalic(True)
15901593

1594+
multiplicity = index.data(MultiplicityRole)
1595+
if isinstance(multiplicity, int) and multiplicity > 1:
1596+
option.palette.setBrush(QPalette.Text, QBrush(Qt.red))
1597+
option.palette.setBrush(QPalette.HighlightedText, QBrush(Qt.red))
1598+
1599+
def helpEvent(self, event: QHelpEvent, view: QAbstractItemView,
1600+
option: QStyleOptionViewItem, index: QModelIndex) -> bool:
1601+
multiplicity = index.data(MultiplicityRole)
1602+
name = VariableListModel.effective_name(index)
1603+
if isinstance(multiplicity, int) and multiplicity > 1 \
1604+
and name is not None:
1605+
QToolTip.showText(
1606+
event.globalPos(), f"Name `{name}` is duplicated",
1607+
view.viewport()
1608+
)
1609+
return True
1610+
else: # pragma: no cover
1611+
return super().helpEvent(event, view, option, index)
1612+
15911613

15921614
# Item model for edited variables (Variable). Define a display role to be the
15931615
# source variable name. This is used only in keyboard search. The display is
15941616
# otherwise completely handled by a delegate.
1595-
class VariableListModel(itemmodels.PyListModel):
1617+
class VariableListModel(CountedListModel):
15961618
def data(self, index, role=Qt.DisplayRole):
15971619
# type: (QModelIndex, Qt.ItemDataRole) -> Any
15981620
row = index.row()
@@ -1606,6 +1628,32 @@ def data(self, index, role=Qt.DisplayRole):
16061628
return item.vtype.name
16071629
return super().data(index, role)
16081630

1631+
def key(self, index):
1632+
return VariableListModel.effective_name(index)
1633+
1634+
def keyRoles(self): # type: () -> FrozenSet[int]
1635+
return frozenset((Qt.DisplayRole, Qt.EditRole, TransformRole))
1636+
1637+
@staticmethod
1638+
def effective_name(index) -> Optional[str]:
1639+
item = index.data(Qt.EditRole)
1640+
if isinstance(item, DataVectorTypes):
1641+
var = item.vtype
1642+
elif isinstance(item, VariableTypes):
1643+
var = item
1644+
else:
1645+
return None
1646+
tr = index.data(TransformRole)
1647+
return effective_name(var, tr or [])
1648+
1649+
1650+
def effective_name(var: Variable, tr: Sequence[Transform]) -> str:
1651+
name = var.name
1652+
for t in tr:
1653+
if isinstance(t, Rename):
1654+
name = t.name
1655+
return name
1656+
16091657

16101658
class ReinterpretVariableEditor(VariableEditor):
16111659
"""

Orange/widgets/data/tests/test_oweditdomain.py

Lines changed: 57 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,13 @@
1010
from numpy.testing import assert_array_equal
1111
import pandas as pd
1212

13-
from AnyQt.QtCore import QItemSelectionModel, Qt, QItemSelection
13+
from AnyQt.QtCore import QItemSelectionModel, Qt, QItemSelection, QPoint
14+
from AnyQt.QtGui import QPalette, QColor, QHelpEvent
1415
from AnyQt.QtWidgets import QAction, QComboBox, QLineEdit, \
15-
QStyleOptionViewItem, QDialog, QMenu
16+
QStyleOptionViewItem, QDialog, QMenu, QToolTip, QListView
1617
from AnyQt.QtTest import QTest, QSignalSpy
1718

18-
from Orange.widgets.utils import colorpalettes
1919
from orangewidget.tests.utils import simulate
20-
from orangewidget.utils.itemmodels import PyListModel
2120

2221
from Orange.data import (
2322
ContinuousVariable, DiscreteVariable, StringVariable, TimeVariable,
@@ -35,10 +34,12 @@
3534
VariableEditDelegate, TransformRole,
3635
RealVector, TimeVector, StringVector, make_dict_mapper, DictMissingConst,
3736
LookupMappingTransform, as_float_or_nan, column_str_repr, time_parse,
38-
GroupItemsDialog)
37+
GroupItemsDialog, VariableListModel
38+
)
3939
from Orange.widgets.data.owcolor import OWColor, ColorRole
4040
from Orange.widgets.tests.base import WidgetTest, GuiTest
4141
from Orange.widgets.tests.utils import contextMenu
42+
from Orange.widgets.utils import colorpalettes
4243
from Orange.tests import test_filename, assert_array_nanequal
4344

4445
MArray = np.ma.MaskedArray
@@ -665,32 +666,72 @@ def test_unlink(self):
665666
self.assertFalse(cbox.isChecked())
666667

667668

669+
class TestModels(GuiTest):
670+
def test_variable_model(self):
671+
model = VariableListModel()
672+
self.assertEqual(model.effective_name(model.index(-1, -1)), None)
673+
674+
def data(row, role):
675+
return model.data(model.index(row,), role)
676+
677+
def set_data(row, data, role):
678+
model.setData(model.index(row), data, role)
679+
680+
model[:] = [
681+
RealVector(Real("A", (3, "g"), (), False), lambda: MArray([])),
682+
RealVector(Real("B", (3, "g"), (), False), lambda: MArray([])),
683+
]
684+
self.assertEqual(data(0, Qt.DisplayRole), "A")
685+
self.assertEqual(data(1, Qt.DisplayRole), "B")
686+
self.assertEqual(model.effective_name(model.index(1)), "B")
687+
set_data(1, [Rename("A")], TransformRole)
688+
self.assertEqual(model.effective_name(model.index(1)), "A")
689+
self.assertEqual(data(0, MultiplicityRole), 2)
690+
self.assertEqual(data(1, MultiplicityRole), 2)
691+
set_data(1, [], TransformRole)
692+
self.assertEqual(data(0, MultiplicityRole), 1)
693+
self.assertEqual(data(1, MultiplicityRole), 1)
694+
695+
668696
class TestDelegates(GuiTest):
669697
def test_delegate(self):
670-
model = PyListModel([None])
698+
model = VariableListModel([None, None])
671699

672-
def set_item(v: dict):
673-
model.setItemData(model.index(0), v)
700+
def set_item(row: int, v: dict):
701+
model.setItemData(model.index(row), v)
674702

675-
def get_style_option() -> QStyleOptionViewItem:
703+
def get_style_option(row: int) -> QStyleOptionViewItem:
676704
opt = QStyleOptionViewItem()
677-
delegate.initStyleOption(opt, model.index(0))
705+
delegate.initStyleOption(opt, model.index(row))
678706
return opt
679707

680-
set_item({Qt.EditRole: Categorical("a", (), (), False)})
708+
set_item(0, {Qt.EditRole: Categorical("a", (), (), False)})
681709
delegate = VariableEditDelegate()
682-
opt = get_style_option()
710+
opt = get_style_option(0)
683711
self.assertEqual(opt.text, "a")
684712
self.assertFalse(opt.font.italic())
685-
set_item({TransformRole: [Rename("b")]})
686-
opt = get_style_option()
713+
set_item(0, {TransformRole: [Rename("b")]})
714+
opt = get_style_option(0)
687715
self.assertEqual(opt.text, "a \N{RIGHTWARDS ARROW} b")
688716
self.assertTrue(opt.font.italic())
689717

690-
set_item({TransformRole: [AsString()]})
691-
opt = get_style_option()
718+
set_item(0, {TransformRole: [AsString()]})
719+
opt = get_style_option(0)
692720
self.assertIn("reinterpreted", opt.text)
693721
self.assertTrue(opt.font.italic())
722+
set_item(1, {
723+
Qt.EditRole: String("b", (), False),
724+
TransformRole: [Rename("a")]
725+
})
726+
opt = get_style_option(1)
727+
self.assertEqual(opt.palette.color(QPalette.Text), QColor(Qt.red))
728+
view = QListView()
729+
with patch.object(QToolTip, "showText") as p:
730+
delegate.helpEvent(
731+
QHelpEvent(QHelpEvent.ToolTip, QPoint(0, 0), QPoint(0, 0)),
732+
view, opt, model.index(1),
733+
)
734+
p.assert_called_once()
694735

695736

696737
class TestTransforms(TestCase):

0 commit comments

Comments
 (0)