Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FIX] Select Rows: Fix incorrectly stored values in settings #4798

Merged
merged 1 commit into from
May 22, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
145 changes: 80 additions & 65 deletions Orange/widgets/data/owselectrows.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import enum
from collections import OrderedDict
from itertools import chain

import numpy as np

Expand All @@ -13,16 +12,16 @@
QFontMetrics, QPalette
)
from AnyQt.QtCore import Qt, QPoint, QRegExp, QPersistentModelIndex, QLocale

from Orange.widgets.utils.itemmodels import DomainModel
from orangewidget.utils.combobox import ComboBoxSearch

from Orange.data import (
Variable, ContinuousVariable, DiscreteVariable, StringVariable,
TimeVariable,
ContinuousVariable, DiscreteVariable, StringVariable, TimeVariable,
Table
)
import Orange.data.filter as data_filter
from Orange.data.filter import FilterContinuous, FilterString
from Orange.data.domain import filter_visible
from Orange.data.sql.table import SqlTable
from Orange.preprocess import Remove
from Orange.widgets import widget, gui
Expand Down Expand Up @@ -52,24 +51,49 @@ def encode_setting(self, context, setting, value):
encoded = []
CONTINUOUS = vartype(ContinuousVariable("x"))
for attr, op, values in value:
vtype = context.attributes.get(attr)
if vtype == CONTINUOUS and values and isinstance(values[0], str):
values = [QLocale().toDouble(v)[0] for v in values]
encoded.append((attr, vtype, op, values))
if isinstance(attr, str):
if OWSelectRows.AllTypes.get(attr) == CONTINUOUS:
values = [QLocale().toDouble(v)[0] for v in values]
# None will match the value returned by all_vars.get
encoded.append((attr, None, op, values))
else:
if type(attr) is ContinuousVariable \
and values and isinstance(values[0], str):
values = [QLocale().toDouble(v)[0] for v in values]
elif isinstance(attr, DiscreteVariable):
values = [attr.values[i - 1] if i else "" for i in values]
encoded.append(
(attr.name, context.attributes.get(attr.name), op, values))
return encoded

def decode_setting(self, setting, value, domain=None):
value = super().decode_setting(setting, value, domain)
if setting.name == 'conditions':
CONTINUOUS = vartype(ContinuousVariable("x"))
# Use this after 2022/2/2:
# for i, (attr, _, op, values) in enumerate(value):
for i, condition in enumerate(value):
attr = condition[0]
op, values = condition[-2:]

var = attr in domain and domain[attr]
if var and var.is_continuous and not isinstance(var, TimeVariable):
# for i, (attr, tpe, op, values) in enumerate(value):
# if tpe is not None:
for i, (attr, *tpe, op, values) in enumerate(value):
if tpe != [None] \
or not tpe and attr not in OWSelectRows.AllTypes:
attr = domain[attr]
if type(attr) is ContinuousVariable \
or OWSelectRows.AllTypes.get(attr) == CONTINUOUS:
values = [QLocale().toString(float(i), 'f') for i in values]
elif isinstance(attr, DiscreteVariable):
# After 2022/2/2, use just the expression in else clause
if values and isinstance(values[0], int):
# Backwards compatibility. Reset setting if we detect
# that the number of values decreased. Still broken if
# they're reordered or we don't detect the decrease.
#
# indices start with 1, thus >, not >=
if max(values) > len(attr.values):
values = (0, )
else:
values = tuple(attr.to_val(val) + 1 if val else 0
for val in values if val in attr.values) \
or (0, )
value[i] = (attr, op, values)
return value

Expand All @@ -80,7 +104,7 @@ def match(self, context, domain, attrs, metas):
conditions = context.values["conditions"]
all_vars = attrs.copy()
all_vars.update(metas)
matched = [all_vars.get(name) == tpe
matched = [all_vars.get(name) == tpe # also matches "all (...)" strings
# After 2022/2/2 remove this line:
if len(rest) == 2 else name in all_vars
for name, tpe, *rest in conditions]
Expand All @@ -101,6 +125,8 @@ def filter_value(self, setting, data, domain, attrs, metas):
# if all_vars.get(name) == tpe]
conditions[:] = [
(name, tpe, *rest) for name, tpe, *rest in conditions
# all_vars.get(name) == tpe also matches "all (...)" which are
# encoded with type `None`
if (all_vars.get(name) == tpe if len(rest) == 2
else name in all_vars)]

Expand Down Expand Up @@ -209,6 +235,9 @@ def __init__(self):
self.last_output_conditions = None
self.data = None
self.data_desc = self.match_desc = self.nonmatch_desc = None
self.variable_model = DomainModel(
[list(self.AllTypes), DomainModel.Separator,
DomainModel.CLASSES, DomainModel.ATTRIBUTES, DomainModel.METAS])

box = gui.vBox(self.controlArea, 'Conditions', stretch=100)
self.cond_list = QTableWidget(
Expand Down Expand Up @@ -268,18 +297,11 @@ def add_row(self, attr=None, condition_type=None, condition_value=None):
attr_combo = ComboBoxSearch(
minimumContentsLength=12,
sizeAdjustPolicy=QComboBox.AdjustToMinimumContentsLengthWithIcon)
attr_combo.setModel(self.variable_model)
attr_combo.row = row
for var in self._visible_variables(self.data.domain):
if isinstance(var, Variable):
attr_combo.addItem(*gui.attributeItem(var))
else:
attr_combo.addItem(var)
if isinstance(attr, str):
attr_combo.setCurrentText(attr)
else:
attr_combo.setCurrentIndex(
attr or
len(self.AllTypes) - (attr_combo.count() == len(self.AllTypes)))
attr_combo.setCurrentIndex(self.variable_model.indexOf(attr) if attr
else len(self.AllTypes) + 1)

self.cond_list.setCellWidget(row, 0, attr_combo)

index = QPersistentModelIndex(model.index(row, 3))
Expand All @@ -297,15 +319,6 @@ def add_row(self, attr=None, condition_type=None, condition_value=None):

self.cond_list.resizeRowToContents(row)

@classmethod
def _visible_variables(cls, domain):
"""Generate variables in order they should be presented in in combos."""
return chain(
cls.AllTypes,
filter_visible(chain(domain.class_vars,
domain.metas,
domain.attributes)))

def add_all(self):
if self.cond_list.rowCount():
Mb = QMessageBox
Expand All @@ -315,9 +328,9 @@ def add_all(self):
"filters for all variables.", Mb.Ok | Mb.Cancel) != Mb.Ok:
return
self.remove_all()
domain = self.data.domain
for i in range(len(domain.variables) + len(domain.metas)):
self.add_row(i)
for attr in self.variable_model[len(self.AllTypes) + 1:]:
self.add_row(attr)
self.conditions_changed()

def remove_one(self, rownum):
self.remove_one_row(rownum)
Expand All @@ -333,6 +346,12 @@ def remove_one_row(self, rownum):
self.remove_all_button.setDisabled(True)

def remove_all_rows(self):
# Disconnect signals to avoid stray emits when changing variable_model
for row in range(self.cond_list.rowCount()):
for col in (0, 1):
widget = self.cond_list.cellWidget(row, col)
if widget:
widget.currentIndexChanged.disconnect()
self.cond_list.clear()
self.cond_list.setRowCount(0)
self.remove_all_button.setDisabled(True)
Expand Down Expand Up @@ -495,24 +514,18 @@ def set_data(self, data):
if not data:
self.info.set_input_summary(self.info.NoInput)
self.data_desc = None
self.variable_model.set_domain(None)
self.commit()
return
self.data_desc = report.describe_data_brief(data)
self.conditions = []
try:
self.openContext(data)
except Exception:
pass
self.variable_model.set_domain(data.domain)

variables = list(self._visible_variables(self.data.domain))
varnames = [v.name if isinstance(v, Variable) else v for v in variables]
if self.conditions:
for attr, cond_type, cond_value in self.conditions:
if attr in varnames:
self.add_row(varnames.index(attr), cond_type, cond_value)
elif attr in self.AllTypes:
self.add_row(attr, cond_type, cond_value)
else:
self.conditions = []
self.openContext(data)
for attr, cond_type, cond_value in self.conditions:
if attr in self.variable_model:
self.add_row(attr, cond_type, cond_value)
if not self.cond_list.model().rowCount():
self.add_row()

self.info.set_input_summary(data.approx_len(),
Expand All @@ -521,12 +534,15 @@ def set_data(self, data):

def conditions_changed(self):
try:
self.conditions = []
cells_by_rows = (
[self.cond_list.cellWidget(row, col) for col in range(3)]
for row in range(self.cond_list.rowCount())
)
self.conditions = [
(self.cond_list.cellWidget(row, 0).currentText(),
self.cond_list.cellWidget(row, 1).currentIndex(),
self._get_value_contents(self.cond_list.cellWidget(row, 2)))
for row in range(self.cond_list.rowCount())]
(var_cell.currentData(gui.TableVariable) or var_cell.currentText(),
oper_cell.currentIndex(),
self._get_value_contents(val_cell))
for var_cell, oper_cell, val_cell in cells_by_rows]
if self.update_on_change and (
self.last_output_conditions is None or
self.last_output_conditions != self.conditions):
Expand Down Expand Up @@ -674,19 +690,18 @@ def send_report(self):
pdesc = ndesc

conditions = []
domain = self.data.domain
for attr_name, oper, values in self.conditions:
if attr_name in self.AllTypes:
attr = attr_name
for attr, oper, values in self.conditions:
if isinstance(attr, str):
attr_name = attr
var_type = self.AllTypes[attr]
names = self.operator_names[attr_name]
var_type = self.AllTypes[attr_name]
else:
attr = domain[attr_name]
attr_name = attr.name
var_type = vartype(attr)
names = self.operator_names[type(attr)]
name = names[oper]
if oper == len(names) - 1:
conditions.append("{} {}".format(attr, name))
conditions.append("{} {}".format(attr_name, name))
elif var_type == 1: # discrete
if name == "is one of":
valnames = [attr.values[v - 1] for v in values]
Expand Down
Loading