Skip to content

Commit

Permalink
Select Rows: Fix incorrectly stored values in settings
Browse files Browse the repository at this point in the history
  • Loading branch information
janezd committed May 21, 2020
1 parent 56a106f commit 38aadce
Show file tree
Hide file tree
Showing 2 changed files with 223 additions and 77 deletions.
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

0 comments on commit 38aadce

Please sign in to comment.