class OWHierarchicalClustering(widget.OWWidget):
    name = "Hierarchical Clustering"
    description = ("Hierarchical clustering based on distance matrix, and "
                   "a dendrogram viewer.")
    icon = "icons/HierarchicalClustering.svg"
    priority = 2100

    inputs = [("Distances", Orange.misc.DistMatrix, "set_distances")]

    outputs = [("Selected Data", Orange.data.Table),
               ("Other Data", Orange.data.Table)]

    #: Selected linkage
    linkage = settings.Setting(1)
    #: Index of the selected annotation item (variable, ...)
    annotation_idx = settings.Setting(0)
    #: Selected tree pruning (none/max depth)
    pruning = settings.Setting(0)
    #: Maximum depth when max depth pruning is selected
    max_depth = settings.Setting(10)

    #: Selected cluster selection method (none, cut distance, top n)
    selection_method = settings.Setting(0)
    #: Cut height ratio wrt root height
    cut_ratio = settings.Setting(75.0)
    #: Number of top clusters to select
    top_n = settings.Setting(3)

    append_clusters = settings.Setting(True)
    cluster_role = settings.Setting(2)
    cluster_name = settings.Setting("Cluster")
    autocommit = settings.Setting(False)

    #: Cluster variable domain role
    AttributeRole, ClassRole, MetaRole = 0, 1, 2

    def __init__(self, parent=None):
        super().__init__(parent)

        self.matrix = None
        self.items = None
        self.linkmatrix = None
        self.root = None
        self._displayed_root = None
        self.cutoff_height = 0.0

        gui.comboBox(gui.widgetBox(self.controlArea, "Linkage"),
                     self, "linkage", items=LINKAGE,
                     callback=self._invalidate_clustering)

        box = gui.widgetBox(self.controlArea, "Annotation")
        self.label_cb = gui.comboBox(
            box, self, "annotation_idx", callback=self._update_labels)

        self.label_cb.setModel(itemmodels.VariableListModel())
        self.label_cb.model()[:] = ["None", "Enumeration"]

        box = gui.radioButtons(
            self.controlArea, self, "pruning", box="Pruning",
            callback=self._invalidate_pruning
        )
        grid = QGridLayout()
        box.layout().addLayout(grid)
        grid.addWidget(
            gui.appendRadioButton(box, "None", addToLayout=False),
            0, 0
        )
        self.max_depth_spin = gui.spin(
            box, self, "max_depth", minv=1, maxv=100,
            callback=self._invalidate_pruning,
            keyboardTracking=False
        )

        grid.addWidget(
            gui.appendRadioButton(box, "Max depth", addToLayout=False),
            1, 0)
        grid.addWidget(self.max_depth_spin, 1, 1)

        box = gui.radioButtons(
            self.controlArea, self, "selection_method",
            box="Selection",
            callback=self._selection_method_changed)

        grid = QGridLayout()
        box.layout().addLayout(grid)
        grid.addWidget(
            gui.appendRadioButton(box, "Manual", addToLayout=False),
            0, 0
        )
        grid.addWidget(
            gui.appendRadioButton(box, "Height ratio", addToLayout=False),
            1, 0
        )
        self.cut_ratio_spin = gui.spin(
            box, self, "cut_ratio", 0, 100, step=1e-1, spinType=float,
            callback=self._selection_method_changed
        )
        self.cut_ratio_spin.setSuffix("%")

        grid.addWidget(self.cut_ratio_spin, 1, 1)

        grid.addWidget(
            gui.appendRadioButton(box, "Top N", addToLayout=False),
            2, 0
        )
        self.top_n_spin = gui.spin(box, self, "top_n", 1, 20,
                                   callback=self._selection_method_changed)
        grid.addWidget(self.top_n_spin, 2, 1)
        box.layout().addLayout(grid)

        self.controlArea.layout().addStretch()

        box = gui.widgetBox(self.controlArea, "Output")
        gui.checkBox(box, self, "append_clusters", "Append cluster IDs",
                     callback=self._invalidate_output)

        ibox = gui.indentedBox(box)
        name_edit = gui.lineEdit(ibox, self, "cluster_name")
        name_edit.editingFinished.connect(self._invalidate_output)

        cb = gui.comboBox(
            ibox, self, "cluster_role", callback=self._invalidate_output,
            items=["Attribute",
                   "Class variable",
                   "Meta variable"]
        )
        form = QFormLayout(
            fieldGrowthPolicy=QFormLayout.AllNonFixedFieldsGrow,
            labelAlignment=Qt.AlignLeft,
            spacing=8
        )
        form.addRow("Name", name_edit)
        form.addRow("Place", cb)

        ibox.layout().addSpacing(5)
        ibox.layout().addLayout(form)
        ibox.layout().addSpacing(5)

        gui.auto_commit(box, self, "autocommit", "Send data", "Auto send is on",
                        box=False)

        self.scene = QGraphicsScene()
        self.view = QGraphicsView(
            self.scene,
            horizontalScrollBarPolicy=Qt.ScrollBarAlwaysOff,
            verticalScrollBarPolicy=Qt.ScrollBarAlwaysOn,
            alignment=Qt.AlignLeft | Qt.AlignVCenter
        )

        def axis_view(orientation):
            ax = pg.AxisItem(orientation=orientation, maxTickLength=7)
            scene = QGraphicsScene()
            scene.addItem(ax)
            view = QGraphicsView(
                scene,
                horizontalScrollBarPolicy=Qt.ScrollBarAlwaysOff,
                verticalScrollBarPolicy=Qt.ScrollBarAlwaysOn,
                alignment=Qt.AlignLeft | Qt.AlignVCenter
            )
            view.setFixedHeight(ax.size().height())
            ax.line = SliderLine(orientation=Qt.Horizontal,
                                 length=ax.size().height())
            scene.addItem(ax.line)
            return view, ax

        self.top_axis_view, self.top_axis = axis_view("top")
        self.mainArea.layout().setSpacing(1)
        self.mainArea.layout().addWidget(self.top_axis_view)
        self.mainArea.layout().addWidget(self.view)
        self.bottom_axis_view, self.bottom_axis = axis_view("bottom")
        self.mainArea.layout().addWidget(self.bottom_axis_view)

        self._main_graphics = QGraphicsWidget()
        self._main_layout = QGraphicsLinearLayout(Qt.Horizontal)
        self._main_layout.setSpacing(1)

        self._main_graphics.setLayout(self._main_layout)
        self.scene.addItem(self._main_graphics)

        self.dendrogram = DendrogramWidget()
        self.dendrogram.setSizePolicy(QSizePolicy.MinimumExpanding,
                                      QSizePolicy.MinimumExpanding)
        self.dendrogram.selectionChanged.connect(self._invalidate_output)
        self.dendrogram.selectionEdited.connect(self._selection_edited)

        fm = self.fontMetrics()
        self.dendrogram.setContentsMargins(
            5, fm.lineSpacing() / 2,
            5, fm.lineSpacing() / 2
        )
        self.labels = GraphicsSimpleTextList()
        self.labels.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred)
        self.labels.setAlignment(Qt.AlignLeft)
        self.labels.setMaximumWidth(200)
        self.labels.layout().setSpacing(0)

        self._main_layout.addItem(self.dendrogram)
        self._main_layout.addItem(self.labels)

        self._main_layout.setAlignment(
            self.dendrogram, Qt.AlignLeft | Qt.AlignVCenter)
        self._main_layout.setAlignment(
            self.labels, Qt.AlignLeft | Qt.AlignVCenter)

        self.view.viewport().installEventFilter(self)
        self.top_axis_view.viewport().installEventFilter(self)
        self.bottom_axis_view.viewport().installEventFilter(self)
        self._main_graphics.installEventFilter(self)

        self.cut_line = SliderLine(self.dendrogram,
                                   orientation=Qt.Horizontal)
        self.cut_line.valueChanged.connect(self._dendrogram_slider_changed)
        self.cut_line.hide()

        self.bottom_axis.line.valueChanged.connect(self._axis_slider_changed)
        self.top_axis.line.valueChanged.connect(self._axis_slider_changed)
        self.dendrogram.geometryChanged.connect(self._dendrogram_geom_changed)
        self._set_cut_line_visible(self.selection_method == 1)

    def set_distances(self, matrix):
        self.matrix = matrix
        self._invalidate_clustering()
        self._set_items(matrix.row_items if matrix is not None else None)

    def _set_items(self, items):
        self.items = items
        if items is None:
            self.label_cb.model()[:] = ["None", "Enumeration"]
        elif isinstance(items, Orange.data.Table):
            vars = list(items.domain)
            self.label_cb.model()[:] = ["None", "Enumeration"] + vars
        elif isinstance(items, list) and \
                all(isinstance(var, Orange.data.Variable) for var in items):
            self.label_cb.model()[:] = ["None", "Enumeration", "Name"]
        else:
            self.label_cb.model()[:] = ["None", "Enumeration"]
        self.annotation_idx = min(self.annotation_idx,
                                  len(self.label_cb.model()) - 1)

    def handleNewSignals(self):
        self._update_labels()

    def _clear_plot(self):
        self.labels.set_labels([])
        self.dendrogram.set_root(None)

    def _set_displayed_root(self, root):
        self._clear_plot()
        self._displayed_root = root
        self.dendrogram.set_root(root)

        self._update_labels()

        self._main_graphics.resize(
            self._main_graphics.size().width(),
            self._main_graphics.sizeHint(Qt.PreferredSize).height()
        )
        self._main_graphics.layout().activate()

    def _update(self):
        self._clear_plot()

        distances = self.matrix

        if distances is not None:
            # Convert to flat upper triangular distances
            i, j = numpy.triu_indices(distances.X.shape[0], k=1)
            distances = distances.X[i, j]

            method = LINKAGE[self.linkage].lower()
            Z = scipy.cluster.hierarchy.linkage(
                distances, method=method
            )
            tree = tree_from_linkage(Z)
            self.linkmatrix = Z
            self.root = tree

            self.top_axis.setRange(tree.value.height, 0.0)
            self.bottom_axis.setRange(tree.value.height, 0.0)

            if self.pruning:
                self._set_displayed_root(prune(tree, level=self.max_depth))
            else:
                self._set_displayed_root(tree)
        else:
            self.linkmatrix = None
            self.root = None
            self._set_displayed_root(None)

        self._apply_selection()

    def _update_labels(self):
        labels = []
        if self.root and self._displayed_root:
            indices = [leaf.value.index for leaf in leaves(self.root)]

            if self.annotation_idx == 0:
                labels = []
            elif self.annotation_idx == 1:
                labels = [str(i) for i in indices]
            elif isinstance(self.items, Orange.data.Table):
                var = self.label_cb.model()[self.annotation_idx]
                col = self.items[:, var]
                labels = [var.repr_val(next(iter(row))) for row in col]
                labels = [labels[idx] for idx in indices]
            else:
                labels = []

            if labels and self._displayed_root is not self.root:
                joined = leaves(self._displayed_root)
                labels = [", ".join(labels[leaf.value.first: leaf.value.last])
                          for leaf in joined]

        self.labels.set_labels(labels)
        self.labels.setMinimumWidth(1 if labels else -1)

    def _invalidate_clustering(self):
        self._update()
        self._update_labels()

    def _invalidate_output(self):
        self.commit()

    def _invalidate_pruning(self):
        if self.root:
            selection = self.dendrogram.selected_nodes()
            ranges = [node.value.range for node in selection]
            if self.pruning:
                self._set_displayed_root(
                    prune(self.root, level=self.max_depth))
            else:
                self._set_displayed_root(self.root)
            selected = [node for node in preorder(self._displayed_root)
                        if node.value.range in ranges]

            self.dendrogram.set_selected_clusters(selected)

        self._apply_selection()

    def commit(self):
        items = getattr(self.matrix, "items", self.items)
        if not items:
            # nothing to commit
            return

        selection = self.dendrogram.selected_nodes()
        selection = sorted(selection, key=lambda c: c.value.first)

        indices = [leaf.value.index for leaf in leaves(self.root)]

        maps = [indices[node.value.first:node.value.last]
                for node in selection]

        selected_indices = list(chain(*maps))
        unselected_indices = sorted(set(range(self.root.value.last)) -
                                    set(selected_indices))

        selected = [items[k] for k in selected_indices]
        unselected = [items[k] for k in unselected_indices]

        if not selected:
            self.send("Selected Data", None)
            self.send("Other Data", None)
            return
        selected_data = unselected_data = None

        if isinstance(items, Orange.data.Table):
            c = numpy.zeros(len(items))

            for i, indices in enumerate(maps):
                c[indices] = i
            c[unselected_indices] = len(maps)

            mask = c != len(maps)

            if self.append_clusters:
                clust_var = Orange.data.DiscreteVariable(
                    str(self.cluster_name),
                    values=["Cluster {}".format(i + 1)
                            for i in range(len(maps))] +
                           ["Other"], ordered=True
                )
                data, domain = items, items.domain

                attrs = domain.attributes
                class_ = domain.class_vars
                metas = domain.metas

                if self.cluster_role == self.AttributeRole:
                    attrs = attrs + (clust_var,)
                elif self.cluster_role == self.ClassRole:
                    class_ = class_ + (clust_var,)
                elif self.cluster_role == self.MetaRole:
                    metas = metas + (clust_var,)

                domain = Orange.data.Domain(attrs, class_, metas)
                data = Orange.data.Table(domain, data)
                data.get_column_view(clust_var)[0][:] = c
            else:
                data = items

            if selected:
                selected_data = data[mask]
            if unselected:
                unselected_data = data[~mask]

        self.send("Selected Data", selected_data)
        self.send("Other Data", unselected_data)

    def sizeHint(self):
        return QSize(800, 500)

    def eventFilter(self, obj, event):
        if obj is self.view.viewport() and event.type() == QEvent.Resize:
            width = self.view.viewport().width() - 2
            self._main_graphics.setMaximumWidth(width)
            self._main_graphics.setMinimumWidth(width)
            self._main_graphics.layout().activate()
        elif event.type() == QEvent.MouseButtonPress and \
                (obj is self.top_axis_view.viewport() or
                 obj is self.bottom_axis_view.viewport()):
            self.selection_method = 1
            # Map click point to cut line local coordinates
            pos = self.top_axis_view.mapToScene(event.pos())
            cut = self.top_axis.line.mapFromScene(pos)
            self.top_axis.line.setValue(cut.x())
            # update the line visibility, output, ...
            self._selection_method_changed()

        return super().eventFilter(obj, event)

    def _dendrogram_geom_changed(self):
        pos = self.dendrogram.pos_at_height(self.cutoff_height)
        geom = self.dendrogram.geometry()
        crect = self.dendrogram.contentsRect()

        self._set_slider_value(pos.x(), geom.width())
        self.cut_line.setLength(geom.height())

        self.top_axis.resize(crect.width(), self.top_axis.height())
        self.top_axis.setPos(geom.left() + crect.left(), 0)
        self.top_axis.line.setPos(self.cut_line.scenePos().x(), 0)

        self.bottom_axis.resize(crect.width(), self.bottom_axis.height())
        self.bottom_axis.setPos(geom.left() + crect.left(), 0)
        self.bottom_axis.line.setPos(self.cut_line.scenePos().x(), 0)

        geom = self._main_graphics.geometry()
        assert geom.topLeft() == QPointF(0, 0)
        self.scene.setSceneRect(geom)

        geom.setHeight(self.top_axis.size().height())

        self.top_axis.scene().setSceneRect(geom)
        self.bottom_axis.scene().setSceneRect(geom)

    def _axis_slider_changed(self, value):
        self.cut_line.setValue(value)

    def _dendrogram_slider_changed(self, value):
        p = QPointF(value, 0)
        cl_height = self.dendrogram.height_at(p)

        self.set_cutoff_height(cl_height)

        # Sync the cut positions between the dendrogram and the axis.
        self._set_slider_value(value, self.dendrogram.size().width())

    def _set_slider_value(self, value, span):
        with blocked(self.cut_line):
            self.cut_line.setValue(value)
            self.cut_line.setRange(0, span)

        with blocked(self.top_axis.line):
            self.top_axis.line.setValue(value)
            self.top_axis.line.setRange(0, span)

        with blocked(self.bottom_axis.line):
            self.bottom_axis.line.setValue(value)
            self.bottom_axis.line.setRange(0, span)

    def set_cutoff_height(self, height):
        self.cutoff_height = height
        if self.root:
            self.cut_ratio = 100 * height / self.root.value.height
        self.select_max_height(height)

    def _set_cut_line_visible(self, visible):
        self.cut_line.setVisible(visible)
        self.top_axis.line.setVisible(visible)
        self.bottom_axis.line.setVisible(visible)

    def select_top_n(self, n):
        root = self._displayed_root
        if root:
            clusters = top_clusters(root, n)
            self.dendrogram.set_selected_clusters(clusters)

    def select_max_height(self, height):
        root = self._displayed_root
        if root:
            clusters = clusters_at_height(root, height)
            self.dendrogram.set_selected_clusters(clusters)

    def _selection_method_changed(self):
        self._set_cut_line_visible(self.selection_method == 1)
        if self.root:
            self._apply_selection()

    def _apply_selection(self):
        if not self.root:
            return

        if self.selection_method == 0:
            pass
        elif self.selection_method == 1:
            height = self.cut_ratio * self.root.value.height / 100
            self.set_cutoff_height(height)
            pos = self.dendrogram.pos_at_height(height)
            self._set_slider_value(pos.x(), self.dendrogram.size().width())
        elif self.selection_method == 2:
            self.select_top_n(self.top_n)

    def _selection_edited(self):
        # Selection was edited by clicking on a cluster in the
        # dendrogram view.
        self.selection_method = 0
        self._selection_method_changed()
示例#2
0
class OWHierarchicalClustering(widget.OWWidget):
    name = "Hierarchical Clustering"
    description = ("Hierarchical clustering based on distance matrix, and "
                   "a dendrogram viewer.")
    icon = "icons/HierarchicalClustering.svg"
    priority = 2100

    inputs = [("Distances", Orange.misc.DistMatrix, "set_distances")]

    outputs = [("Selected Data", Orange.data.Table),
               ("Other Data", Orange.data.Table)]

    #: Selected linkage
    linkage = settings.Setting(1)
    #: Index of the selected annotation item (variable, ...)
    annotation_idx = settings.Setting(0)
    #: Selected tree pruning (none/max depth)
    pruning = settings.Setting(0)
    #: Maximum depth when max depth pruning is selected
    max_depth = settings.Setting(10)

    #: Selected cluster selection method (none, cut distance, top n)
    selection_method = settings.Setting(0)
    #: Cut height ratio wrt root height
    cut_ratio = settings.Setting(75.0)
    #: Number of top clusters to select
    top_n = settings.Setting(3)

    append_clusters = settings.Setting(True)
    cluster_role = settings.Setting(2)
    cluster_name = settings.Setting("Cluster")
    autocommit = settings.Setting(False)

    #: Cluster variable domain role
    AttributeRole, ClassRole, MetaRole = 0, 1, 2

    def __init__(self, parent=None):
        super().__init__(parent)

        self.matrix = None
        self.items = None
        self.linkmatrix = None
        self.root = None
        self._displayed_root = None
        self.cutoff_height = 0.0
        self._invalidated = False

        gui.comboBox(gui.widgetBox(self.controlArea, "Linkage"),
                     self,
                     "linkage",
                     items=LINKAGE,
                     callback=self._invalidate_clustering)

        box = gui.widgetBox(self.controlArea, "Annotation")
        self.label_cb = gui.comboBox(box,
                                     self,
                                     "annotation_idx",
                                     callback=self._update_labels)

        self.label_cb.setModel(itemmodels.VariableListModel())
        self.label_cb.model()[:] = ["None", "Enumeration"]

        box = gui.radioButtons(self.controlArea,
                               self,
                               "pruning",
                               box="Pruning",
                               callback=self._invalidate_pruning)
        grid = QGridLayout()
        box.layout().addLayout(grid)
        grid.addWidget(gui.appendRadioButton(box, "None", addToLayout=False),
                       0, 0)
        self.max_depth_spin = gui.spin(box,
                                       self,
                                       "max_depth",
                                       minv=1,
                                       maxv=100,
                                       callback=self._invalidate_pruning,
                                       keyboardTracking=False)

        grid.addWidget(
            gui.appendRadioButton(box, "Max depth", addToLayout=False), 1, 0)
        grid.addWidget(self.max_depth_spin, 1, 1)

        box = gui.radioButtons(self.controlArea,
                               self,
                               "selection_method",
                               box="Selection",
                               callback=self._selection_method_changed)

        grid = QGridLayout()
        box.layout().addLayout(grid)
        grid.addWidget(gui.appendRadioButton(box, "Manual", addToLayout=False),
                       0, 0)
        grid.addWidget(
            gui.appendRadioButton(box, "Height ratio", addToLayout=False), 1,
            0)
        self.cut_ratio_spin = gui.spin(box,
                                       self,
                                       "cut_ratio",
                                       0,
                                       100,
                                       step=1e-1,
                                       spinType=float,
                                       callback=self._selection_method_changed)
        self.cut_ratio_spin.setSuffix("%")

        grid.addWidget(self.cut_ratio_spin, 1, 1)

        grid.addWidget(gui.appendRadioButton(box, "Top N", addToLayout=False),
                       2, 0)
        self.top_n_spin = gui.spin(box,
                                   self,
                                   "top_n",
                                   1,
                                   20,
                                   callback=self._selection_method_changed)
        grid.addWidget(self.top_n_spin, 2, 1)
        box.layout().addLayout(grid)

        self.controlArea.layout().addStretch()

        box = gui.widgetBox(self.controlArea, "Output")
        gui.checkBox(box,
                     self,
                     "append_clusters",
                     "Append cluster IDs",
                     callback=self._invalidate_output)

        ibox = gui.indentedBox(box)
        name_edit = gui.lineEdit(ibox, self, "cluster_name")
        name_edit.editingFinished.connect(self._invalidate_output)

        cb = gui.comboBox(
            ibox,
            self,
            "cluster_role",
            callback=self._invalidate_output,
            items=["Attribute", "Class variable", "Meta variable"])
        form = QFormLayout(fieldGrowthPolicy=QFormLayout.AllNonFixedFieldsGrow,
                           labelAlignment=Qt.AlignLeft,
                           spacing=8)
        form.addRow("Name", name_edit)
        form.addRow("Place", cb)

        ibox.layout().addSpacing(5)
        ibox.layout().addLayout(form)
        ibox.layout().addSpacing(5)

        cb = gui.checkBox(box, self, "autocommit", "Commit automatically")
        b = gui.button(box, self, "Commit", callback=self.commit, default=True)
        gui.setStopper(self, b, cb, "_invalidated", callback=self.commit)

        self.scene = QGraphicsScene()
        self.view = QGraphicsView(
            self.scene,
            horizontalScrollBarPolicy=Qt.ScrollBarAlwaysOff,
            verticalScrollBarPolicy=Qt.ScrollBarAlwaysOn,
            alignment=Qt.AlignLeft | Qt.AlignVCenter)

        def axis_view(orientation):
            ax = pg.AxisItem(orientation=orientation, maxTickLength=7)
            scene = QGraphicsScene()
            scene.addItem(ax)
            view = QGraphicsView(
                scene,
                horizontalScrollBarPolicy=Qt.ScrollBarAlwaysOff,
                verticalScrollBarPolicy=Qt.ScrollBarAlwaysOn,
                alignment=Qt.AlignLeft | Qt.AlignVCenter)
            view.setFixedHeight(ax.size().height())
            ax.line = SliderLine(orientation=Qt.Horizontal,
                                 length=ax.size().height())
            scene.addItem(ax.line)
            return view, ax

        self.top_axis_view, self.top_axis = axis_view("top")
        self.mainArea.layout().setSpacing(1)
        self.mainArea.layout().addWidget(self.top_axis_view)
        self.mainArea.layout().addWidget(self.view)
        self.bottom_axis_view, self.bottom_axis = axis_view("bottom")
        self.mainArea.layout().addWidget(self.bottom_axis_view)

        self._main_graphics = QGraphicsWidget()
        self._main_layout = QGraphicsLinearLayout(Qt.Horizontal)
        self._main_layout.setSpacing(1)

        self._main_graphics.setLayout(self._main_layout)
        self.scene.addItem(self._main_graphics)

        self.dendrogram = DendrogramWidget()
        self.dendrogram.setSizePolicy(QSizePolicy.MinimumExpanding,
                                      QSizePolicy.MinimumExpanding)
        self.dendrogram.selectionChanged.connect(self._invalidate_output)
        self.dendrogram.selectionEdited.connect(self._selection_edited)

        fm = self.fontMetrics()
        self.dendrogram.setContentsMargins(5,
                                           fm.lineSpacing() / 2, 5,
                                           fm.lineSpacing() / 2)
        self.labels = GraphicsSimpleTextList()
        self.labels.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred)
        self.labels.setAlignment(Qt.AlignLeft)
        self.labels.setMaximumWidth(200)
        self.labels.layout().setSpacing(0)

        self._main_layout.addItem(self.dendrogram)
        self._main_layout.addItem(self.labels)

        self._main_layout.setAlignment(self.dendrogram,
                                       Qt.AlignLeft | Qt.AlignVCenter)
        self._main_layout.setAlignment(self.labels,
                                       Qt.AlignLeft | Qt.AlignVCenter)

        self.view.viewport().installEventFilter(self)
        self.top_axis_view.viewport().installEventFilter(self)
        self.bottom_axis_view.viewport().installEventFilter(self)
        self._main_graphics.installEventFilter(self)

        self.cut_line = SliderLine(self.dendrogram, orientation=Qt.Horizontal)
        self.cut_line.valueChanged.connect(self._dendrogram_slider_changed)
        self.cut_line.hide()

        self.bottom_axis.line.valueChanged.connect(self._axis_slider_changed)
        self.top_axis.line.valueChanged.connect(self._axis_slider_changed)
        self.dendrogram.geometryChanged.connect(self._dendrogram_geom_changed)
        self._set_cut_line_visible(self.selection_method == 1)

    def set_distances(self, matrix):
        self.matrix = matrix
        self._invalidate_clustering()
        self._set_items(matrix.row_items if matrix is not None else None)

    def _set_items(self, items):
        self.items = items
        if items is None:
            self.label_cb.model()[:] = ["None", "Enumeration"]
        elif isinstance(items, Orange.data.Table):
            vars = list(items.domain)
            self.label_cb.model()[:] = ["None", "Enumeration"] + vars
        elif isinstance(items, list) and \
                all(isinstance(var, Orange.data.Variable) for var in items):
            self.label_cb.model()[:] = ["None", "Enumeration", "Name"]
        else:
            self.label_cb.model()[:] = ["None", "Enumeration"]
        self.annotation_idx = min(self.annotation_idx,
                                  len(self.label_cb.model()) - 1)

    def handleNewSignals(self):
        self._update_labels()

    def _clear_plot(self):
        self.labels.set_labels([])
        self.dendrogram.set_root(None)

    def _set_displayed_root(self, root):
        self._clear_plot()
        self._displayed_root = root
        self.dendrogram.set_root(root)

        self._update_labels()

        self._main_graphics.resize(
            self._main_graphics.size().width(),
            self._main_graphics.sizeHint(Qt.PreferredSize).height())
        self._main_graphics.layout().activate()

    def _update(self):
        self._clear_plot()

        distances = self.matrix

        if distances is not None:
            # Convert to flat upper triangular distances
            i, j = numpy.triu_indices(distances.X.shape[0], k=1)
            distances = distances.X[i, j]

            method = LINKAGE[self.linkage].lower()
            Z = scipy.cluster.hierarchy.linkage(distances, method=method)
            tree = tree_from_linkage(Z)
            self.linkmatrix = Z
            self.root = tree

            self.top_axis.setRange(tree.value.height, 0.0)
            self.bottom_axis.setRange(tree.value.height, 0.0)

            if self.pruning:
                self._set_displayed_root(prune(tree, level=self.max_depth))
            else:
                self._set_displayed_root(tree)
        else:
            self.linkmatrix = None
            self.root = None
            self._set_displayed_root(None)

        self._apply_selection()

    def _update_labels(self):
        labels = []
        if self.root and self._displayed_root:
            indices = [leaf.value.index for leaf in leaves(self.root)]

            if self.annotation_idx == 0:
                labels = []
            elif self.annotation_idx == 1:
                labels = [str(i) for i in indices]
            elif isinstance(self.items, Orange.data.Table):
                var = self.label_cb.model()[self.annotation_idx]
                col = self.items[:, var]
                labels = [var.repr_val(next(iter(row))) for row in col]
                labels = [labels[idx] for idx in indices]
            else:
                labels = []

            if labels and self._displayed_root is not self.root:
                joined = leaves(self._displayed_root)
                labels = [
                    ", ".join(labels[leaf.value.first:leaf.value.last])
                    for leaf in joined
                ]

        self.labels.set_labels(labels)
        self.labels.setMinimumWidth(1 if labels else -1)

    def _invalidate_clustering(self):
        self._update()
        self._update_labels()

    def _invalidate_output(self):
        self._invalidated = True
        if self.autocommit:
            self.commit()

    def _invalidate_pruning(self):
        if self.root:
            selection = self.dendrogram.selected_nodes()
            ranges = [node.value.range for node in selection]
            if self.pruning:
                self._set_displayed_root(prune(self.root,
                                               level=self.max_depth))
            else:
                self._set_displayed_root(self.root)
            selected = [
                node for node in preorder(self._displayed_root)
                if node.value.range in ranges
            ]

            self.dendrogram.set_selected_clusters(selected)

        self._apply_selection()

    def commit(self):
        self._invalidated = False

        items = getattr(self.matrix, "items", self.items)
        if not items:
            # nothing to commit
            return

        selection = self.dendrogram.selected_nodes()
        selection = sorted(selection, key=lambda c: c.value.first)

        indices = [leaf.value.index for leaf in leaves(self.root)]

        maps = [
            indices[node.value.first:node.value.last] for node in selection
        ]

        selected_indices = list(chain(*maps))
        unselected_indices = sorted(
            set(range(self.root.value.last)) - set(selected_indices))

        selected = [items[k] for k in selected_indices]
        unselected = [items[k] for k in unselected_indices]

        if not selected:
            self.send("Selected Data", None)
            self.send("Other Data", None)
            return
        selected_data = unselected_data = None

        if isinstance(items, Orange.data.Table):
            c = numpy.zeros(len(items))

            for i, indices in enumerate(maps):
                c[indices] = i
            c[unselected_indices] = len(maps)

            mask = c != len(maps)

            if self.append_clusters:
                clust_var = Orange.data.DiscreteVariable(
                    str(self.cluster_name),
                    values=[
                        "Cluster {}".format(i + 1) for i in range(len(maps))
                    ] + ["Other"])
                data, domain = items, items.domain

                attrs = domain.attributes
                class_ = domain.class_vars
                metas = domain.metas

                X, Y, M = data.X, data.Y, data.metas
                if self.cluster_role == self.AttributeRole:
                    attrs = attrs + (clust_var, )
                    X = numpy.c_[X, c]
                elif self.cluster_role == self.ClassRole:
                    class_ = class_ + (clust_var, )
                    Y = numpy.c_[Y, c]
                elif self.cluster_role == self.MetaRole:
                    metas = metas + (clust_var, )
                    M = numpy.c_[M, c]

                domain = Orange.data.Domain(attrs, class_, metas)
                data = Orange.data.Table(domain, X, Y, M)
            else:
                data = items

            if selected:
                selected_data = data[mask]
            if unselected:
                unselected_data = data[~mask]

        self.send("Selected Data", selected_data)
        self.send("Other Data", unselected_data)

    def sizeHint(self):
        return QSize(800, 500)

    def eventFilter(self, obj, event):
        if obj is self.view.viewport() and event.type() == QEvent.Resize:
            width = self.view.viewport().width() - 2
            self._main_graphics.setMaximumWidth(width)
            self._main_graphics.setMinimumWidth(width)
            self._main_graphics.layout().activate()
        elif event.type() == QEvent.MouseButtonPress and \
                (obj is self.top_axis_view.viewport() or
                 obj is self.bottom_axis_view.viewport()):
            self.selection_method = 1
            # Map click point to cut line local coordinates
            pos = self.top_axis_view.mapToScene(event.pos())
            cut = self.top_axis.line.mapFromScene(pos)
            self.top_axis.line.setValue(cut.x())
            # update the line visibility, output, ...
            self._selection_method_changed()

        return super().eventFilter(obj, event)

    def _dendrogram_geom_changed(self):
        pos = self.dendrogram.pos_at_height(self.cutoff_height)
        geom = self.dendrogram.geometry()
        crect = self.dendrogram.contentsRect()

        self._set_slider_value(pos.x(), geom.width())
        self.cut_line.setLength(geom.height())

        self.top_axis.resize(crect.width(), self.top_axis.height())
        self.top_axis.setPos(geom.left() + crect.left(), 0)
        self.top_axis.line.setPos(self.cut_line.scenePos().x(), 0)

        self.bottom_axis.resize(crect.width(), self.bottom_axis.height())
        self.bottom_axis.setPos(geom.left() + crect.left(), 0)
        self.bottom_axis.line.setPos(self.cut_line.scenePos().x(), 0)

        geom = self._main_graphics.geometry()
        assert geom.topLeft() == QPointF(0, 0)
        self.scene.setSceneRect(geom)

        geom.setHeight(self.top_axis.size().height())

        self.top_axis.scene().setSceneRect(geom)
        self.bottom_axis.scene().setSceneRect(geom)

    def _axis_slider_changed(self, value):
        self.cut_line.setValue(value)

    def _dendrogram_slider_changed(self, value):
        p = QPointF(value, 0)
        cl_height = self.dendrogram.height_at(p)

        self.set_cutoff_height(cl_height)

        # Sync the cut positions between the dendrogram and the axis.
        self._set_slider_value(value, self.dendrogram.size().width())

    def _set_slider_value(self, value, span):
        with blocked(self.cut_line):
            self.cut_line.setValue(value)
            self.cut_line.setRange(0, span)

        with blocked(self.top_axis.line):
            self.top_axis.line.setValue(value)
            self.top_axis.line.setRange(0, span)

        with blocked(self.bottom_axis.line):
            self.bottom_axis.line.setValue(value)
            self.bottom_axis.line.setRange(0, span)

    def set_cutoff_height(self, height):
        self.cutoff_height = height
        if self.root:
            self.cut_ratio = 100 * height / self.root.value.height
        self.select_max_height(height)

    def _set_cut_line_visible(self, visible):
        self.cut_line.setVisible(visible)
        self.top_axis.line.setVisible(visible)
        self.bottom_axis.line.setVisible(visible)

    def select_top_n(self, n):
        root = self._displayed_root
        if root:
            clusters = top_clusters(root, n)
            self.dendrogram.set_selected_clusters(clusters)

    def select_max_height(self, height):
        root = self._displayed_root
        if root:
            clusters = clusters_at_height(root, height)
            self.dendrogram.set_selected_clusters(clusters)

    def _selection_method_changed(self):
        self._set_cut_line_visible(self.selection_method == 1)
        if self.root:
            self._apply_selection()

    def _apply_selection(self):
        if not self.root:
            return

        if self.selection_method == 0:
            pass
        elif self.selection_method == 1:
            height = self.cut_ratio * self.root.value.height / 100
            self.set_cutoff_height(height)
            pos = self.dendrogram.pos_at_height(height)
            self._set_slider_value(pos.x(), self.dendrogram.size().width())
        elif self.selection_method == 2:
            self.select_top_n(self.top_n)

    def _selection_edited(self):
        # Selection was edited by clicking on a cluster in the
        # dendrogram view.
        self.selection_method = 0
        self._selection_method_changed()
示例#3
0
class Channel(QGraphicsWidget):

    def __init__(self , parent):
        QGraphicsWidget.__init__(self)
        self.veromix = parent
        self.index = -1
        self.pa = parent.getPulseAudio()
        self.set_name("")
        self.deleted = True
        self.pa_sink = None
        self.extended_panel_shown = False
        self.extended_panel= None
        self.show_meter = True
        self.expander = None
        self.popup_menu = None
        self.card_settings = None
        self.menus = None
        self.port_actions = None

        self.double_click_filter = ChannelEventFilter(self)
        self.installEventFilter(self.double_click_filter)

        self.init()
        self.setSizePolicy(QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed,True))

    def init(self):
        self.layout = QGraphicsLinearLayout(Qt.Vertical)
        self.layout.setContentsMargins(2,2,2,0)
        self.setLayout(self.layout)
        self.initArrangement()
        self.composeArrangement()
        self.setAcceptDrops(True)
        self._on_upate_expander_enabled()

    def initArrangement(self):
        self.create_frame()
        self.create_panel()
        self.createMute()
        self.createMiddle()
        self.create_expander()

    def composeArrangement(self):
        self.layout.addItem(self.frame)
        self.frame_layout.addItem(self.panel)
        self.panel_layout.addItem(self.mute)
        self.panel_layout.addItem(self.middle)

    def create_frame(self):
        self.frame = Plasma.Frame()
        self.frame_layout = QGraphicsLinearLayout(Qt.Vertical)
        self.frame.setEnabledBorders (Plasma.FrameSvg.NoBorder)
        self.frame.setFrameShadow(Plasma.Frame.Plain)
        self.frame_layout.setContentsMargins(0,0,0,0)
        self.frame.setLayout(self.frame_layout)

    def create_panel(self):
        self.panel = QGraphicsWidget()
        self.panel_layout = QGraphicsLinearLayout(Qt.Horizontal)
        self.panel_layout.setContentsMargins(6,8,10,6)
        self.panel.setLayout(self.panel_layout)

    def createMute(self):
        self.mute = MuteButton(self)
        self.connect(self.mute, SIGNAL("clicked()"), self.on_mute_cb)

    def createMiddle(self):
        self.middle = QGraphicsWidget()
        self.middle_layout = QGraphicsLinearLayout(Qt.Vertical)
        #self.middle_layout.setContentsMargins(6,8,6,0)
        self.middle_layout.setContentsMargins(0,0,0,0)
        self.middle.setLayout(self.middle_layout)
        self.middle.setSizePolicy(QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Minimum))
        self.createSlider()
        self.middle_layout.addItem(self.slider)

    def createSlider(self):
        self.slider = MeterSlider(None, self.veromix.is_slider_unit_value_visible())
        self.slider.installEventFilter(self.double_click_filter)
        self.slider.set_meter_visible(self.veromix.is_meter_visible())
        self.slider.setOrientation(Qt.Horizontal)
        self.slider.setMaximum(self.veromix.get_max_volume_value())
        self.slider.setMinimum(0)
        self.slider.volumeChanged.connect( self.on_slider_cb)

    def create_expander(self):
        self.expander = Plasma.IconWidget(self.panel)
        self.expander.setZValue(10)
        self.connect(self, SIGNAL("geometryChanged()"), self._resize_widgets)
        self.expander.setSizePolicy(QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed))
        self.expander.clicked.connect(self.on_expander_clicked)
        self.expander.setSvg("widgets/arrows", "left-arrow")

    def create_context_menu(self, event):
        self.popup_menu = QMenu()
        self.popup_menu.triggered.connect(self.on_contextmenu_clicked)
        self.context_menu_create_custom()
        self.context_menu_create_mute()
        self.context_menu_create_meter()
        self.context_menu_create_unlock_channels()
        self.context_menu_create_effects()
        self.create_menu_kill_sink()
        self.context_menu_create_settings()
        if event:
            self.popup_menu.exec_(event.screenPos())
        else:
            self.popup_menu.exec_(QCursor.pos())

    def context_menu_create_mute(self):
        action_mute = QAction(i18n("Muted"), self.popup_menu)
        self.popup_menu.addAction(action_mute)
        action_mute.setCheckable(True)
        action_mute.setChecked(self.isMuted())
        action_mute.triggered.connect(self.on_mute_cb)

    def context_menu_create_meter(self):
        action_meter = QAction(i18n("Volume meter"), self.popup_menu)
        self.popup_menu.addAction(action_meter)
        action_meter.setCheckable(True)
        action_meter.setChecked(self.pa_sink.has_monitor())
        action_meter.triggered.connect(self.on_meter_cb)

    def context_menu_create_unlock_channels(self):
        action_unlock = QAction(i18n("Unlock channels"), self.popup_menu)
        self.popup_menu.addAction(action_unlock)
        action_unlock.setCheckable(True)
        action_unlock.setChecked(self.extended_panel_shown)
        action_unlock.triggered.connect(self.toggle_channel_lock)

    def context_menu_create_ports(self):
        self.port_actions = {}
        if len(self.pa_sink.ports.keys()) > 1:
            ports_menu = QMenu(i18n("Ports"), self.popup_menu)
            ports = self.pa_sink.ports

            for port in ports.keys():
                action = QAction(in_unicode(ports[port]), self.popup_menu)
                self.port_actions[action]=port
                if port == self.pa_sink.active_port:
                    action.setCheckable(True)
                    action.setChecked(True)
                else:
                    action.setChecked(False)
                    action.setCheckable(False)
                ports_menu.addAction(action)
            self.popup_menu.addMenu(ports_menu)

    def create_menu_kill_sink(self):
        pass

    def context_menu_create_sounddevices(self):
        self.card_settings = {}
        self.menus = []
        for card in self.veromix.card_infos.values():
            current = self.veromix.get_card_info_for(self)
            if current != None and  current.get_description() == card.get_description():
                card_menu = QMenu(i18n("Profile"), self.popup_menu)
                self.popup_menu.addMenu(card_menu)
            else:
                card_menu = QMenu(card.get_description(), self.popup_menu)
                self.menus.append(card_menu)
            active_profile_name = card.get_active_profile_name()
            self.profiles = card.get_profiles()
            for profile in self.profiles:
                action = QAction(in_unicode(profile.description), card_menu)
                self.card_settings[action] = card
                if profile.name == active_profile_name:
                    action.setCheckable(True)
                    action.setChecked(True)
                card_menu.addAction(action)

    def context_menu_create_sounddevices_other(self):
        if len(self.menus) > 0:
            self.popup_menu.addSeparator()
            for each in self.menus:
                self.popup_menu.addMenu(each)

    def context_menu_create_custom(self):
        pass

    def context_menu_create_effects(self):
        pass

    def context_menu_create_settings(self):
        self.popup_menu.addSeparator()
        action_settings = QAction(i18n("Veromix Settings"), self.popup_menu)
        self.popup_menu.addAction(action_settings)
        action_settings.triggered.connect(self.veromix.applet.showConfigurationInterface)

    def _resize_widgets(self):
        self.expander.setPos(int(self.panel.size().width() - self.expander.size().width()) ,0)

    def on_double_clicked(self):
        self.slider.toggle_meter()
        self.pa_sink.toggle_monitor()
        self.slider.set_meter_value(0)

    def on_step_volume(self, up):
        vol = self.pa_sink.get_volume()
        STEP = 5
        if up:
            vol = vol + STEP
        else:
            vol = vol - STEP
        if vol < 0:
            vol = 0
        if vol > self.veromix.get_max_volume_value():
            vol = self.veromix.get_max_volume_value()
        self.setVolume(vol)

    def setVolume(self, value):
        vol = self.pa_sink.volumeDiffFor(value)
        if self.veromix.get_auto_mute():
            for c in vol:
                if c <= 0:
                    ## FIXME HACK for MurzNN this should be conditional
                    self.pa.set_sink_mute(self.index, True)
                    self.automatically_muted = True
                    return
            if self.automatically_muted :
                self.automatically_muted = False
                self.pa.set_sink_mute(self.index, False)
        self.set_channel_volumes(vol)

    def get_volume(self):
        if self.pa_sink:
            return self.pa_sink.get_volume()
        return [0]

    def on_expander_clicked(self):
        self.contextMenuEvent(None)

    def toggle_channel_lock(self):
        self.middle_layout.removeItem(self.slider)
        self.slider = None
        if (self.extended_panel_shown):
            self.extended_panel_shown = False
            self.expander.setSvg("widgets/arrows", "left-arrow")
            self.createSlider()
            self.middle_layout.addItem(self.slider)
        else:
            self.extended_panel_shown = True
            self.expander.setSvg("widgets/arrows", "down-arrow")
            self.slider = SinkChannelWidget(self.veromix, self)
            self.slider.installEventFilter(self.double_click_filter)
            self.middle_layout.addItem(self.slider)
        self.middle_layout.setContentsMargins(0,0,0,0)
        self.middle.setContentsMargins(0,0,0,0)
        self.update_with_info(self.pa_sink)
        self.veromix.check_geometries()

    def on_update_configuration(self):
        self.slider.set_meter_visible(self.veromix.is_meter_visible())
        self.slider.setMaximum(self.veromix.get_max_volume_value())
        self.slider.set_unit_value_visible(self.veromix.is_slider_unit_value_visible())
        self._on_upate_expander_enabled()

    def _on_upate_expander_enabled(self):
        if self.veromix.is_expander_enabled():
            self.expander.show()
        else:
            self.expander.hide()

    def on_contextmenu_clicked(self, action):
        if action in self.card_settings.keys():
            card = self.card_settings[action]
            for profile in card.get_profiles():
                if action.text() == profile.description:
                    self.veromix.pa.set_card_profile(card.index, profile.name)
        if action in self.port_actions.keys():
            self.pa_sink.set_port(self.port_actions[action])

    def contextMenuEvent(self,event):
        self.create_context_menu(event)

    def on_mute_cb(self):
        self.pa_sink.toggle_mute()

    def on_meter_cb(self):
        self.on_double_clicked()

    def sink_input_kill(self):
        self.pa_sink.kill()

    def set_channel_volumes(self, values):
        self.pa_sink.set_volume(values)

    def on_update_meter(self, index, value, number_of_sinks):
        if self.index == index:
            self.slider.set_meter_value(int(value))

    def update_with_info(self,info):
        self.pa_sink = info
        self.index = info.index
        self.update_label()
        self.updateIcon()
        if self.slider:
            self.slider.update_with_info(info)
        if self.extended_panel:
            self.extended_panel.update_with_info(info)
        self.update()

    def update_label(self):
        if self.pa_sink:
            self.set_name(self.pa_sink.name)

    def getOutputIndex(self):
        return self.index

    def sinkIndexFor( self, index):
        return (index * 100000) + 100000

    def updateIcon(self):
        pass

    def on_slider_cb(self, value):
        self.setVolume(value)

    def isDefaultSink(self):
        if self.pa_sink and "isdefault" in self.pa_sink.props:
            return self.pa_sink.props["isdefault"] == "True"
        return False

    def startDrag(self,event):
        pass

    def removeSlider(self):
        # if a slider is not visible, plasmoidviewer crashes if the slider is not removed.. (dont ask me)
        if self.slider:
            self.middle_layout.removeItem(self.slider)
        self.slider = None

    def isMuted(self):
        if self.pa_sink:
            return self.pa_sink.isMuted()
        return False

    def isSinkOutput(self):
        if self.pa_sink:
            return self.pa_sink.is_sinkoutput()
        return False

    def isSinkInput(self):
        if self.pa_sink:
            return self.pa_sink.is_sinkinput()
        return False

    def isSink(self):
        if self.pa_sink:
            return self.pa_sink.is_sink()
        return False

    def isNowplaying(self):
        return False

    def isSourceOutput(self):
        if self.pa_sink:
            return self.pa_sink.is_sourceoutput()
        return False

    def wheelEvent(self, event):
        if self.slider:
            self.slider.wheelEvent(event)

    def set_name(self, string):
        self._name = in_unicode(string)

    def name(self):
        return self._name

    def update_module_info(self, index, name, argument, n_used, auto_unload):
        pass

    def get_ladspa_type(self):
        return str(type(self))

    def get_pasink_name(self):
        return self.pa_sink.name

## LADSPA helpers

    def populate_presets_menu(self, target_menu, checked_item, add_actions):
        effect_menu = QMenu(i18n("Presets"), target_menu)
        if add_actions:
            self.action_save_preset = QAction(i18n("Save"),effect_menu)
            effect_menu.addAction(self.action_save_preset)
            if not self.is_preset():
                self.action_save_preset.setEnabled(False)

            self.action_save_as_preset = QAction(i18n("Save As..."),effect_menu)
            effect_menu.addAction(self.action_save_as_preset)
            effect_menu.addSeparator()

        for preset in LADSPAPresetLoader().presets():
            action = QAction(preset["preset_name"],effect_menu)
            effect_menu.addAction(action)
            if checked_item == preset["preset_name"]:
                action.setCheckable(True)
                action.setChecked(True)
                action.setEnabled(False)
        target_menu.addMenu(effect_menu)

    def populate_switch_effect_menu(self, target_menu, checked_item):
        effect_menu = QMenu(i18n("Effect"), target_menu)
        for preset in LADSPAEffects().effects():
            action = QAction(preset["preset_name"],effect_menu)
            effect_menu.addAction(action)
            if checked_item == preset["label"]:
                action.setCheckable(True)
                action.setChecked(True)
                action.setEnabled(False)
        target_menu.addMenu(effect_menu)

    def on_set_ladspa_effect(self, value, master):
        parameters = ""
        preset = None
        for p in LADSPAEffects().effects():
            if p["preset_name"] == value:
                parameters = "sink_name=" + urllib.quote(p["name"])
                preset = p

        for p in LADSPAPresetLoader().presets():
            if p["preset_name"] == value:
                parameters = "sink_name=" + urllib.quote(p["preset_name"])
                preset = p

        parameters =  parameters + " master=" + master + " "
        parameters =  parameters + " plugin=" + preset["plugin"]
        parameters =  parameters + " label=" + preset["label"]
        parameters =  parameters + " control=" + preset["control"]
        self.pa_sink.set_ladspa_sink(parameters)

    def next_focus(self, forward=True):
        channels = self.veromix.get_visible_channels()
        if len(channels) > 0:
            index = 0
            if self in channels:
                index = channels.index(self)
                if forward:
                    index = index + 1
                    if index >= len(channels):
                        index = 0
                else:
                    index = index - 1
                    if index < 0:
                        index = len(channels) - 1
            channels[index].set_focus()

    def set_focus(self):
        self.slider.set_focus()

    def pa_sink_proxy(self):
        return self.pa_sink