def setUp(self): self.domain = Domain( attributes=[ ContinuousVariable("c1"), DiscreteVariable("d1", values="abc"), DiscreteVariable("d2", values="def"), ], class_vars=[DiscreteVariable("d3", values="ghi")], metas=[ ContinuousVariable("c2"), DiscreteVariable("d4", values="jkl") ], ) self.args = ( self.domain, { "c1": Continuous, "d1": Discrete, "d2": Discrete, "d3": Discrete }, { "c2": Continuous, "d4": Discrete }, ) self.handler = SelectAttributesDomainContextHandler() self.handler.read_defaults = lambda: None
def setUp(self): self.domain = Domain( attributes=[ContinuousVariable('c1'), DiscreteVariable('d1', values='abc'), DiscreteVariable('d2', values='def')], class_vars=[DiscreteVariable('d3', values='ghi')], metas=[ContinuousVariable('c2'), DiscreteVariable('d4', values='jkl')] ) self.args = (self.domain, {'c1': Continuous, 'd1': Discrete, 'd2': Discrete, 'd3': Discrete}, {'c2': Continuous, 'd4': Discrete, }) self.handler = SelectAttributesDomainContextHandler(metas_in_res=True) self.handler.read_defaults = lambda: None
def setUp(self): self.domain = Domain( attributes=[ContinuousVariable('c1'), DiscreteVariable('d1', values='abc'), DiscreteVariable('d2', values='def')], class_vars=[DiscreteVariable('d3', values='ghi')], metas=[ContinuousVariable('c2'), DiscreteVariable('d4', values='jkl')] ) self.args = (self.domain, {'c1': Continuous, 'd1': Discrete, 'd2': Discrete, 'd3': Discrete}, {'c2': Continuous, 'd4': Discrete, }) self.handler = SelectAttributesDomainContextHandler() self.handler.read_defaults = lambda: None
class OWSelectAttributes(widget.OWWidget): # pylint: disable=too-many-instance-attributes name = "Select Columns" description = "Select columns from the data table and assign them to " \ "data features, classes or meta variables." icon = "icons/SelectColumns.svg" priority = 100 keywords = ["filter"] class Inputs: data = Input("Data", Table, default=True) features = Input("Features", AttributeList) class Outputs: data = Output("Data", Table) features = Output("Features", AttributeList, dynamic=False) want_main_area = False want_control_area = True settingsHandler = SelectAttributesDomainContextHandler() domain_role_hints = ContextSetting({}) use_input_features = Setting(False) auto_commit = Setting(True) class Warning(widget.OWWidget.Warning): mismatching_domain = Msg("Features and data domain do not match") def __init__(self): super().__init__() self.data = None self.features = None # Schedule interface updates (enabled buttons) using a coalescing # single shot timer (complex interactions on selection and filtering # updates in the 'available_attrs_view') self.__interface_update_timer = QTimer(self, interval=0, singleShot=True) self.__interface_update_timer.timeout.connect( self.__update_interface_state) # The last view that has the selection for move operation's source self.__last_active_view = None # type: Optional[QListView] def update_on_change(view): # Schedule interface state update on selection change in `view` self.__last_active_view = view self.__interface_update_timer.start() self.controlArea = QWidget(self.controlArea) self.layout().addWidget(self.controlArea) layout = QGridLayout() self.controlArea.setLayout(layout) layout.setContentsMargins(4, 4, 4, 4) box = gui.vBox(self.controlArea, "Available Variables", addToLayout=False) self.available_attrs = VariablesListItemModel() filter_edit, self.available_attrs_view = variables_filter( parent=self, model=self.available_attrs) box.layout().addWidget(filter_edit) def dropcompleted(action): if action == Qt.MoveAction: self.commit() self.available_attrs_view.selectionModel().selectionChanged.connect( partial(update_on_change, self.available_attrs_view)) self.available_attrs_view.dragDropActionDidComplete.connect( dropcompleted) box.layout().addWidget(self.available_attrs_view) layout.addWidget(box, 0, 0, 3, 1) box = gui.vBox(self.controlArea, "Features", addToLayout=False) self.used_attrs = VariablesListItemModel() filter_edit, self.used_attrs_view = variables_filter( parent=self, model=self.used_attrs, accepted_type=(Orange.data.DiscreteVariable, Orange.data.ContinuousVariable)) self.used_attrs.rowsInserted.connect(self.__used_attrs_changed) self.used_attrs.rowsRemoved.connect(self.__used_attrs_changed) self.used_attrs_view.selectionModel().selectionChanged.connect( partial(update_on_change, self.used_attrs_view)) self.used_attrs_view.dragDropActionDidComplete.connect(dropcompleted) self.use_features_box = gui.auto_commit( self.controlArea, self, "use_input_features", "Use input features", "Always use input features", box=False, commit=self.__use_features_clicked, callback=self.__use_features_changed, addToLayout=False) self.enable_use_features_box() box.layout().addWidget(self.use_features_box) box.layout().addWidget(filter_edit) box.layout().addWidget(self.used_attrs_view) layout.addWidget(box, 0, 2, 1, 1) box = gui.vBox(self.controlArea, "Target Variable", addToLayout=False) self.class_attrs = VariablesListItemModel() self.class_attrs_view = VariablesListItemView( acceptedType=(Orange.data.DiscreteVariable, Orange.data.ContinuousVariable)) self.class_attrs_view.setModel(self.class_attrs) self.class_attrs_view.selectionModel().selectionChanged.connect( partial(update_on_change, self.class_attrs_view)) self.class_attrs_view.dragDropActionDidComplete.connect(dropcompleted) self.class_attrs_view.setMaximumHeight(72) box.layout().addWidget(self.class_attrs_view) layout.addWidget(box, 1, 2, 1, 1) box = gui.vBox(self.controlArea, "Meta Attributes", addToLayout=False) self.meta_attrs = VariablesListItemModel() self.meta_attrs_view = VariablesListItemView( acceptedType=Orange.data.Variable) self.meta_attrs_view.setModel(self.meta_attrs) self.meta_attrs_view.selectionModel().selectionChanged.connect( partial(update_on_change, self.meta_attrs_view)) self.meta_attrs_view.dragDropActionDidComplete.connect(dropcompleted) box.layout().addWidget(self.meta_attrs_view) layout.addWidget(box, 2, 2, 1, 1) bbox = gui.vBox(self.controlArea, addToLayout=False, margin=0) layout.addWidget(bbox, 0, 1, 1, 1) self.up_attr_button = gui.button(bbox, self, "Up", callback=partial( self.move_up, self.used_attrs_view)) self.move_attr_button = gui.button(bbox, self, ">", callback=partial( self.move_selected, self.used_attrs_view)) self.down_attr_button = gui.button(bbox, self, "Down", callback=partial( self.move_down, self.used_attrs_view)) bbox = gui.vBox(self.controlArea, addToLayout=False, margin=0) layout.addWidget(bbox, 1, 1, 1, 1) self.up_class_button = gui.button(bbox, self, "Up", callback=partial( self.move_up, self.class_attrs_view)) self.move_class_button = gui.button(bbox, self, ">", callback=partial( self.move_selected, self.class_attrs_view)) self.down_class_button = gui.button(bbox, self, "Down", callback=partial( self.move_down, self.class_attrs_view)) bbox = gui.vBox(self.controlArea, addToLayout=False, margin=0) layout.addWidget(bbox, 2, 1, 1, 1) self.up_meta_button = gui.button(bbox, self, "Up", callback=partial( self.move_up, self.meta_attrs_view)) self.move_meta_button = gui.button(bbox, self, ">", callback=partial( self.move_selected, self.meta_attrs_view)) self.down_meta_button = gui.button(bbox, self, "Down", callback=partial( self.move_down, self.meta_attrs_view)) autobox = gui.auto_commit(None, self, "auto_commit", "Send") layout.addWidget(autobox, 3, 0, 1, 3) reset = gui.button(None, self, "Reset", callback=self.reset, width=120) autobox.layout().insertWidget(0, reset) autobox.layout().insertStretch(1, 20) layout.setRowStretch(0, 4) layout.setRowStretch(1, 0) layout.setRowStretch(2, 2) layout.setHorizontalSpacing(0) self.controlArea.setLayout(layout) self.output_data = None self.original_completer_items = [] self.resize(600, 600) @property def features_from_data_attributes(self): if self.data is None or self.features is None: return [] domain = self.data.domain return [ domain[feature.name] for feature in self.features if feature.name in domain and domain[feature.name] in domain.attributes ] def can_use_features(self): return bool(self.features_from_data_attributes) and \ self.features_from_data_attributes != self.used_attrs[:] def __use_features_changed(self): # Use input features check box # Needs a check since callback is invoked before object is created if not hasattr(self, "use_features_box"): return self.enable_used_attrs(not self.use_input_features) if self.use_input_features and self.can_use_features(): self.use_features() if not self.use_input_features: self.enable_use_features_box() def __use_features_clicked(self): # Use input features button self.use_features() def __used_attrs_changed(self): self.enable_use_features_box() @Inputs.data def set_data(self, data=None): self.update_domain_role_hints() self.closeContext() self.data = data if data is not None: self.openContext(data) all_vars = data.domain.variables + data.domain.metas var_sig = lambda attr: (attr.name, vartype(attr)) domain_hints = { var_sig(attr): ("attribute", i) for i, attr in enumerate(data.domain.attributes) } domain_hints.update({ var_sig(attr): ("meta", i) for i, attr in enumerate(data.domain.metas) }) if data.domain.class_vars: domain_hints.update({ var_sig(attr): ("class", i) for i, attr in enumerate(data.domain.class_vars) }) # update the hints from context settings domain_hints.update(self.domain_role_hints) attrs_for_role = lambda role: [ (domain_hints[var_sig(attr)][1], attr) for attr in all_vars if domain_hints[var_sig(attr)][0] == role ] attributes = [ attr for place, attr in sorted(attrs_for_role("attribute"), key=lambda a: a[0]) ] classes = [ attr for place, attr in sorted(attrs_for_role("class"), key=lambda a: a[0]) ] metas = [ attr for place, attr in sorted(attrs_for_role("meta"), key=lambda a: a[0]) ] available = [ attr for place, attr in sorted(attrs_for_role("available"), key=lambda a: a[0]) ] self.used_attrs[:] = attributes self.class_attrs[:] = classes self.meta_attrs[:] = metas self.available_attrs[:] = available else: self.used_attrs[:] = [] self.class_attrs[:] = [] self.meta_attrs[:] = [] self.available_attrs[:] = [] def update_domain_role_hints(self): """ Update the domain hints to be stored in the widgets settings. """ hints_from_model = lambda role, model: [( (attr.name, vartype(attr)), (role, i)) for i, attr in enumerate(model)] hints = dict(hints_from_model("available", self.available_attrs)) hints.update(hints_from_model("attribute", self.used_attrs)) hints.update(hints_from_model("class", self.class_attrs)) hints.update(hints_from_model("meta", self.meta_attrs)) self.domain_role_hints = hints @Inputs.features def set_features(self, features): self.features = features def handleNewSignals(self): self.check_data() self.enable_used_attrs() self.enable_use_features_box() if self.use_input_features and self.features_from_data_attributes: self.enable_used_attrs(False) self.use_features() self.unconditional_commit() def check_data(self): self.Warning.mismatching_domain.clear() if self.data is not None and self.features is not None and \ not self.features_from_data_attributes: self.Warning.mismatching_domain() def enable_used_attrs(self, enable=True): self.up_attr_button.setEnabled(enable) self.move_attr_button.setEnabled(enable) self.down_attr_button.setEnabled(enable) self.used_attrs_view.setEnabled(enable) self.used_attrs_view.repaint() def enable_use_features_box(self): self.use_features_box.button.setEnabled(self.can_use_features()) enable_checkbox = bool(self.features_from_data_attributes) self.use_features_box.setHidden(not enable_checkbox) self.use_features_box.repaint() def use_features(self): attributes = self.features_from_data_attributes available, used = self.available_attrs[:], self.used_attrs[:] self.available_attrs[:] = [ attr for attr in used + available if attr not in attributes ] self.used_attrs[:] = attributes self.commit() @staticmethod def selected_rows(view): """ Return the selected rows in the view. """ rows = view.selectionModel().selectedRows() model = view.model() if isinstance(model, QSortFilterProxyModel): rows = [model.mapToSource(r) for r in rows] return [r.row() for r in rows] def move_rows(self, view, rows, offset): model = view.model() newrows = [min(max(0, row + offset), len(model) - 1) for row in rows] for row, newrow in sorted(zip(rows, newrows), reverse=offset > 0): model[row], model[newrow] = model[newrow], model[row] selection = QItemSelection() for nrow in newrows: index = model.index(nrow, 0) selection.select(index, index) view.selectionModel().select(selection, QItemSelectionModel.ClearAndSelect) self.commit() def move_up(self, view): selected = self.selected_rows(view) self.move_rows(view, selected, -1) def move_down(self, view): selected = self.selected_rows(view) self.move_rows(view, selected, 1) def move_selected(self, view): if self.selected_rows(view): self.move_selected_from_to(view, self.available_attrs_view) elif self.selected_rows(self.available_attrs_view): self.move_selected_from_to(self.available_attrs_view, view) def move_selected_from_to(self, src, dst): self.move_from_to(src, dst, self.selected_rows(src)) def move_from_to(self, src, dst, rows): src_model = source_model(src) attrs = [src_model[r] for r in rows] for s1, s2 in reversed(list(slices(rows))): del src_model[s1:s2] dst_model = source_model(dst) dst_model.extend(attrs) self.commit() def __update_interface_state(self): last_view = self.__last_active_view if last_view is not None: self.update_interface_state(last_view) def update_interface_state(self, focus=None): for view in [ self.available_attrs_view, self.used_attrs_view, self.class_attrs_view, self.meta_attrs_view ]: if view is not focus and not view.hasFocus() \ and view.selectionModel().hasSelection(): view.selectionModel().clear() def selected_vars(view): model = source_model(view) return [model[i] for i in self.selected_rows(view)] available_selected = selected_vars(self.available_attrs_view) attrs_selected = selected_vars(self.used_attrs_view) class_selected = selected_vars(self.class_attrs_view) meta_selected = selected_vars(self.meta_attrs_view) available_types = set(map(type, available_selected)) all_primitive = all(var.is_primitive() for var in available_types) move_attr_enabled = \ ((available_selected and all_primitive) or attrs_selected) and \ self.used_attrs_view.isEnabled() self.move_attr_button.setEnabled(bool(move_attr_enabled)) if move_attr_enabled: self.move_attr_button.setText(">" if available_selected else "<") move_class_enabled = bool(all_primitive and available_selected) or class_selected self.move_class_button.setEnabled(bool(move_class_enabled)) if move_class_enabled: self.move_class_button.setText(">" if available_selected else "<") move_meta_enabled = available_selected or meta_selected self.move_meta_button.setEnabled(bool(move_meta_enabled)) if move_meta_enabled: self.move_meta_button.setText(">" if available_selected else "<") self.__last_active_view = None self.__interface_update_timer.stop() def commit(self): self.update_domain_role_hints() if self.data is not None: attributes = list(self.used_attrs) class_var = list(self.class_attrs) metas = list(self.meta_attrs) domain = Orange.data.Domain(attributes, class_var, metas) newdata = self.data.transform(domain) self.output_data = newdata self.Outputs.data.send(newdata) self.Outputs.features.send(AttributeList(attributes)) else: self.output_data = None self.Outputs.data.send(None) self.Outputs.features.send(None) def reset(self): self.enable_used_attrs() self.use_features_box.checkbox.setChecked(False) if self.data is not None: self.available_attrs[:] = [] self.used_attrs[:] = self.data.domain.attributes self.class_attrs[:] = self.data.domain.class_vars self.meta_attrs[:] = self.data.domain.metas self.update_domain_role_hints() self.commit() def send_report(self): if not self.data or not self.output_data: return in_domain, out_domain = self.data.domain, self.output_data.domain self.report_domain("Input data", self.data.domain) if (in_domain.attributes, in_domain.class_vars, in_domain.metas) == (out_domain.attributes, out_domain.class_vars, out_domain.metas): self.report_paragraph("Output data", "No changes.") else: self.report_domain("Output data", self.output_data.domain) diff = list( set(in_domain.variables + in_domain.metas) - set(out_domain.variables + out_domain.metas)) if diff: text = "%i (%s)" % (len(diff), ", ".join(x.name for x in diff)) self.report_items((("Removed", text), ))
class OWSelectAttributes(widget.OWWidget): name = "Select Columns" description = "Select columns from the data table and assign them to " \ "data features, classes or meta variables." icon = "icons/SelectColumns.svg" priority = 100 class Inputs: data = Input("Data", Table) class Outputs: data = Output("Data", Table) features = Output("Features", widget.AttributeList, dynamic=False) want_main_area = False want_control_area = True settingsHandler = SelectAttributesDomainContextHandler() domain_role_hints = ContextSetting({}) auto_commit = Setting(True) def __init__(self): super().__init__() self.controlArea = QWidget(self.controlArea) self.layout().addWidget(self.controlArea) layout = QGridLayout() self.controlArea.setLayout(layout) layout.setContentsMargins(4, 4, 4, 4) box = gui.vBox(self.controlArea, "Available Variables", addToLayout=False) self.filter_edit = QLineEdit() self.filter_edit.setToolTip("Filter the list of available variables.") box.layout().addWidget(self.filter_edit) if hasattr(self.filter_edit, "setPlaceholderText"): self.filter_edit.setPlaceholderText("Filter") self.completer = QCompleter() self.completer.setCompletionMode(QCompleter.InlineCompletion) self.completer_model = QStringListModel() self.completer.setModel(self.completer_model) self.completer.setModelSorting(QCompleter.CaseSensitivelySortedModel) self.filter_edit.setCompleter(self.completer) self.completer_navigator = CompleterNavigator(self) self.filter_edit.installEventFilter(self.completer_navigator) def dropcompleted(action): if action == Qt.MoveAction: self.commit() self.available_attrs = VariableListModel(enable_dnd=True) self.available_attrs_proxy = VariableFilterProxyModel() self.available_attrs_proxy.setSourceModel(self.available_attrs) self.available_attrs_view = VariablesListItemView( acceptedType=Orange.data.Variable) self.available_attrs_view.setModel(self.available_attrs_proxy) aa = self.available_attrs aa.dataChanged.connect(self.update_completer_model) aa.rowsInserted.connect(self.update_completer_model) aa.rowsRemoved.connect(self.update_completer_model) self.available_attrs_view.selectionModel().selectionChanged.connect( partial(self.update_interface_state, self.available_attrs_view)) self.available_attrs_view.dragDropActionDidComplete.connect( dropcompleted) self.filter_edit.textChanged.connect(self.update_completer_prefix) self.filter_edit.textChanged.connect( self.available_attrs_proxy.set_filter_string) box.layout().addWidget(self.available_attrs_view) layout.addWidget(box, 0, 0, 3, 1) box = gui.vBox(self.controlArea, "Features", addToLayout=False) self.used_attrs = VariableListModel(enable_dnd=True) self.used_attrs_view = VariablesListItemView( acceptedType=(Orange.data.DiscreteVariable, Orange.data.ContinuousVariable)) self.used_attrs_view.setModel(self.used_attrs) self.used_attrs_view.selectionModel().selectionChanged.connect( partial(self.update_interface_state, self.used_attrs_view)) self.used_attrs_view.dragDropActionDidComplete.connect(dropcompleted) box.layout().addWidget(self.used_attrs_view) layout.addWidget(box, 0, 2, 1, 1) box = gui.vBox(self.controlArea, "Target Variable", addToLayout=False) self.class_attrs = ClassVarListItemModel(enable_dnd=True) self.class_attrs_view = ClassVariableItemView( acceptedType=(Orange.data.DiscreteVariable, Orange.data.ContinuousVariable)) self.class_attrs_view.setModel(self.class_attrs) self.class_attrs_view.selectionModel().selectionChanged.connect( partial(self.update_interface_state, self.class_attrs_view)) self.class_attrs_view.dragDropActionDidComplete.connect(dropcompleted) self.class_attrs_view.setMaximumHeight(72) box.layout().addWidget(self.class_attrs_view) layout.addWidget(box, 1, 2, 1, 1) box = gui.vBox(self.controlArea, "Meta Attributes", addToLayout=False) self.meta_attrs = VariableListModel(enable_dnd=True) self.meta_attrs_view = VariablesListItemView( acceptedType=Orange.data.Variable) self.meta_attrs_view.setModel(self.meta_attrs) self.meta_attrs_view.selectionModel().selectionChanged.connect( partial(self.update_interface_state, self.meta_attrs_view)) self.meta_attrs_view.dragDropActionDidComplete.connect(dropcompleted) box.layout().addWidget(self.meta_attrs_view) layout.addWidget(box, 2, 2, 1, 1) bbox = gui.vBox(self.controlArea, addToLayout=False, margin=0) layout.addWidget(bbox, 0, 1, 1, 1) self.up_attr_button = gui.button(bbox, self, "Up", callback=partial( self.move_up, self.used_attrs_view)) self.move_attr_button = gui.button(bbox, self, ">", callback=partial( self.move_selected, self.used_attrs_view)) self.down_attr_button = gui.button(bbox, self, "Down", callback=partial( self.move_down, self.used_attrs_view)) bbox = gui.vBox(self.controlArea, addToLayout=False, margin=0) layout.addWidget(bbox, 1, 1, 1, 1) self.up_class_button = gui.button(bbox, self, "Up", callback=partial( self.move_up, self.class_attrs_view)) self.move_class_button = gui.button(bbox, self, ">", callback=partial( self.move_selected, self.class_attrs_view, exclusive=False)) self.down_class_button = gui.button(bbox, self, "Down", callback=partial( self.move_down, self.class_attrs_view)) bbox = gui.vBox(self.controlArea, addToLayout=False, margin=0) layout.addWidget(bbox, 2, 1, 1, 1) self.up_meta_button = gui.button(bbox, self, "Up", callback=partial( self.move_up, self.meta_attrs_view)) self.move_meta_button = gui.button(bbox, self, ">", callback=partial( self.move_selected, self.meta_attrs_view)) self.down_meta_button = gui.button(bbox, self, "Down", callback=partial( self.move_down, self.meta_attrs_view)) autobox = gui.auto_commit(None, self, "auto_commit", "Send") layout.addWidget(autobox, 3, 0, 1, 3) reset = gui.button(None, self, "Reset", callback=self.reset) autobox.layout().insertWidget(0, self.report_button) autobox.layout().insertWidget(1, reset) autobox.layout().insertSpacing(2, 10) reset.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) self.report_button.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) layout.setRowStretch(0, 4) layout.setRowStretch(1, 0) layout.setRowStretch(2, 2) layout.setHorizontalSpacing(0) self.controlArea.setLayout(layout) self.data = None self.output_data = None self.original_completer_items = [] self.resize(500, 600) @Inputs.data def set_data(self, data=None): self.update_domain_role_hints() self.closeContext() self.data = data if data is not None: self.openContext(data) all_vars = data.domain.variables + data.domain.metas var_sig = lambda attr: (attr.name, vartype(attr)) domain_hints = { var_sig(attr): ("attribute", i) for i, attr in enumerate(data.domain.attributes) } domain_hints.update({ var_sig(attr): ("meta", i) for i, attr in enumerate(data.domain.metas) }) if data.domain.class_vars: domain_hints.update({ var_sig(attr): ("class", i) for i, attr in enumerate(data.domain.class_vars) }) # update the hints from context settings domain_hints.update(self.domain_role_hints) attrs_for_role = lambda role: [ (domain_hints[var_sig(attr)][1], attr) for attr in all_vars if domain_hints[var_sig(attr)][0] == role ] attributes = [ attr for place, attr in sorted(attrs_for_role("attribute"), key=lambda a: a[0]) ] classes = [ attr for place, attr in sorted(attrs_for_role("class"), key=lambda a: a[0]) ] metas = [ attr for place, attr in sorted(attrs_for_role("meta"), key=lambda a: a[0]) ] available = [ attr for place, attr in sorted(attrs_for_role("available"), key=lambda a: a[0]) ] self.used_attrs[:] = attributes self.class_attrs[:] = classes self.meta_attrs[:] = metas self.available_attrs[:] = available else: self.used_attrs[:] = [] self.class_attrs[:] = [] self.meta_attrs[:] = [] self.available_attrs[:] = [] self.unconditional_commit() def update_domain_role_hints(self): """ Update the domain hints to be stored in the widgets settings. """ hints_from_model = lambda role, model: [( (attr.name, vartype(attr)), (role, i)) for i, attr in enumerate(model)] hints = dict(hints_from_model("available", self.available_attrs)) hints.update(hints_from_model("attribute", self.used_attrs)) hints.update(hints_from_model("class", self.class_attrs)) hints.update(hints_from_model("meta", self.meta_attrs)) self.domain_role_hints = hints def selected_rows(self, view): """ Return the selected rows in the view. """ rows = view.selectionModel().selectedRows() model = view.model() if isinstance(model, QSortFilterProxyModel): rows = [model.mapToSource(r) for r in rows] return [r.row() for r in rows] def move_rows(self, view, rows, offset): model = view.model() newrows = [min(max(0, row + offset), len(model) - 1) for row in rows] for row, newrow in sorted(zip(rows, newrows), reverse=offset > 0): model[row], model[newrow] = model[newrow], model[row] selection = QItemSelection() for nrow in newrows: index = model.index(nrow, 0) selection.select(index, index) view.selectionModel().select(selection, QItemSelectionModel.ClearAndSelect) self.commit() def move_up(self, view): selected = self.selected_rows(view) self.move_rows(view, selected, -1) def move_down(self, view): selected = self.selected_rows(view) self.move_rows(view, selected, 1) def move_selected(self, view, exclusive=False): if self.selected_rows(view): self.move_selected_from_to(view, self.available_attrs_view) elif self.selected_rows(self.available_attrs_view): self.move_selected_from_to(self.available_attrs_view, view, exclusive) def move_selected_from_to(self, src, dst, exclusive=False): self.move_from_to(src, dst, self.selected_rows(src), exclusive) def move_from_to(self, src, dst, rows, exclusive=False): src_model = source_model(src) attrs = [src_model[r] for r in rows] for s1, s2 in reversed(list(slices(rows))): del src_model[s1:s2] dst_model = source_model(dst) dst_model.extend(attrs) self.commit() def update_interface_state(self, focus=None, selected=None, deselected=None): for view in [ self.available_attrs_view, self.used_attrs_view, self.class_attrs_view, self.meta_attrs_view ]: if view is not focus and not view.hasFocus( ) and self.selected_rows(view): view.selectionModel().clear() def selected_vars(view): model = source_model(view) return [model[i] for i in self.selected_rows(view)] available_selected = selected_vars(self.available_attrs_view) attrs_selected = selected_vars(self.used_attrs_view) class_selected = selected_vars(self.class_attrs_view) meta_selected = selected_vars(self.meta_attrs_view) available_types = set(map(type, available_selected)) all_primitive = all(var.is_primitive() for var in available_types) move_attr_enabled = (available_selected and all_primitive) or \ attrs_selected self.move_attr_button.setEnabled(bool(move_attr_enabled)) if move_attr_enabled: self.move_attr_button.setText(">" if available_selected else "<") move_class_enabled = (all_primitive and available_selected) or class_selected self.move_class_button.setEnabled(bool(move_class_enabled)) if move_class_enabled: self.move_class_button.setText(">" if available_selected else "<") move_meta_enabled = available_selected or meta_selected self.move_meta_button.setEnabled(bool(move_meta_enabled)) if move_meta_enabled: self.move_meta_button.setText(">" if available_selected else "<") def update_completer_model(self, *_): """ This gets called when the model for available attributes changes through either drag/drop or the left/right button actions. """ vars = list(self.available_attrs) items = [var.name for var in vars] items += [ "%s=%s" % item for v in vars for item in v.attributes.items() ] new = sorted(set(items)) if new != self.original_completer_items: self.original_completer_items = new self.completer_model.setStringList(self.original_completer_items) def update_completer_prefix(self, filter): """ Prefixes all items in the completer model with the current already done completion to enable the completion of multiple keywords. """ prefix = str(self.completer.completionPrefix()) if not prefix.endswith(" ") and " " in prefix: prefix, _ = prefix.rsplit(" ", 1) items = [ prefix + " " + item for item in self.original_completer_items ] else: items = self.original_completer_items old = list(map(str, self.completer_model.stringList())) if set(old) != set(items): self.completer_model.setStringList(items) def commit(self): self.update_domain_role_hints() if self.data is not None: attributes = list(self.used_attrs) class_var = list(self.class_attrs) metas = list(self.meta_attrs) domain = Orange.data.Domain(attributes, class_var, metas) newdata = self.data.transform(domain) self.output_data = newdata self.Outputs.data.send(newdata) self.Outputs.features.send(widget.AttributeList(attributes)) else: self.output_data = None self.Outputs.data.send(None) self.Outputs.features.send(None) def reset(self): if self.data is not None: self.available_attrs[:] = [] self.used_attrs[:] = self.data.domain.attributes self.class_attrs[:] = self.data.domain.class_vars self.meta_attrs[:] = self.data.domain.metas self.update_domain_role_hints() self.commit() def send_report(self): if not self.data or not self.output_data: return in_domain, out_domain = self.data.domain, self.output_data.domain self.report_domain("Input data", self.data.domain) if (in_domain.attributes, in_domain.class_vars, in_domain.metas) == (out_domain.attributes, out_domain.class_vars, out_domain.metas): self.report_paragraph("Output data", "No changes.") else: self.report_domain("Output data", self.output_data.domain) diff = list( set(in_domain.variables + in_domain.metas) - set(out_domain.variables + out_domain.metas)) if diff: text = "%i (%s)" % (len(diff), ", ".join(x.name for x in diff)) self.report_items((("Removed", text), ))
class TestSelectAttributesDomainContextHandler(TestCase): def setUp(self): self.domain = Domain(attributes=[ ContinuousVariable('c1'), DiscreteVariable('d1', values='abc'), DiscreteVariable('d2', values='def') ], class_vars=[DiscreteVariable('d3', values='ghi')], metas=[ ContinuousVariable('c2'), DiscreteVariable('d4', values='jkl') ]) self.args = (self.domain, { 'c1': Continuous, 'd1': Discrete, 'd2': Discrete, 'd3': Discrete }, { 'c2': Continuous, 'd4': Discrete, }) self.handler = SelectAttributesDomainContextHandler() self.handler.read_defaults = lambda: None def test_open_context(self): self.handler.bind(SimpleWidget) context = Mock(attributes=self.args[1], metas=self.args[2], values=dict(domain_role_hints=({ ('d1', Discrete): ('available', 0), ('d2', Discrete): ('meta', 0), ('c1', Continuous): ('attribute', 0), ('d3', Discrete): ('attribute', 1), ('d4', Discrete): ('attribute', 2), ('c2', Continuous): ('class', 0) }, -2), with_metas=[('d1', Discrete), ('d2', Discrete)])) self.handler.global_contexts = \ [Mock(values={}), context, Mock(values={})] widget = SimpleWidget() self.handler.initialize(widget) self.handler.open_context(widget, self.args[0]) self.assertEqual( widget.domain_role_hints, { ('d1', Discrete): ('available', 0), ('d2', Discrete): ('meta', 0), ('c1', Continuous): ('attribute', 0), ('d3', Discrete): ('attribute', 1), ('d4', Discrete): ('attribute', 2), ('c2', Continuous): ('class', 0) }) def test_open_context_with_imperfect_match(self): self.handler.bind(SimpleWidget) context = Mock(values=dict(domain_role_hints=({ ('d1', Discrete): ('available', 0), ('d2', Discrete): ('meta', 0), ('c1', Continuous): ('attribute', 0), ('d6', Discrete): ('attribute', 1), ('d7', Discrete): ('attribute', 2), ('c2', Continuous): ('class', 0) }, -2))) self.handler.global_contexts = \ [Mock(values={}), context, Mock(values={})] widget = SimpleWidget() self.handler.initialize(widget) self.handler.open_context(widget, self.args[0]) self.assertEqual( widget.domain_role_hints, { ('d1', Discrete): ('available', 0), ('d2', Discrete): ('meta', 0), ('c1', Continuous): ('attribute', 0), ('c2', Continuous): ('class', 0) }) def test_open_context_with_no_match(self): self.handler.bind(SimpleWidget) context = Mock(values=dict( domain_role_hints=({ ('d1', Discrete): ('available', 0), ('d2', Discrete): ('meta', 0), ('c1', Continuous): ('attribute', 0), ('d3', Discrete): ('attribute', 1), ('d4', Discrete): ('attribute', 2), ('c2', Continuous): ('class', 0) }, -2), required=('g1', Continuous), )) self.handler.global_contexts = [context] widget = SimpleWidget() self.handler.initialize(widget) self.handler.open_context(widget, self.args[0]) self.assertEqual(widget.domain_role_hints, {})
class TestSelectAttributesDomainContextHandler(TestCase): def setUp(self): self.domain = Domain( attributes=[ContinuousVariable('c1'), DiscreteVariable('d1', values='abc'), DiscreteVariable('d2', values='def')], class_vars=[DiscreteVariable('d3', values='ghi')], metas=[ContinuousVariable('c2'), DiscreteVariable('d4', values='jkl')] ) self.args = (self.domain, {'c1': Continuous, 'd1': Discrete, 'd2': Discrete, 'd3': Discrete}, {'c2': Continuous, 'd4': Discrete, }) self.handler = SelectAttributesDomainContextHandler() self.handler.read_defaults = lambda: None def test_open_context(self): self.handler.bind(SimpleWidget) context = Mock( attributes=self.args[1], metas=self.args[2], values=dict( domain_role_hints=({('d1', Discrete): ('available', 0), ('d2', Discrete): ('meta', 0), ('c1', Continuous): ('attribute', 0), ('d3', Discrete): ('attribute', 1), ('d4', Discrete): ('attribute', 2), ('c2', Continuous): ('class', 0)}, -2), with_metas=[('d1', Discrete), ('d2', Discrete)] )) self.handler.global_contexts = \ [Mock(values={}), context, Mock(values={})] widget = SimpleWidget() self.handler.initialize(widget) self.handler.open_context(widget, self.args[0]) self.assertEqual(widget.domain_role_hints, {('d1', Discrete): ('available', 0), ('d2', Discrete): ('meta', 0), ('c1', Continuous): ('attribute', 0), ('d3', Discrete): ('attribute', 1), ('d4', Discrete): ('attribute', 2), ('c2', Continuous): ('class', 0)}) def test_open_context_with_imperfect_match(self): self.handler.bind(SimpleWidget) context = Mock(values=dict( domain_role_hints=({('d1', Discrete): ('available', 0), ('d2', Discrete): ('meta', 0), ('c1', Continuous): ('attribute', 0), ('d6', Discrete): ('attribute', 1), ('d7', Discrete): ('attribute', 2), ('c2', Continuous): ('class', 0)}, -2) )) self.handler.global_contexts = \ [Mock(values={}), context, Mock(values={})] widget = SimpleWidget() self.handler.initialize(widget) self.handler.open_context(widget, self.args[0]) self.assertEqual(widget.domain_role_hints, {('d1', Discrete): ('available', 0), ('d2', Discrete): ('meta', 0), ('c1', Continuous): ('attribute', 0), ('c2', Continuous): ('class', 0)}) def test_open_context_with_no_match(self): self.handler.bind(SimpleWidget) context = Mock(values=dict( domain_role_hints=({('d1', Discrete): ('available', 0), ('d2', Discrete): ('meta', 0), ('c1', Continuous): ('attribute', 0), ('d3', Discrete): ('attribute', 1), ('d4', Discrete): ('attribute', 2), ('c2', Continuous): ('class', 0)}, -2), required=('g1', Continuous), )) self.handler.global_contexts = [context] widget = SimpleWidget() self.handler.initialize(widget) self.handler.open_context(widget, self.args[0]) self.assertEqual(widget.domain_role_hints, {})
class TestSelectAttributesDomainContextHandler(TestCase): def setUp(self): self.domain = Domain( attributes=[ ContinuousVariable("c1"), DiscreteVariable("d1", values="abc"), DiscreteVariable("d2", values="def"), ], class_vars=[DiscreteVariable("d3", values="ghi")], metas=[ ContinuousVariable("c2"), DiscreteVariable("d4", values="jkl") ], ) self.args = ( self.domain, { "c1": Continuous, "d1": Discrete, "d2": Discrete, "d3": Discrete }, { "c2": Continuous, "d4": Discrete }, ) self.handler = SelectAttributesDomainContextHandler() self.handler.read_defaults = lambda: None def test_open_context(self): self.handler.bind(SimpleWidget) context = Mock( attributes=self.args[1], metas=self.args[2], values=dict( domain_role_hints=( { ("d1", Discrete): ("available", 0), ("d2", Discrete): ("meta", 0), ("c1", Continuous): ("attribute", 0), ("d3", Discrete): ("attribute", 1), ("d4", Discrete): ("attribute", 2), ("c2", Continuous): ("class", 0), }, -2, ), with_metas=[("d1", Discrete), ("d2", Discrete)], ), ) self.handler.global_contexts = [ Mock(values={}), context, Mock(values={}) ] widget = SimpleWidget() self.handler.initialize(widget) self.handler.open_context(widget, self.args[0]) self.assertEqual( widget.domain_role_hints, { ("d1", Discrete): ("available", 0), ("d2", Discrete): ("meta", 0), ("c1", Continuous): ("attribute", 0), ("d3", Discrete): ("attribute", 1), ("d4", Discrete): ("attribute", 2), ("c2", Continuous): ("class", 0), }, ) def test_open_context_with_imperfect_match(self): self.handler.bind(SimpleWidget) context = Mock(values=dict(domain_role_hints=( { ("d1", Discrete): ("available", 0), ("d2", Discrete): ("meta", 0), ("c1", Continuous): ("attribute", 0), ("d6", Discrete): ("attribute", 1), ("d7", Discrete): ("attribute", 2), ("c2", Continuous): ("class", 0), }, -2, ))) self.handler.global_contexts = [ Mock(values={}), context, Mock(values={}) ] widget = SimpleWidget() self.handler.initialize(widget) self.handler.open_context(widget, self.args[0]) self.assertEqual( widget.domain_role_hints, { ("d1", Discrete): ("available", 0), ("d2", Discrete): ("meta", 0), ("c1", Continuous): ("attribute", 0), ("c2", Continuous): ("class", 0), }, ) def test_open_context_with_no_match(self): self.handler.bind(SimpleWidget) context = Mock(values=dict( domain_role_hints=( { ("d1", Discrete): ("available", 0), ("d2", Discrete): ("meta", 0), ("c1", Continuous): ("attribute", 0), ("d3", Discrete): ("attribute", 1), ("d4", Discrete): ("attribute", 2), ("c2", Continuous): ("class", 0), }, -2, ), required=("g1", Continuous), )) self.handler.global_contexts = [context] widget = SimpleWidget() self.handler.initialize(widget) self.handler.open_context(widget, self.args[0]) self.assertEqual(widget.domain_role_hints, {})
class OWSelectAttributes(widget.OWWidget): name = "Select Columns" description = """Select columns from the data table and define sets of features, classes or meta variables.""" icon = "icons/SelectColumns.svg" priority = 100 author = "Ales Erjavec" author_email = "ales.erjavec(@at@)fri.uni-lj.si" inputs = [("Data", Table, "set_data")] outputs = [("Data", Table), ("Features", widget.AttributeList)] want_main_area = False want_control_area = False settingsHandler = SelectAttributesDomainContextHandler() domain_role_hints = ContextSetting({}) def __init__(self): super().__init__() self.controlArea = QtGui.QWidget(self.leftWidgetPart) self.layout().addWidget(self.controlArea) layout = QtGui.QGridLayout() self.controlArea.setLayout(layout) layout.setMargin(4) box = gui.widgetBox(self.controlArea, "Available Variables", addToLayout=False) self.filter_edit = QtGui.QLineEdit() self.filter_edit.setToolTip("Filter the list of available variables.") box.layout().addWidget(self.filter_edit) if hasattr(self.filter_edit, "setPlaceholderText"): self.filter_edit.setPlaceholderText("Filter") self.completer = QtGui.QCompleter() self.completer.setCompletionMode(QtGui.QCompleter.InlineCompletion) self.completer_model = QtGui.QStringListModel() self.completer.setModel(self.completer_model) self.completer.setModelSorting( QtGui.QCompleter.CaseSensitivelySortedModel) self.filter_edit.setCompleter(self.completer) self.completer_navigator = CompleterNavigator(self) self.filter_edit.installEventFilter(self.completer_navigator) self.available_attrs = VariablesListItemModel() self.available_attrs_proxy = VariableFilterProxyModel() self.available_attrs_proxy.setSourceModel(self.available_attrs) self.available_attrs_view = VariablesListItemView() self.available_attrs_view.setModel(self.available_attrs_proxy) aa = self.available_attrs aa.dataChanged.connect(self.update_completer_model) aa.rowsInserted.connect(self.update_completer_model) aa.rowsRemoved.connect(self.update_completer_model) self.available_attrs_view.selectionModel().selectionChanged.connect( partial(self.update_interface_state, self.available_attrs_view)) self.filter_edit.textChanged.connect(self.update_completer_prefix) self.filter_edit.textChanged.connect( self.available_attrs_proxy.set_filter_string) box.layout().addWidget(self.available_attrs_view) layout.addWidget(box, 0, 0, 3, 1) box = gui.widgetBox(self.controlArea, "Features", addToLayout=False) self.used_attrs = VariablesListItemModel() self.used_attrs_view = VariablesListItemView() self.used_attrs_view.setModel(self.used_attrs) self.used_attrs_view.selectionModel().selectionChanged.connect( partial(self.update_interface_state, self.used_attrs_view)) box.layout().addWidget(self.used_attrs_view) layout.addWidget(box, 0, 2, 1, 1) box = gui.widgetBox(self.controlArea, "Class", addToLayout=False) self.class_attrs = ClassVarListItemModel() self.class_attrs_view = ClassVariableItemView() self.class_attrs_view.setModel(self.class_attrs) self.class_attrs_view.selectionModel().selectionChanged.connect( partial(self.update_interface_state, self.class_attrs_view)) self.class_attrs_view.setMaximumHeight(24) box.layout().addWidget(self.class_attrs_view) layout.addWidget(box, 1, 2, 1, 1) box = gui.widgetBox(self.controlArea, "Meta Attributes", addToLayout=False) self.meta_attrs = VariablesListItemModel() self.meta_attrs_view = VariablesListItemView() self.meta_attrs_view.setModel(self.meta_attrs) self.meta_attrs_view.selectionModel().selectionChanged.connect( partial(self.update_interface_state, self.meta_attrs_view)) box.layout().addWidget(self.meta_attrs_view) layout.addWidget(box, 2, 2, 1, 1) bbox = gui.widgetBox(self.controlArea, addToLayout=False, margin=0) layout.addWidget(bbox, 0, 1, 1, 1) self.up_attr_button = gui.button(bbox, self, "Up", callback=partial( self.move_up, self.used_attrs_view)) self.move_attr_button = gui.button(bbox, self, ">", callback=partial( self.move_selected, self.used_attrs_view)) self.down_attr_button = gui.button(bbox, self, "Down", callback=partial( self.move_down, self.used_attrs_view)) bbox = gui.widgetBox(self.controlArea, addToLayout=False, margin=0) layout.addWidget(bbox, 1, 1, 1, 1) self.move_class_button = gui.button(bbox, self, ">", callback=partial( self.move_selected, self.class_attrs_view, exclusive=True)) bbox = gui.widgetBox(self.controlArea, addToLayout=False, margin=0) layout.addWidget(bbox, 2, 1, 1, 1) self.up_meta_button = gui.button(bbox, self, "Up", callback=partial( self.move_up, self.meta_attrs_view)) self.move_meta_button = gui.button(bbox, self, ">", callback=partial( self.move_selected, self.meta_attrs_view)) self.down_meta_button = gui.button(bbox, self, "Down", callback=partial( self.move_down, self.meta_attrs_view)) bbox = gui.widgetBox(self.controlArea, orientation="horizontal", addToLayout=False, margin=0) gui.button(bbox, self, "Apply", callback=self.commit) gui.button(bbox, self, "Reset", callback=self.reset) layout.addWidget(bbox, 3, 0, 1, 3) layout.setRowStretch(0, 4) layout.setRowStretch(1, 0) layout.setRowStretch(2, 2) layout.setHorizontalSpacing(0) self.controlArea.setLayout(layout) self.data = None self.output_report = None self.original_completer_items = [] self.resize(500, 600) # For automatic widget testing using self._guiElements.extend([ (QtGui.QListView, self.available_attrs_view), (QtGui.QListView, self.used_attrs_view), (QtGui.QListView, self.class_attrs_view), (QtGui.QListView, self.meta_attrs_view), ]) def set_data(self, data=None): self.update_domain_role_hints() self.closeContext() self.data = data if data is not None: self.openContext(data) all_vars = data.domain.variables + data.domain.metas var_sig = lambda attr: (attr.name, vartype(attr)) domain_hints = { var_sig(attr): ("attribute", i) for i, attr in enumerate(data.domain.attributes) } domain_hints.update({ var_sig(attr): ("meta", i) for i, attr in enumerate(data.domain.metas) }) if data.domain.class_var: domain_hints[var_sig(data.domain.class_var)] = ("class", 0) # update the hints from context settings domain_hints.update(self.domain_role_hints) attrs_for_role = lambda role: [ (domain_hints[var_sig(attr)][1], attr) for attr in all_vars if domain_hints[var_sig(attr)][0] == role ] attributes = [ attr for place, attr in sorted(attrs_for_role("attribute"), key=lambda a: a[0]) ] classes = [ attr for place, attr in sorted(attrs_for_role("class"), key=lambda a: a[0]) ] metas = [ attr for place, attr in sorted(attrs_for_role("meta"), key=lambda a: a[0]) ] available = [ attr for place, attr in sorted(attrs_for_role("available"), key=lambda a: a[0]) ] self.used_attrs[:] = attributes self.class_attrs[:] = classes self.meta_attrs[:] = metas self.available_attrs[:] = available else: self.used_attrs[:] = [] self.class_attrs[:] = [] self.meta_attrs[:] = [] self.available_attrs[:] = [] self.commit() def update_domain_role_hints(self): """ Update the domain hints to be stored in the widgets settings. """ hints_from_model = lambda role, model: [( (attr.name, vartype(attr)), (role, i)) for i, attr in enumerate(model)] hints = dict(hints_from_model("available", self.available_attrs)) hints.update(hints_from_model("attribute", self.used_attrs)) hints.update(hints_from_model("class", self.class_attrs)) hints.update(hints_from_model("meta", self.meta_attrs)) self.domain_role_hints = hints def selected_rows(self, view): """ Return the selected rows in the view. """ rows = view.selectionModel().selectedRows() model = view.model() if isinstance(model, QtGui.QSortFilterProxyModel): rows = [model.mapToSource(r) for r in rows] return [r.row() for r in rows] def move_rows(self, view, rows, offset): model = view.model() newrows = [min(max(0, row + offset), len(model) - 1) for row in rows] for row, newrow in sorted(zip(rows, newrows), reverse=offset > 0): model[row], model[newrow] = model[newrow], model[row] selection = QtGui.QItemSelection() for nrow in newrows: index = model.index(nrow, 0) selection.select(index, index) view.selectionModel().select(selection, QtGui.QItemSelectionModel.ClearAndSelect) def move_up(self, view): selected = self.selected_rows(view) self.move_rows(view, selected, -1) def move_down(self, view): selected = self.selected_rows(view) self.move_rows(view, selected, 1) def move_selected(self, view, exclusive=False): if self.selected_rows(view): self.move_selected_from_to(view, self.available_attrs_view) elif self.selected_rows(self.available_attrs_view): self.move_selected_from_to(self.available_attrs_view, view, exclusive) def move_selected_from_to(self, src, dst, exclusive=False): self.move_from_to(src, dst, self.selected_rows(src), exclusive) def move_from_to(self, src, dst, rows, exclusive=False): src_model = source_model(src) attrs = [src_model[r] for r in rows] if exclusive and len(attrs) != 1: return for s1, s2 in reversed(list(slices(rows))): del src_model[s1:s2] dst_model = source_model(dst) if exclusive and len(dst_model) > 0: src_model.append(dst_model[0]) del dst_model[0] dst_model.extend(attrs) def update_interface_state(self, focus=None, selected=None, deselected=None): for view in [ self.available_attrs_view, self.used_attrs_view, self.class_attrs_view, self.meta_attrs_view ]: if view is not focus and not view.hasFocus( ) and self.selected_rows(view): view.selectionModel().clear() available_selected = bool(self.selected_rows( self.available_attrs_view)) move_attr_enabled = bool(self.selected_rows(self.available_attrs_view) or \ self.selected_rows(self.used_attrs_view)) self.move_attr_button.setEnabled(move_attr_enabled) if move_attr_enabled: self.move_attr_button.setText(">" if available_selected else "<") move_class_enabled = bool(len(self.selected_rows(self.available_attrs_view)) == 1 or \ self.selected_rows(self.class_attrs_view)) self.move_class_button.setEnabled(move_class_enabled) if move_class_enabled: self.move_class_button.setText(">" if available_selected else "<") move_meta_enabled = bool(self.selected_rows(self.available_attrs_view) or \ self.selected_rows(self.meta_attrs_view)) self.move_meta_button.setEnabled(move_meta_enabled) if move_meta_enabled: self.move_meta_button.setText(">" if available_selected else "<") def update_completer_model(self, *_): """ This gets called when the model for available attributes changes through either drag/drop or the left/right button actions. """ vars = list(self.available_attrs) items = [var.name for var in vars] labels = reduce(list.__add__, [list(v.attributes.items()) for v in vars], []) items.extend(["%s=%s" % item for item in labels]) items.extend(reduce(list.__add__, list(map(list, labels)), [])) new = sorted(set(items)) if new != self.original_completer_items: self.original_completer_items = new self.completer_model.setStringList(self.original_completer_items) def update_completer_prefix(self, filter): """ Prefixes all items in the completer model with the current already done completion to enable the completion of multiple keywords. """ prefix = str(self.completer.completionPrefix()) if not prefix.endswith(" ") and " " in prefix: prefix, _ = prefix.rsplit(" ", 1) items = [ prefix + " " + item for item in self.original_completer_items ] else: items = self.original_completer_items old = list(map(str, self.completer_model.stringList())) if set(old) != set(items): self.completer_model.setStringList(items) def commit(self): self.update_domain_role_hints() if self.data is not None: attributes = list(self.used_attrs) class_var = list(self.class_attrs) metas = list(self.meta_attrs) domain = Orange.data.Domain(attributes, class_var, metas) newdata = self.data.from_table(domain, self.data) self.output_report = self.prepareDataReport(newdata) self.output_domain = domain self.send("Data", newdata) self.send("Features", widget.AttributeList(attributes)) else: self.output_report = [] self.send("Data", None) self.send("Features", None) def reset(self): if self.data is not None: self.available_attrs[:] = [] self.used_attrs[:] = self.data.domain.attributes self.class_attrs[:] = self.data.domain.class_vars self.meta_attrs[:] = self.data.domain.metas self.update_domain_role_hints() def sendReport(self): self.reportData(self.data, "Input data") self.reportData(self.output_report, "Output data") if self.data: all_vars = self.data.domain.variables + self.data.domain.metas used_vars = self.output_domain.variables + self.output_domain.metas if len(all_vars) != len(used_vars): removed = set(all_vars).difference(set(used_vars)) self.reportSettings( "", [("Removed", "%i (%s)" % (len(removed), ", ".join(x.name for x in removed)))])