class OWPreprocess(widget.OWWidget, openclass=True): name = "Preprocess" description = "Construct a data preprocessing pipeline." icon = "icons/Preprocess.svg" priority = 2105 keywords = ["process"] settings_version = 2 class Inputs: data = Input("Data", Orange.data.Table) class Outputs: preprocessor = Output("Preprocessor", preprocess.preprocess.Preprocess, dynamic=False) preprocessed_data = Output("Preprocessed Data", Orange.data.Table) storedsettings = Setting({}) autocommit = Setting(True) PREPROCESSORS = PREPROCESS_ACTIONS CONTROLLER = Controller def __init__(self): super().__init__() self.data = None self._invalidated = False # List of available preprocessors (DescriptionRole : Description) self.preprocessors = QStandardItemModel() def mimeData(indexlist): assert len(indexlist) == 1 index = indexlist[0] qname = index.data(DescriptionRole).qualname m = QMimeData() m.setData("application/x-qwidget-ref", qname.encode("utf-8")) return m # TODO: Fix this (subclass even if just to pass a function # for mimeData delegate) self.preprocessors.mimeData = mimeData box = gui.vBox(self.controlArea, "Preprocessors") gui.rubber(self.controlArea) # we define a class that lets us set the vertical sizeHint # based on the height and number of items in the list # see self.__update_list_sizeHint class ListView(QListView): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.vertical_hint = None def sizeHint(self): sh = super().sizeHint() if self.vertical_hint: return QSize(sh.width(), self.vertical_hint) return sh self.preprocessorsView = view = ListView( selectionMode=QListView.SingleSelection, dragEnabled=True, dragDropMode=QListView.DragOnly) view.setModel(self.preprocessors) view.activated.connect(self.__activated) box.layout().addWidget(view) #### self._qname2ppdef = { ppdef.qualname: ppdef for ppdef in self.PREPROCESSORS } # List of 'selected' preprocessors and their parameters. self.preprocessormodel = None self.flow_view = SequenceFlow() self.controler = self.CONTROLLER(self.flow_view, parent=self) self.overlay = OverlayWidget(self) self.overlay.setAttribute(Qt.WA_TransparentForMouseEvents) self.overlay.setWidget(self.flow_view) self.overlay.setLayout(QVBoxLayout()) self.overlay.layout().addWidget( QLabel("Drag items from the list on the left", wordWrap=True)) self.scroll_area = QScrollArea( verticalScrollBarPolicy=Qt.ScrollBarAlwaysOn) self.scroll_area.viewport().setAcceptDrops(True) self.scroll_area.setWidget(self.flow_view) self.scroll_area.setWidgetResizable(True) self.mainArea.layout().addWidget(self.scroll_area) self.flow_view.installEventFilter(self) gui.auto_apply(self.buttonsArea, self, "autocommit") self._initialize() def _initialize(self): for pp_def in self.PREPROCESSORS: description = pp_def.description if description.icon: icon = QIcon(description.icon) else: icon = QIcon() item = QStandardItem(icon, description.title) item.setToolTip(description.summary or "") item.setData(pp_def, DescriptionRole) item.setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable | Qt.ItemIsDragEnabled) self.preprocessors.appendRow([item]) self.__update_list_sizeHint() model = self.load(self.storedsettings) self.set_model(model) if not model.rowCount(): # enforce default width constraint if no preprocessors # are instantiated (if the model is not empty the constraints # will be triggered by LayoutRequest event on the `flow_view`) self.__update_size_constraint() self.apply() def __update_list_sizeHint(self): view = self.preprocessorsView h = view.sizeHintForRow(0) n = self.preprocessors.rowCount() view.vertical_hint = n * h + 2 # only on Mac? view.updateGeometry() def load(self, saved): """Load a preprocessor list from a dict.""" preprocessors = saved.get("preprocessors", []) model = StandardItemModel() def dropMimeData(data, action, row, _column, _parent): if data.hasFormat("application/x-qwidget-ref") and \ action == Qt.CopyAction: qname = bytes(data.data("application/x-qwidget-ref")).decode() ppdef = self._qname2ppdef[qname] item = QStandardItem(ppdef.description.title) item.setData({}, ParametersRole) item.setData(ppdef.description.title, Qt.DisplayRole) item.setData(ppdef, DescriptionRole) self.preprocessormodel.insertRow(row, [item]) return True else: return False model.dropMimeData = dropMimeData for qualname, params in preprocessors: pp_def = self._qname2ppdef[qualname] description = pp_def.description item = QStandardItem(description.title) if description.icon: icon = QIcon(description.icon) else: icon = QIcon() item.setIcon(icon) item.setToolTip(description.summary) item.setData(pp_def, DescriptionRole) item.setData(params, ParametersRole) model.appendRow(item) return model @staticmethod def save(model): """Save the preprocessor list to a dict.""" d = {"name": ""} preprocessors = [] for i in range(model.rowCount()): item = model.item(i) pp_def = item.data(DescriptionRole) params = item.data(ParametersRole) preprocessors.append((pp_def.qualname, params)) d["preprocessors"] = preprocessors return d def set_model(self, ppmodel): if self.preprocessormodel: self.preprocessormodel.dataChanged.disconnect( self.__on_modelchanged) self.preprocessormodel.rowsInserted.disconnect( self.__on_modelchanged) self.preprocessormodel.rowsRemoved.disconnect( self.__on_modelchanged) self.preprocessormodel.rowsMoved.disconnect(self.__on_modelchanged) self.preprocessormodel.deleteLater() self.preprocessormodel = ppmodel self.controler.setModel(ppmodel) if ppmodel is not None: self.preprocessormodel.dataChanged.connect(self.__on_modelchanged) self.preprocessormodel.rowsInserted.connect(self.__on_modelchanged) self.preprocessormodel.rowsRemoved.connect(self.__on_modelchanged) self.preprocessormodel.rowsMoved.connect(self.__on_modelchanged) self.__update_overlay() def __update_overlay(self): if self.preprocessormodel is None or \ self.preprocessormodel.rowCount() == 0: self.overlay.setWidget(self.flow_view) self.overlay.show() else: self.overlay.setWidget(None) self.overlay.hide() def __on_modelchanged(self): self.__update_overlay() self.commit() @Inputs.data @check_sql_input def set_data(self, data=None): """Set the input dataset.""" self.data = data def handleNewSignals(self): self.apply() def __activated(self, index): item = self.preprocessors.itemFromIndex(index) action = item.data(DescriptionRole) item = QStandardItem() item.setData({}, ParametersRole) item.setData(action.description.title, Qt.DisplayRole) item.setData(action, DescriptionRole) self.preprocessormodel.appendRow([item]) def buildpreproc(self): plist = [] for i in range(self.preprocessormodel.rowCount()): item = self.preprocessormodel.item(i) desc = item.data(DescriptionRole) params = item.data(ParametersRole) if not isinstance(params, dict): params = {} create = desc.viewclass.createinstance plist.append(create(params)) if len(plist) == 1: return plist[0] else: return preprocess.preprocess.PreprocessorList(plist) def apply(self): # Sync the model into storedsettings on every apply. self.storeSpecificSettings() preprocessor = self.buildpreproc() if self.data is not None: self.error() try: data = preprocessor(self.data) except (ValueError, ZeroDivisionError) as e: self.error(str(e)) return else: data = None self.Outputs.preprocessor.send(preprocessor) self.Outputs.preprocessed_data.send(data) def commit(self): if not self._invalidated: self._invalidated = True QApplication.postEvent(self, QEvent(QEvent.User)) def customEvent(self, event): if event.type() == QEvent.User and self._invalidated: self._invalidated = False self.apply() def eventFilter(self, receiver, event): if receiver is self.flow_view and event.type() == QEvent.LayoutRequest: QTimer.singleShot(0, self.__update_size_constraint) return super().eventFilter(receiver, event) def storeSpecificSettings(self): """Reimplemented.""" self.storedsettings = self.save(self.preprocessormodel) super().storeSpecificSettings() def saveSettings(self): """Reimplemented.""" self.storedsettings = self.save(self.preprocessormodel) super().saveSettings() @classmethod def migrate_settings(cls, settings, version): if version < 2: for action, params in settings["storedsettings"]["preprocessors"]: if action == "orange.preprocess.scale": scale = center = None if "center" in params: center = params.pop("center").name if "scale" in params: scale = params.pop("scale").name migratable = { ("Mean", "NoScaling"): Scale.CenterByMean, ("NoCentering", "Std"): Scale.ScaleBySD, ("Mean", "Std"): Scale.NormalizeBySD, ("NoCentering", "Span"): Scale.NormalizeBySpan_ZeroBased } params["method"] = \ migratable.get((center, scale), Scale.NormalizeBySD) def onDeleteWidget(self): self.data = None self.set_model(None) super().onDeleteWidget() @Slot() def __update_size_constraint(self): # Update minimum width constraint on the scroll area containing # the 'instantiated' preprocessor list (to avoid the horizontal # scroll bar). sh = self.flow_view.minimumSizeHint() scroll_width = self.scroll_area.verticalScrollBar().width() self.scroll_area.setMinimumWidth( min(max(sh.width() + scroll_width + 2, self.controlArea.width()), 520)) def send_report(self): pp = [(self.controler.model().index(i, 0).data(Qt.DisplayRole), w) for i, w in enumerate(self.controler.view.widgets())] if pp: self.report_items("Settings", pp)
class OWPreprocess(widget.OWWidget): name = "Preprocess" description = "Construct a data preprocessing pipeline." icon = "icons/Preprocess.svg" priority = 2105 class Inputs: data = Input("Data", Orange.data.Table) class Outputs: preprocessor = Output("Preprocessor", preprocess.preprocess.Preprocess, dynamic=False) preprocessed_data = Output("Preprocessed Data", Orange.data.Table) storedsettings = settings.Setting({}) autocommit = settings.Setting(True) def __init__(self): super().__init__() self.data = None self._invalidated = False # List of available preprocessors (DescriptionRole : Description) self.preprocessors = QStandardItemModel() def mimeData(indexlist): assert len(indexlist) == 1 index = indexlist[0] qname = index.data(DescriptionRole).qualname m = QMimeData() m.setData("application/x-qwidget-ref", qname.encode("utf-8")) return m # TODO: Fix this (subclass even if just to pass a function # for mimeData delegate) self.preprocessors.mimeData = mimeData box = gui.vBox(self.controlArea, "Preprocessors") self.preprocessorsView = view = QListView( selectionMode=QListView.SingleSelection, dragEnabled=True, dragDropMode=QListView.DragOnly ) view.setModel(self.preprocessors) view.activated.connect(self.__activated) box.layout().addWidget(view) #### self._qname2ppdef = {ppdef.qualname: ppdef for ppdef in PREPROCESSORS} # List of 'selected' preprocessors and their parameters. self.preprocessormodel = None self.flow_view = SequenceFlow() self.controler = Controller(self.flow_view, parent=self) self.overlay = OverlayWidget(self) self.overlay.setAttribute(Qt.WA_TransparentForMouseEvents) self.overlay.setWidget(self.flow_view) self.overlay.setLayout(QVBoxLayout()) self.overlay.layout().addWidget( QLabel("Drag items from the list on the left", wordWrap=True)) self.scroll_area = QScrollArea( verticalScrollBarPolicy=Qt.ScrollBarAlwaysOn ) self.scroll_area.viewport().setAcceptDrops(True) self.scroll_area.setWidget(self.flow_view) self.scroll_area.setWidgetResizable(True) self.mainArea.layout().addWidget(self.scroll_area) self.flow_view.installEventFilter(self) box = gui.vBox(self.controlArea, "Output") gui.auto_commit(box, self, "autocommit", "Send", box=False) self._initialize() def _initialize(self): for pp_def in PREPROCESSORS: description = pp_def.description if description.icon: icon = QIcon(description.icon) else: icon = QIcon() item = QStandardItem(icon, description.title) item.setToolTip(description.summary or "") item.setData(pp_def, DescriptionRole) item.setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable | Qt.ItemIsDragEnabled) self.preprocessors.appendRow([item]) try: model = self.load(self.storedsettings) except Exception: model = self.load({}) self.set_model(model) if not model.rowCount(): # enforce default width constraint if no preprocessors # are instantiated (if the model is not empty the constraints # will be triggered by LayoutRequest event on the `flow_view`) self.__update_size_constraint() self.apply() def load(self, saved): """Load a preprocessor list from a dict.""" name = saved.get("name", "") preprocessors = saved.get("preprocessors", []) model = StandardItemModel() def dropMimeData(data, action, row, column, parent): if data.hasFormat("application/x-qwidget-ref") and \ action == Qt.CopyAction: qname = bytes(data.data("application/x-qwidget-ref")).decode() ppdef = self._qname2ppdef[qname] item = QStandardItem(ppdef.description.title) item.setData({}, ParametersRole) item.setData(ppdef.description.title, Qt.DisplayRole) item.setData(ppdef, DescriptionRole) self.preprocessormodel.insertRow(row, [item]) return True else: return False model.dropMimeData = dropMimeData for qualname, params in preprocessors: pp_def = self._qname2ppdef[qualname] description = pp_def.description item = QStandardItem(description.title) if description.icon: icon = QIcon(description.icon) else: icon = QIcon() item.setIcon(icon) item.setToolTip(description.summary) item.setData(pp_def, DescriptionRole) item.setData(params, ParametersRole) model.appendRow(item) return model def save(self, model): """Save the preprocessor list to a dict.""" d = {"name": ""} preprocessors = [] for i in range(model.rowCount()): item = model.item(i) pp_def = item.data(DescriptionRole) params = item.data(ParametersRole) preprocessors.append((pp_def.qualname, params)) d["preprocessors"] = preprocessors return d def set_model(self, ppmodel): if self.preprocessormodel: self.preprocessormodel.dataChanged.disconnect(self.__on_modelchanged) self.preprocessormodel.rowsInserted.disconnect(self.__on_modelchanged) self.preprocessormodel.rowsRemoved.disconnect(self.__on_modelchanged) self.preprocessormodel.rowsMoved.disconnect(self.__on_modelchanged) self.preprocessormodel.deleteLater() self.preprocessormodel = ppmodel self.controler.setModel(ppmodel) if ppmodel is not None: self.preprocessormodel.dataChanged.connect(self.__on_modelchanged) self.preprocessormodel.rowsInserted.connect(self.__on_modelchanged) self.preprocessormodel.rowsRemoved.connect(self.__on_modelchanged) self.preprocessormodel.rowsMoved.connect(self.__on_modelchanged) self.__update_overlay() def __update_overlay(self): if self.preprocessormodel is None or \ self.preprocessormodel.rowCount() == 0: self.overlay.setWidget(self.flow_view) self.overlay.show() else: self.overlay.setWidget(None) self.overlay.hide() def __on_modelchanged(self): self.__update_overlay() self.commit() @Inputs.data @check_sql_input def set_data(self, data=None): """Set the input data set.""" self.data = data def handleNewSignals(self): self.apply() def __activated(self, index): item = self.preprocessors.itemFromIndex(index) action = item.data(DescriptionRole) item = QStandardItem() item.setData({}, ParametersRole) item.setData(action.description.title, Qt.DisplayRole) item.setData(action, DescriptionRole) self.preprocessormodel.appendRow([item]) def buildpreproc(self): plist = [] for i in range(self.preprocessormodel.rowCount()): item = self.preprocessormodel.item(i) desc = item.data(DescriptionRole) params = item.data(ParametersRole) if not isinstance(params, dict): params = {} create = desc.viewclass.createinstance plist.append(create(params)) if len(plist) == 1: return plist[0] else: return preprocess.preprocess.PreprocessorList(plist) def apply(self): # Sync the model into storedsettings on every apply. self.storeSpecificSettings() preprocessor = self.buildpreproc() if self.data is not None: self.error() try: data = preprocessor(self.data) except (ValueError, ZeroDivisionError) as e: self.error(str(e)) return else: data = None self.Outputs.preprocessor.send(preprocessor) self.Outputs.preprocessed_data.send(data) def commit(self): if not self._invalidated: self._invalidated = True QApplication.postEvent(self, QEvent(QEvent.User)) def customEvent(self, event): if event.type() == QEvent.User and self._invalidated: self._invalidated = False self.apply() def eventFilter(self, receiver, event): if receiver is self.flow_view and event.type() == QEvent.LayoutRequest: QTimer.singleShot(0, self.__update_size_constraint) return super().eventFilter(receiver, event) def storeSpecificSettings(self): """Reimplemented.""" self.storedsettings = self.save(self.preprocessormodel) super().storeSpecificSettings() def saveSettings(self): """Reimplemented.""" self.storedsettings = self.save(self.preprocessormodel) super().saveSettings() def onDeleteWidget(self): self.data = None self.set_model(None) super().onDeleteWidget() @Slot() def __update_size_constraint(self): # Update minimum width constraint on the scroll area containing # the 'instantiated' preprocessor list (to avoid the horizontal # scroll bar). sh = self.flow_view.minimumSizeHint() scroll_width = self.scroll_area.verticalScrollBar().width() self.scroll_area.setMinimumWidth( min(max(sh.width() + scroll_width + 2, self.controlArea.width()), 520)) def sizeHint(self): sh = super().sizeHint() return sh.expandedTo(QSize(sh.width() + 300, 500)) def send_report(self): pp = [(self.controler.model().index(i, 0).data(Qt.DisplayRole), w) for i, w in enumerate(self.controler.view.widgets())] if len(pp): self.report_items("Settings", pp)
class OWPreprocess(widget.OWWidget): name = "Preprocess" description = "Construct a data preprocessing pipeline." icon = "icons/Preprocess.svg" priority = 2105 inputs = [("Data", Orange.data.Table, "set_data")] outputs = [("Preprocessor", preprocess.preprocess.Preprocess), ("Preprocessed Data", Orange.data.Table)] storedsettings = settings.Setting({}) autocommit = settings.Setting(True) def __init__(self): super().__init__() self.data = None self._invalidated = False # List of available preprocessors (DescriptionRole : Description) self.preprocessors = QStandardItemModel() def mimeData(indexlist): assert len(indexlist) == 1 index = indexlist[0] qname = index.data(DescriptionRole).qualname m = QMimeData() m.setData("application/x-qwidget-ref", qname.encode("utf-8")) return m # TODO: Fix this (subclass even if just to pass a function # for mimeData delegate) self.preprocessors.mimeData = mimeData box = gui.vBox(self.controlArea, "Preprocessors") self.preprocessorsView = view = QListView( selectionMode=QListView.SingleSelection, dragEnabled=True, dragDropMode=QListView.DragOnly) view.setModel(self.preprocessors) view.activated.connect(self.__activated) box.layout().addWidget(view) #### self._qname2ppdef = {ppdef.qualname: ppdef for ppdef in PREPROCESSORS} # List of 'selected' preprocessors and their parameters. self.preprocessormodel = None self.flow_view = SequenceFlow() self.controler = Controller(self.flow_view, parent=self) self.overlay = OverlayWidget(self) self.overlay.setAttribute(Qt.WA_TransparentForMouseEvents) self.overlay.setWidget(self.flow_view) self.overlay.setLayout(QVBoxLayout()) self.overlay.layout().addWidget( QLabel("Drag items from the list on the left", wordWrap=True)) self.scroll_area = QScrollArea( verticalScrollBarPolicy=Qt.ScrollBarAlwaysOn) self.scroll_area.viewport().setAcceptDrops(True) self.scroll_area.setWidget(self.flow_view) self.scroll_area.setWidgetResizable(True) self.mainArea.layout().addWidget(self.scroll_area) self.flow_view.installEventFilter(self) box = gui.vBox(self.controlArea, "Output") gui.auto_commit(box, self, "autocommit", "Send", box=False) self._initialize() def _initialize(self): for pp_def in PREPROCESSORS: description = pp_def.description if description.icon: icon = QIcon(description.icon) else: icon = QIcon() item = QStandardItem(icon, description.title) item.setToolTip(description.summary or "") item.setData(pp_def, DescriptionRole) item.setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable | Qt.ItemIsDragEnabled) self.preprocessors.appendRow([item]) try: model = self.load(self.storedsettings) except Exception: model = self.load({}) self.set_model(model) if not model.rowCount(): # enforce default width constraint if no preprocessors # are instantiated (if the model is not empty the constraints # will be triggered by LayoutRequest event on the `flow_view`) self.__update_size_constraint() self.apply() def load(self, saved): """Load a preprocessor list from a dict.""" name = saved.get("name", "") preprocessors = saved.get("preprocessors", []) model = StandardItemModel() def dropMimeData(data, action, row, column, parent): if data.hasFormat("application/x-qwidget-ref") and \ action == Qt.CopyAction: qname = bytes(data.data("application/x-qwidget-ref")).decode() ppdef = self._qname2ppdef[qname] item = QStandardItem(ppdef.description.title) item.setData({}, ParametersRole) item.setData(ppdef.description.title, Qt.DisplayRole) item.setData(ppdef, DescriptionRole) self.preprocessormodel.insertRow(row, [item]) return True else: return False model.dropMimeData = dropMimeData for qualname, params in preprocessors: pp_def = self._qname2ppdef[qualname] description = pp_def.description item = QStandardItem(description.title) if description.icon: icon = QIcon(description.icon) else: icon = QIcon() item.setIcon(icon) item.setToolTip(description.summary) item.setData(pp_def, DescriptionRole) item.setData(params, ParametersRole) model.appendRow(item) return model def save(self, model): """Save the preprocessor list to a dict.""" d = {"name": ""} preprocessors = [] for i in range(model.rowCount()): item = model.item(i) pp_def = item.data(DescriptionRole) params = item.data(ParametersRole) preprocessors.append((pp_def.qualname, params)) d["preprocessors"] = preprocessors return d def set_model(self, ppmodel): if self.preprocessormodel: self.preprocessormodel.dataChanged.disconnect( self.__on_modelchanged) self.preprocessormodel.rowsInserted.disconnect( self.__on_modelchanged) self.preprocessormodel.rowsRemoved.disconnect( self.__on_modelchanged) self.preprocessormodel.rowsMoved.disconnect(self.__on_modelchanged) self.preprocessormodel.deleteLater() self.preprocessormodel = ppmodel self.controler.setModel(ppmodel) if ppmodel is not None: self.preprocessormodel.dataChanged.connect(self.__on_modelchanged) self.preprocessormodel.rowsInserted.connect(self.__on_modelchanged) self.preprocessormodel.rowsRemoved.connect(self.__on_modelchanged) self.preprocessormodel.rowsMoved.connect(self.__on_modelchanged) self.__update_overlay() def __update_overlay(self): if self.preprocessormodel is None or \ self.preprocessormodel.rowCount() == 0: self.overlay.setWidget(self.flow_view) self.overlay.show() else: self.overlay.setWidget(None) self.overlay.hide() def __on_modelchanged(self): self.__update_overlay() self.commit() @check_sql_input def set_data(self, data=None): """Set the input data set.""" self.data = data def handleNewSignals(self): self.apply() def __activated(self, index): item = self.preprocessors.itemFromIndex(index) action = item.data(DescriptionRole) item = QStandardItem() item.setData({}, ParametersRole) item.setData(action.description.title, Qt.DisplayRole) item.setData(action, DescriptionRole) self.preprocessormodel.appendRow([item]) def buildpreproc(self): plist = [] for i in range(self.preprocessormodel.rowCount()): item = self.preprocessormodel.item(i) desc = item.data(DescriptionRole) params = item.data(ParametersRole) if not isinstance(params, dict): params = {} create = desc.viewclass.createinstance plist.append(create(params)) if len(plist) == 1: return plist[0] else: return preprocess.preprocess.PreprocessorList(plist) def apply(self): # Sync the model into storedsettings on every apply. self.storeSpecificSettings() preprocessor = self.buildpreproc() if self.data is not None: self.error() try: data = preprocessor(self.data) except (ValueError, ZeroDivisionError) as e: self.error(str(e)) return else: data = None self.send("Preprocessor", preprocessor) self.send("Preprocessed Data", data) def commit(self): if not self._invalidated: self._invalidated = True QApplication.postEvent(self, QEvent(QEvent.User)) def customEvent(self, event): if event.type() == QEvent.User and self._invalidated: self._invalidated = False self.apply() def eventFilter(self, receiver, event): if receiver is self.flow_view and event.type() == QEvent.LayoutRequest: QTimer.singleShot(0, self.__update_size_constraint) return super().eventFilter(receiver, event) def storeSpecificSettings(self): """Reimplemented.""" self.storedsettings = self.save(self.preprocessormodel) super().storeSpecificSettings() def saveSettings(self): """Reimplemented.""" self.storedsettings = self.save(self.preprocessormodel) super().saveSettings() def onDeleteWidget(self): self.data = None self.set_model(None) super().onDeleteWidget() @Slot() def __update_size_constraint(self): # Update minimum width constraint on the scroll area containing # the 'instantiated' preprocessor list (to avoid the horizontal # scroll bar). sh = self.flow_view.minimumSizeHint() scroll_width = self.scroll_area.verticalScrollBar().width() self.scroll_area.setMinimumWidth( min(max(sh.width() + scroll_width + 2, self.controlArea.width()), 520)) def sizeHint(self): sh = super().sizeHint() return sh.expandedTo(QSize(sh.width() + 300, 500)) def send_report(self): pp = [(self.controler.model().index(i, 0).data(Qt.DisplayRole), w) for i, w in enumerate(self.controler.view.widgets())] if len(pp): self.report_items("Settings", pp)
class EditableTreeView(QWidget): dataChanged = Signal() selectionChanged = Signal() def __init__(self, parent=None): super().__init__(parent=parent) self.__stack: List = [] self.__stack_index: int = -1 def push_on_data_changed(_, __, roles): if Qt.EditRole in roles: self._push_data() self.__model = QStandardItemModel() self.__model.dataChanged.connect(self.dataChanged) self.__model.dataChanged.connect(push_on_data_changed) self.__root: QStandardItem = self.__model.invisibleRootItem() self.__tree = TreeView(self.dataChanged) self.__tree.drop_finished.connect(self.dataChanged) self.__tree.drop_finished.connect(self._push_data) self.__tree.setModel(self.__model) self.__tree.selectionModel().selectionChanged.connect( self.selectionChanged) actions_widget = ModelActionsWidget() actions_widget.layout().setSpacing(1) action = QAction("+", self, toolTip="Add a new word") action.triggered.connect(self.__on_add) actions_widget.addAction(action) action = QAction("\N{MINUS SIGN}", self, toolTip="Remove word") action.triggered.connect(self.__on_remove) actions_widget.addAction(action) action = QAction("\N{MINUS SIGN}R", self, toolTip="Remove word recursively (incl. children)") action.triggered.connect(self.__on_remove_recursive) actions_widget.addAction(action) gui.rubber(actions_widget) self.__undo_action = action = QAction("Undo", self, toolTip="Undo") action.triggered.connect(self.__on_undo) actions_widget.addAction(action) self.__redo_action = action = QAction("Redo", self, toolTip="Redo") action.triggered.connect(self.__on_redo) actions_widget.addAction(action) self._enable_undo_redo() layout = QVBoxLayout() layout.setSpacing(1) layout.setContentsMargins(0, 0, 0, 0) layout.addWidget(self.__tree) layout.addWidget(actions_widget) self.setLayout(layout) def __on_add(self): parent: QStandardItem = self.__root selection: List = self.__tree.selectionModel().selectedIndexes() if selection: sel_index: QModelIndex = selection[0] parent: QStandardItem = self.__model.itemFromIndex(sel_index) item = QStandardItem("") parent.appendRow(item) index: QModelIndex = item.index() with disconnected(self.__model.dataChanged, self.dataChanged): self.__model.setItemData(index, {Qt.EditRole: ""}) self.__tree.setCurrentIndex(index) self.__tree.edit(index) def __on_remove_recursive(self): sel_model: QItemSelectionModel = self.__tree.selectionModel() if len(sel_model.selectedIndexes()): while sel_model.selectedIndexes(): index: QModelIndex = sel_model.selectedIndexes()[0] self.__model.removeRow(index.row(), index.parent()) self._push_data() self.dataChanged.emit() def __on_remove(self): sel_model: QItemSelectionModel = self.__tree.selectionModel() if len(sel_model.selectedIndexes()): while sel_model.selectedIndexes(): index: QModelIndex = sel_model.selectedIndexes()[0] # move children to item's parent item: QStandardItem = self.__model.itemFromIndex(index) children = [item.takeChild(i) for i in range(item.rowCount())] parent = item.parent() or self.__root self.__model.removeRow(index.row(), index.parent()) for child in children[::-1]: parent.insertRow(index.row(), child) self.__tree.expandAll() self._push_data() self.dataChanged.emit() def __on_undo(self): self.__stack_index -= 1 self._set_from_stack() def __on_redo(self): self.__stack_index += 1 self._set_from_stack() def get_words(self) -> List: return _model_to_words(self.__root) def get_selected_words(self) -> Set: return set(self.__model.itemFromIndex(index).text() for index in self.__tree.selectionModel().selectedIndexes()) def get_selected_words_with_children(self) -> Set: words = set() for index in self.__tree.selectionModel().selectedIndexes(): item: QStandardItem = self.__model.itemFromIndex(index) words.update(_model_to_words(item)) return words def get_data(self, with_selection=False) -> Union[Dict, OntoType]: selection = self.__tree.selectionModel().selectedIndexes() return _model_to_tree(self.__root, selection, with_selection) def set_data(self, data: Dict, keep_history: bool = False): if not keep_history: self.__stack = [] self.__stack_index = -1 self._set_data(data) self._push_data() def _set_data(self, data: Dict): self.clear() _tree_to_model(data, self.__root, self.__tree.selectionModel()) self.__tree.expandAll() def clear(self): if self.__model.hasChildren(): self.__model.removeRows(0, self.__model.rowCount()) def _enable_undo_redo(self): index = self.__stack_index self.__undo_action.setEnabled(index >= 1) self.__redo_action.setEnabled(index < len(self.__stack) - 1) def _push_data(self): self.__stack_index += 1 self.__stack = self.__stack[:self.__stack_index] self.__stack.append(self.get_data()) self._enable_undo_redo() def _set_from_stack(self): assert self.__stack_index < len(self.__stack) assert self.__stack_index >= 0 self._set_data(self.__stack[self.__stack_index]) self._enable_undo_redo() self.dataChanged.emit()