class OWEditDomain(widget.OWWidget): name = "Edit Domain" description = "Rename features and their values." icon = "icons/EditDomain.svg" priority = 3125 inputs = [("Data", Orange.data.Table, "set_data")] outputs = [("Data", Orange.data.Table)] settingsHandler = settings.DomainContextHandler() domain_change_hints = settings.ContextSetting({}) selected_index = settings.ContextSetting({}) autocommit = settings.Setting(False) def __init__(self): super().__init__() self.data = None self.input_vars = () self._invalidated = False box = gui.vBox(self.controlArea, "Domain Features") self.domain_model = itemmodels.VariableListModel() self.domain_view = QListView( selectionMode=QListView.SingleSelection ) self.domain_view.setModel(self.domain_model) self.domain_view.selectionModel().selectionChanged.connect( self._on_selection_changed) box.layout().addWidget(self.domain_view) box = gui.hBox(self.controlArea) gui.button(box, self, "Reset Selected", callback=self.reset_selected) gui.button(box, self, "Reset All", callback=self.reset_all) gui.auto_commit(self.controlArea, self, "autocommit", "Apply") box = gui.vBox(self.mainArea, "Edit") self.editor_stack = QStackedWidget() self.editor_stack.addWidget(DiscreteVariableEditor()) self.editor_stack.addWidget(ContinuousVariableEditor()) self.editor_stack.addWidget(VariableEditor()) box.layout().addWidget(self.editor_stack) @check_sql_input def set_data(self, data): """Set input data set.""" self.closeContext() self.clear() self.data = data if self.data is not None: self._initialize() self.openContext(self.data) self._restore() self.unconditional_commit() def clear(self): """Clear the widget state.""" self.data = None self.domain_model[:] = [] self.input_vars = [] self.domain_change_hints = {} self.selected_index = -1 def reset_selected(self): """Reset the currently selected variable to its original state.""" ind = self.selected_var_index() if ind >= 0: var = self.input_vars[ind] desc = variable_description(var, skip_attributes=True) if desc in self.domain_change_hints: del self.domain_change_hints[desc] self.domain_model[ind] = var self.editor_stack.currentWidget().set_data(var) self._invalidate() def reset_all(self): """Reset all variables to their original state.""" self.domain_change_hints = {} if self.data is not None: # To invalidate stored hints self.domain_model[:] = self.input_vars itemmodels.select_row(self.domain_view, self.selected_index) self._invalidate() def selected_var_index(self): """Return the selected row in 'Domain Features' view.""" rows = self.domain_view.selectedIndexes() assert len(rows) <= 1 return rows[0].row() if rows else -1 def _initialize(self): domain = self.data.domain self.input_vars = tuple(domain) + domain.metas self.domain_model[:] = list(self.input_vars) def _restore(self): # Restore the variable states from saved settings. def transform(var): vdesc = variable_description(var, skip_attributes=True) if vdesc in self.domain_change_hints: return variable_from_description( self.domain_change_hints[vdesc], compute_value=Orange.preprocess.transformation.Identity(var)) else: return var self.domain_model[:] = map(transform, self.input_vars) # Restore the variable selection if possible index = self.selected_index if index >= len(self.input_vars): index = 0 if len(self.input_vars) else -1 if index >= 0: itemmodels.select_row(self.domain_view, index) def _on_selection_changed(self): self.selected_index = self.selected_var_index() self.open_editor(self.selected_index) def open_editor(self, index): self.clear_editor() if index < 0: return var = self.domain_model[index] editor_index = 2 if var.is_discrete: editor_index = 0 elif var.is_continuous: editor_index = 1 editor = self.editor_stack.widget(editor_index) self.editor_stack.setCurrentWidget(editor) editor.set_data(var) editor.variable_changed.connect(self._on_variable_changed) def clear_editor(self): current = self.editor_stack.currentWidget() try: current.variable_changed.disconnect(self._on_variable_changed) except Exception: pass current.set_data(None) def _on_variable_changed(self): """User edited the current variable in editor.""" assert 0 <= self.selected_index <= len(self.domain_model) editor = self.editor_stack.currentWidget() # Replace the variable in the 'Domain Features' view/model old_var = self.input_vars[self.selected_index] new_var = editor.get_data().copy(compute_value=Orange.preprocess.transformation.Identity(old_var)) self.domain_model[self.selected_index] = new_var # Store the transformation hint. old_var_desc = variable_description(old_var, skip_attributes=True) self.domain_change_hints[old_var_desc] = variable_description(new_var) self._invalidate() def _invalidate(self): self.commit() def commit(self): """Send the changed data to output.""" new_data = None if self.data is not None: input_domain = self.data.domain n_attrs = len(input_domain.attributes) n_vars = len(input_domain.variables) n_class_vars = len(input_domain.class_vars) all_new_vars = list(self.domain_model) attrs = all_new_vars[: n_attrs] class_vars = all_new_vars[n_attrs: n_attrs + n_class_vars] new_metas = all_new_vars[n_attrs + n_class_vars:] new_domain = Orange.data.Domain(attrs, class_vars, new_metas) new_data = self.data.from_table(new_domain, self.data) self.send("Data", new_data) def sizeHint(self): sh = super().sizeHint() return sh.expandedTo(QSize(660, 550)) def send_report(self): if self.data is not None: self.report_raw("", EditDomainReport( old_domain=chain(self.data.domain.variables, self.data.domain.metas), new_domain=self.domain_model).to_html()) else: self.report_data(None)
class OWEditDomain(widget.OWWidget): name = "Edit Domain" description = "Rename variables, edit categories and variable annotations." icon = "icons/EditDomain.svg" priority = 3125 keywords = [] class Inputs: data = Input("Data", Orange.data.Table) class Outputs: data = Output("Data", Orange.data.Table) class Error(widget.OWWidget.Error): duplicate_var_name = widget.Msg("A variable name is duplicated.") settingsHandler = settings.DomainContextHandler() settings_version = 2 _domain_change_store = settings.ContextSetting({}) _selected_item = settings.ContextSetting(None) # type: Optional[str] want_control_area = False def __init__(self): super().__init__() self.data = None # type: Optional[Orange.data.Table] #: The current selected variable index self.selected_index = -1 self._invalidated = False mainlayout = self.mainArea.layout() assert isinstance(mainlayout, QVBoxLayout) layout = QHBoxLayout() mainlayout.addLayout(layout) box = QGroupBox("Variables") box.setLayout(QVBoxLayout()) layout.addWidget(box) self.variables_model = VariableListModel(parent=self) self.variables_view = self.domain_view = QListView( selectionMode=QListView.SingleSelection, uniformItemSizes=True, ) self.variables_view.setItemDelegate(VariableEditDelegate(self)) self.variables_view.setModel(self.variables_model) self.variables_view.selectionModel().selectionChanged.connect( self._on_selection_changed) box.layout().addWidget(self.variables_view) box = QGroupBox("Edit", ) box.setLayout(QVBoxLayout(margin=4)) layout.addWidget(box) self.editor_stack = QStackedWidget() self.editor_stack.addWidget(DiscreteVariableEditor()) self.editor_stack.addWidget(ContinuousVariableEditor()) self.editor_stack.addWidget(TimeVariableEditor()) self.editor_stack.addWidget(VariableEditor()) box.layout().addWidget(self.editor_stack) bbox = QDialogButtonBox() bbox.setStyleSheet("button-layout: {:d};".format( QDialogButtonBox.MacLayout)) bapply = QPushButton( "Apply", objectName="button-apply", toolTip="Apply changes and commit data on output.", default=True, autoDefault=False) bapply.clicked.connect(self.commit) breset = QPushButton( "Reset Selected", objectName="button-reset", toolTip="Rest selected variable to its input state.", autoDefault=False) breset.clicked.connect(self.reset_selected) breset_all = QPushButton( "Reset All", objectName="button-reset-all", toolTip="Reset all variables to their input state.", autoDefault=False) breset_all.clicked.connect(self.reset_all) bbox.addButton(bapply, QDialogButtonBox.AcceptRole) bbox.addButton(breset, QDialogButtonBox.ResetRole) bbox.addButton(breset_all, QDialogButtonBox.ResetRole) mainlayout.addWidget(bbox) self.variables_view.setFocus(Qt.NoFocusReason) # initial focus @Inputs.data def set_data(self, data): """Set input dataset.""" self.closeContext() self.clear() self.data = data if self.data is not None: self.set_domain(data.domain) self.openContext(self.data) self._restore() self.commit() def clear(self): """Clear the widget state.""" self.data = None self.variables_model.clear() assert self.selected_index == -1 self.selected_index = -1 self._selected_item = None self._domain_change_store = {} def reset_selected(self): """Reset the currently selected variable to its original state.""" ind = self.selected_var_index() if ind >= 0: model = self.variables_model midx = model.index(ind) var = midx.data(Qt.EditRole) tr = midx.data(TransformRole) if not tr: return # nothing to reset editor = self.editor_stack.currentWidget() with disconnected(editor.variable_changed, self._on_variable_changed): model.setData(midx, [], TransformRole) editor.set_data(var, []) self._invalidate() def reset_all(self): """Reset all variables to their original state.""" self._domain_change_store = {} if self.data is not None: model = self.variables_model for i in range(model.rowCount()): midx = model.index(i) model.setData(midx, [], TransformRole) index = self.selected_var_index() if index >= 0: self.open_editor(index) self._invalidate() def selected_var_index(self): """Return the current selected variable index.""" rows = self.variables_view.selectedIndexes() assert len(rows) <= 1 return rows[0].row() if rows else -1 def set_domain(self, domain): # type: (Orange.data.Domain) -> None self.variables_model[:] = [ abstract(v) for v in domain.variables + domain.metas ] def _restore(self, ): """ Restore the edit transform from saved state. """ model = self.variables_model for i in range(model.rowCount()): midx = model.index(i, 0) var = model.data(midx, Qt.EditRole) tr = self._restore_transform(var) if tr: model.setData(midx, tr, TransformRole) # Restore the current variable selection i = -1 if self._selected_item is not None: for i, var in enumerate(model): if var.name == self._selected_item: break if i == -1 and model.rowCount(): i = 0 if i != -1: itemmodels.select_row(self.variables_view, i) def _on_selection_changed(self): self.selected_index = self.selected_var_index() if self.selected_index != -1: self._selected_item = self.variables_model[ self.selected_index].name else: self._selected_item = None self.open_editor(self.selected_index) def open_editor(self, index): # type: (int) -> None self.clear_editor() model = self.variables_model if not 0 <= index < model.rowCount(): return idx = model.index(index, 0) var = model.data(idx, Qt.EditRole) tr = model.data(idx, TransformRole) if tr is None: tr = [] editors = {Categorical: 0, Real: 1, Time: 2, String: 3} editor_index = editors.get(type(var), 3) editor = self.editor_stack.widget(editor_index) self.editor_stack.setCurrentWidget(editor) editor.set_data(var, tr) editor.variable_changed.connect(self._on_variable_changed, Qt.UniqueConnection) def clear_editor(self): current = self.editor_stack.currentWidget() try: current.variable_changed.disconnect(self._on_variable_changed) except TypeError: pass current.set_data(None) @Slot() def _on_variable_changed(self): """User edited the current variable in editor.""" assert 0 <= self.selected_index <= len(self.variables_model) editor = self.editor_stack.currentWidget() var, transform = editor.get_data() model = self.variables_model midx = model.index(self.selected_index, 0) model.setData(midx, transform, TransformRole) self._store_transform(var, transform) self._invalidate() def _store_transform(self, var, transform): # type: (Variable, List[Transform]) -> None self._domain_change_store[deconstruct(var)] = [ deconstruct(t) for t in transform ] def _restore_transform(self, var): # type: (Variable) -> List[Transform] tr_ = self._domain_change_store.get(deconstruct(var), []) tr = [] for t in tr_: try: tr.append(reconstruct(*t)) except (NameError, TypeError) as err: warnings.warn("Failed to restore transform: {}, {!r}".format( t, err), UserWarning, stacklevel=2) return tr def _invalidate(self): self._set_modified(True) def _set_modified(self, state): self._invalidated = state b = self.findChild(QPushButton, "button-apply") if isinstance(b, QPushButton): f = b.font() f.setItalic(state) b.setFont(f) def commit(self): """ Apply the changes to the input data and send the changed data to output. """ self._set_modified(False) self.Error.duplicate_var_name.clear() data = self.data if data is None: self.Outputs.data.send(None) return model = self.variables_model def state(i): # type: (int) -> Tuple[Variable, List[Transform]] midx = self.variables_model.index(i, 0) return (model.data(midx, Qt.EditRole), model.data(midx, TransformRole)) state = [state(i) for i in range(model.rowCount())] if all(tr is None or not tr for _, tr in state): self.Outputs.data.send(data) return output_vars = [] input_vars = data.domain.variables + data.domain.metas assert all(v_.name == v.name for v, (v_, _) in zip(input_vars, state)) for (_, tr), v in zip(state, input_vars): if tr is not None and len(tr) > 0: var = apply_transform(v, tr) else: var = v output_vars.append(var) if len(output_vars) != len({v.name for v in output_vars}): self.Error.duplicate_var_name() self.Outputs.data.send(None) return domain = data.domain nx = len(domain.attributes) ny = len(domain.class_vars) domain = Orange.data.Domain(output_vars[:nx], output_vars[nx:nx + ny], output_vars[nx + ny:]) new_data = data.transform(domain) # print(new_data) self.Outputs.data.send(new_data) def sizeHint(self): sh = super().sizeHint() return sh.expandedTo(QSize(660, 550)) def send_report(self): if self.data is not None: model = self.variables_model state = ((model.data(midx, Qt.EditRole), model.data(midx, TransformRole)) for i in range(model.rowCount()) for midx in [model.index(i)]) parts = [] for var, trs in state: if trs: parts.append(report_transform(var, trs)) if parts: html = ("<ul>" + "".join(map("<li>{}</li>".format, parts)) + "</ul>") else: html = "No changes" self.report_raw("", html) else: self.report_data(None) @classmethod def migrate_context(cls, context, version): # pylint: disable=bad-continuation if version is None or version <= 1: hints_ = context.values.get("domain_change_hints", ({}, -2))[0] store = [] ns = "Orange.data.variable" mapping = { "DiscreteVariable": lambda name, args, attrs: ("Categorical", (name, tuple(args[0][1]), None, ())), "TimeVariable": lambda name, _, attrs: ("Time", (name, ())), "ContinuousVariable": lambda name, _, attrs: ("Real", (name, (3, "f"), ())), "StringVariable": lambda name, _, attrs: ("String", (name, ())), } for (module, class_name, *rest), target in hints_.items(): if module != ns: continue f = mapping.get(class_name) if f is None: continue trs = [] key_mapped = f(*rest) item_mapped = f(*target[2:]) src = reconstruct(*key_mapped) # type: Variable dst = reconstruct(*item_mapped) # type: Variable if src.name != dst.name: trs.append(Rename(dst.name)) if src.annotations != dst.annotations: trs.append(Annotate(dst.annotations)) if isinstance(src, Categorical): if src.categories != dst.categories: assert len(src.categories) == len(dst.categories) trs.append( CategoriesMapping( list(zip(src.categories, dst.categories)))) store.append( (deconstruct(src), [deconstruct(tr) for tr in trs])) context.values["_domain_change_store"] = (dict(store), -2)
class OWEditDomain(widget.OWWidget): name = "Edit Domain" description = "Rename features and their values." icon = "icons/EditDomain.svg" priority = 3125 class Inputs: data = Input("Data", Orange.data.Table) class Outputs: data = Output("Data", Orange.data.Table) settingsHandler = settings.DomainContextHandler() domain_change_hints = settings.ContextSetting({}) selected_index = settings.ContextSetting({}) autocommit = settings.Setting(True) def __init__(self): super().__init__() self.data = None self.input_vars = () self._invalidated = False box = gui.vBox(self.controlArea, "Domain Features") self.domain_model = itemmodels.VariableListModel() self.domain_view = QListView(selectionMode=QListView.SingleSelection, uniformItemSizes=True) self.domain_view.setModel(self.domain_model) self.domain_view.selectionModel().selectionChanged.connect( self._on_selection_changed) box.layout().addWidget(self.domain_view) box = gui.hBox(self.controlArea) gui.button(box, self, "Reset Selected", callback=self.reset_selected) gui.button(box, self, "Reset All", callback=self.reset_all) gui.auto_commit(self.controlArea, self, "autocommit", "Apply") box = gui.vBox(self.mainArea, "Edit") self.editor_stack = QStackedWidget() self.editor_stack.addWidget(DiscreteVariableEditor()) self.editor_stack.addWidget(ContinuousVariableEditor()) self.editor_stack.addWidget(VariableEditor()) box.layout().addWidget(self.editor_stack) self.Error.add_message("duplicate_var_name", "A variable name is duplicated.") @Inputs.data @check_sql_input def set_data(self, data): """Set input dataset.""" self.closeContext() self.clear() self.data = data if self.data is not None: self._initialize() self.openContext(self.data) self._restore() self.unconditional_commit() def clear(self): """Clear the widget state.""" self.data = None self.domain_model[:] = [] self.input_vars = [] self.domain_change_hints = {} self.selected_index = -1 def reset_selected(self): """Reset the currently selected variable to its original state.""" ind = self.selected_var_index() if ind >= 0: var = self.input_vars[ind] desc = variable_description(var, skip_attributes=True) if desc in self.domain_change_hints: del self.domain_change_hints[desc] self.domain_model[ind] = var self.editor_stack.currentWidget().set_data(var) self._invalidate() def reset_all(self): """Reset all variables to their original state.""" self.domain_change_hints = {} if self.data is not None: # To invalidate stored hints self.domain_model[:] = self.input_vars itemmodels.select_row(self.domain_view, self.selected_index) self._invalidate() def selected_var_index(self): """Return the selected row in 'Domain Features' view.""" rows = self.domain_view.selectedIndexes() assert len(rows) <= 1 return rows[0].row() if rows else -1 def _initialize(self): domain = self.data.domain self.input_vars = domain.variables + domain.metas self.domain_model[:] = list(self.input_vars) def _restore(self): # Restore the variable states from saved settings. def transform(var): vdesc = variable_description(var, skip_attributes=True) if vdesc in self.domain_change_hints: return variable_from_description( self.domain_change_hints[vdesc], compute_value=Orange.preprocess.transformation.Identity( var), ) else: return var self.domain_model[:] = map(transform, self.input_vars) # Restore the variable selection if possible index = self.selected_index if index >= len(self.input_vars): index = 0 if len(self.input_vars) else -1 if index >= 0: itemmodels.select_row(self.domain_view, index) def _on_selection_changed(self): self.selected_index = self.selected_var_index() self.open_editor(self.selected_index) def open_editor(self, index): self.clear_editor() if index < 0: return var = self.domain_model[index] editor_index = 2 if var.is_discrete: editor_index = 0 elif var.is_continuous: editor_index = 1 editor = self.editor_stack.widget(editor_index) self.editor_stack.setCurrentWidget(editor) editor.set_data(var) editor.variable_changed.connect(self._on_variable_changed) def clear_editor(self): current = self.editor_stack.currentWidget() try: current.variable_changed.disconnect(self._on_variable_changed) except Exception: pass current.set_data(None) def _on_variable_changed(self): """User edited the current variable in editor.""" assert 0 <= self.selected_index <= len(self.domain_model) editor = self.editor_stack.currentWidget() # Replace the variable in the 'Domain Features' view/model old_var = self.input_vars[self.selected_index] new_var = editor.get_data().copy( compute_value=Orange.preprocess.transformation.Identity(old_var)) self.domain_model[self.selected_index] = new_var # Store the transformation hint. old_var_desc = variable_description(old_var, skip_attributes=True) self.domain_change_hints[old_var_desc] = variable_description(new_var) self._invalidate() def _invalidate(self): self.commit() def commit(self): """Send the changed data to output.""" new_data = None var_names = [vn.name for vn in self.domain_model] self.Error.duplicate_var_name.clear() if self.data is not None: if len(var_names) == len(set(var_names)): input_domain = self.data.domain n_attrs = len(input_domain.attributes) n_class_vars = len(input_domain.class_vars) all_new_vars = list(self.domain_model) attrs = all_new_vars[:n_attrs] class_vars = all_new_vars[n_attrs:n_attrs + n_class_vars] new_metas = all_new_vars[n_attrs + n_class_vars:] new_domain = Orange.data.Domain(attrs, class_vars, new_metas) new_data = self.data.transform(new_domain) else: self.Error.duplicate_var_name() self.Outputs.data.send(new_data) def sizeHint(self): sh = super().sizeHint() return sh.expandedTo(QSize(660, 550)) def send_report(self): if self.data is not None: self.report_raw( "", EditDomainReport( old_domain=chain(self.data.domain.variables, self.data.domain.metas), new_domain=self.domain_model, ).to_html(), ) else: self.report_data(None)
class OWEditDomain(widget.OWWidget): name = "Edit Domain" description = "Rename variables, edit categories and variable annotations." icon = "icons/EditDomain.svg" priority = 3125 keywords = [] class Inputs: data = Input("Data", Orange.data.Table) class Outputs: data = Output("Data", Orange.data.Table) class Error(widget.OWWidget.Error): duplicate_var_name = widget.Msg("A variable name is duplicated.") settingsHandler = settings.DomainContextHandler() settings_version = 2 _domain_change_store = settings.ContextSetting({}) _selected_item = settings.ContextSetting(None) # type: Optional[str] want_control_area = False def __init__(self): super().__init__() self.data = None # type: Optional[Orange.data.Table] #: The current selected variable index self.selected_index = -1 self._invalidated = False mainlayout = self.mainArea.layout() assert isinstance(mainlayout, QVBoxLayout) layout = QHBoxLayout() mainlayout.addLayout(layout) box = QGroupBox("Variables") box.setLayout(QVBoxLayout()) layout.addWidget(box) self.variables_model = VariableListModel(parent=self) self.variables_view = self.domain_view = QListView( selectionMode=QListView.SingleSelection, uniformItemSizes=True, ) self.variables_view.setItemDelegate(VariableEditDelegate(self)) self.variables_view.setModel(self.variables_model) self.variables_view.selectionModel().selectionChanged.connect( self._on_selection_changed ) box.layout().addWidget(self.variables_view) box = QGroupBox("Edit", ) box.setLayout(QVBoxLayout(margin=4)) layout.addWidget(box) self.editor_stack = QStackedWidget() self.editor_stack.addWidget(DiscreteVariableEditor()) self.editor_stack.addWidget(ContinuousVariableEditor()) self.editor_stack.addWidget(TimeVariableEditor()) self.editor_stack.addWidget(VariableEditor()) box.layout().addWidget(self.editor_stack) bbox = QDialogButtonBox() bbox.setStyleSheet( "button-layout: {:d};".format(QDialogButtonBox.MacLayout)) bapply = QPushButton( "Apply", objectName="button-apply", toolTip="Apply changes and commit data on output.", default=True, autoDefault=False ) bapply.clicked.connect(self.commit) breset = QPushButton( "Reset Selected", objectName="button-reset", toolTip="Rest selected variable to its input state.", autoDefault=False ) breset.clicked.connect(self.reset_selected) breset_all = QPushButton( "Reset All", objectName="button-reset-all", toolTip="Reset all variables to their input state.", autoDefault=False ) breset_all.clicked.connect(self.reset_all) bbox.addButton(bapply, QDialogButtonBox.AcceptRole) bbox.addButton(breset, QDialogButtonBox.ResetRole) bbox.addButton(breset_all, QDialogButtonBox.ResetRole) mainlayout.addWidget(bbox) self.variables_view.setFocus(Qt.NoFocusReason) # initial focus @Inputs.data def set_data(self, data): """Set input dataset.""" self.closeContext() self.clear() self.data = data if self.data is not None: self.set_domain(data.domain) self.openContext(self.data) self._restore() self.commit() def clear(self): """Clear the widget state.""" self.data = None self.variables_model.clear() assert self.selected_index == -1 self.selected_index = -1 self._selected_item = None self._domain_change_store = {} def reset_selected(self): """Reset the currently selected variable to its original state.""" ind = self.selected_var_index() if ind >= 0: model = self.variables_model midx = model.index(ind) var = midx.data(Qt.EditRole) tr = midx.data(TransformRole) if not tr: return # nothing to reset editor = self.editor_stack.currentWidget() with disconnected(editor.variable_changed, self._on_variable_changed): model.setData(midx, [], TransformRole) editor.set_data(var, []) self._invalidate() def reset_all(self): """Reset all variables to their original state.""" self._domain_change_store = {} if self.data is not None: model = self.variables_model for i in range(model.rowCount()): midx = model.index(i) model.setData(midx, [], TransformRole) index = self.selected_var_index() if index >= 0: self.open_editor(index) self._invalidate() def selected_var_index(self): """Return the current selected variable index.""" rows = self.variables_view.selectedIndexes() assert len(rows) <= 1 return rows[0].row() if rows else -1 def set_domain(self, domain): # type: (Orange.data.Domain) -> None self.variables_model[:] = [abstract(v) for v in domain.variables + domain.metas] def _restore(self, ): """ Restore the edit transform from saved state. """ model = self.variables_model for i in range(model.rowCount()): midx = model.index(i, 0) var = model.data(midx, Qt.EditRole) tr = self._restore_transform(var) if tr: model.setData(midx, tr, TransformRole) # Restore the current variable selection i = -1 if self._selected_item is not None: for i, var in enumerate(model): if var.name == self._selected_item: break if i == -1 and model.rowCount(): i = 0 if i != -1: itemmodels.select_row(self.variables_view, i) def _on_selection_changed(self): self.selected_index = self.selected_var_index() if self.selected_index != -1: self._selected_item = self.variables_model[self.selected_index].name else: self._selected_item = None self.open_editor(self.selected_index) def open_editor(self, index): # type: (int) -> None self.clear_editor() model = self.variables_model if not 0 <= index < model.rowCount(): return idx = model.index(index, 0) var = model.data(idx, Qt.EditRole) tr = model.data(idx, TransformRole) if tr is None: tr = [] editors = { Categorical: 0, Real: 1, Time: 2, String: 3 } editor_index = editors.get(type(var), 3) editor = self.editor_stack.widget(editor_index) self.editor_stack.setCurrentWidget(editor) editor.set_data(var, tr) editor.variable_changed.connect( self._on_variable_changed, Qt.UniqueConnection ) def clear_editor(self): current = self.editor_stack.currentWidget() try: current.variable_changed.disconnect(self._on_variable_changed) except TypeError: pass current.set_data(None) @Slot() def _on_variable_changed(self): """User edited the current variable in editor.""" assert 0 <= self.selected_index <= len(self.variables_model) editor = self.editor_stack.currentWidget() var, transform = editor.get_data() model = self.variables_model midx = model.index(self.selected_index, 0) model.setData(midx, transform, TransformRole) self._store_transform(var, transform) self._invalidate() def _store_transform(self, var, transform): # type: (Variable, List[Transform]) -> None self._domain_change_store[deconstruct(var)] = [deconstruct(t) for t in transform] def _restore_transform(self, var): # type: (Variable) -> List[Transform] tr_ = self._domain_change_store.get(deconstruct(var), []) tr = [] for t in tr_: try: tr.append(reconstruct(*t)) except (NameError, TypeError) as err: warnings.warn( "Failed to restore transform: {}, {!r}".format(t, err), UserWarning, stacklevel=2 ) return tr def _invalidate(self): self._set_modified(True) def _set_modified(self, state): self._invalidated = state b = self.findChild(QPushButton, "button-apply") if isinstance(b, QPushButton): f = b.font() f.setItalic(state) b.setFont(f) def commit(self): """ Apply the changes to the input data and send the changed data to output. """ self._set_modified(False) self.Error.duplicate_var_name.clear() data = self.data if data is None: self.Outputs.data.send(None) return model = self.variables_model def state(i): # type: (int) -> Tuple[Variable, List[Transform]] midx = self.variables_model.index(i, 0) return (model.data(midx, Qt.EditRole), model.data(midx, TransformRole)) state = [state(i) for i in range(model.rowCount())] if all(tr is None or not tr for _, tr in state): self.Outputs.data.send(data) return output_vars = [] input_vars = data.domain.variables + data.domain.metas assert all(v_.name == v.name for v, (v_, _) in zip(input_vars, state)) for (_, tr), v in zip(state, input_vars): if tr is not None and len(tr) > 0: var = apply_transform(v, tr) else: var = v output_vars.append(var) if len(output_vars) != len({v.name for v in output_vars}): self.Error.duplicate_var_name() self.Outputs.data.send(None) return domain = data.domain nx = len(domain.attributes) ny = len(domain.class_vars) domain = Orange.data.Domain( output_vars[:nx], output_vars[nx: nx + ny], output_vars[nx + ny:] ) new_data = data.transform(domain) # print(new_data) self.Outputs.data.send(new_data) def sizeHint(self): sh = super().sizeHint() return sh.expandedTo(QSize(660, 550)) def send_report(self): if self.data is not None: model = self.variables_model state = ((model.data(midx, Qt.EditRole), model.data(midx, TransformRole)) for i in range(model.rowCount()) for midx in [model.index(i)]) parts = [] for var, trs in state: if trs: parts.append(report_transform(var, trs)) if parts: html = ("<ul>" + "".join(map("<li>{}</li>".format, parts)) + "</ul>") else: html = "No changes" self.report_raw("", html) else: self.report_data(None) @classmethod def migrate_context(cls, context, version): # pylint: disable=bad-continuation if version is None or version <= 1: hints_ = context.values.get("domain_change_hints", ({}, -2))[0] store = [] ns = "Orange.data.variable" mapping = { "DiscreteVariable": lambda name, args, attrs: ("Categorical", (name, tuple(args[0][1]), None, ())), "TimeVariable": lambda name, _, attrs: ("Time", (name, ())), "ContinuousVariable": lambda name, _, attrs: ("Real", (name, (3, "f"), ())), "StringVariable": lambda name, _, attrs: ("String", (name, ())), } for (module, class_name, *rest), target in hints_.items(): if module != ns: continue f = mapping.get(class_name) if f is None: continue trs = [] key_mapped = f(*rest) item_mapped = f(*target[2:]) src = reconstruct(*key_mapped) # type: Variable dst = reconstruct(*item_mapped) # type: Variable if src.name != dst.name: trs.append(Rename(dst.name)) if src.annotations != dst.annotations: trs.append(Annotate(dst.annotations)) if isinstance(src, Categorical): if src.categories != dst.categories: assert len(src.categories) == len(dst.categories) trs.append(CategoriesMapping( list(zip(src.categories, dst.categories)))) store.append((deconstruct(src), [deconstruct(tr) for tr in trs])) context.values["_domain_change_store"] = (dict(store), -2)