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 __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()
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 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)
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()
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)