def __updateState(self): """ Update the widget with the new source/sink node signal descriptions. """ widget = QGraphicsWidget() widget.setLayout(QGraphicsGridLayout()) # Space between left and right anchors widget.layout().setHorizontalSpacing(50) left_node = EditLinksNode(self, direction=Qt.LeftToRight, node=self.source) left_node.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding) right_node = EditLinksNode(self, direction=Qt.RightToLeft, node=self.sink) right_node.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding) left_node.setMinimumWidth(150) right_node.setMinimumWidth(150) widget.layout().addItem(left_node, 0, 0) widget.layout().addItem(right_node, 0, 1) title_template = "<center><b>{0}<b></center>" left_title = GraphicsTextWidget(self) left_title.setHtml(title_template.format(escape(self.source.title))) left_title.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) right_title = GraphicsTextWidget(self) right_title.setHtml(title_template.format(escape(self.sink.title))) right_title.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) widget.layout().addItem(left_title, 1, 0, alignment=Qt.AlignHCenter | Qt.AlignTop) widget.layout().addItem(right_title, 1, 1, alignment=Qt.AlignHCenter | Qt.AlignTop) widget.setParentItem(self) max_w = max(left_node.sizeHint(Qt.PreferredSize).width(), right_node.sizeHint(Qt.PreferredSize).width()) # fix same size left_node.setMinimumWidth(max_w) right_node.setMinimumWidth(max_w) left_title.setMinimumWidth(max_w) right_title.setMinimumWidth(max_w) self.layout().addItem(widget) self.layout().activate() self.sourceNodeWidget = left_node self.sinkNodeWidget = right_node self.sourceNodeTitle = left_title self.sinkNodeTitle = right_title
def switchView(self, startup=False): if self.showsTabs: self.scrolled_panel_layout = QGraphicsLinearLayout(Qt.Vertical) scrolled_panel = QGraphicsWidget() scrolled_panel.setLayout(self.scrolled_panel_layout) self.scrolled_panel_layout.setSizePolicy(QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)) self.scrolled_panel_layout.addItem(self.source_panel) self.scrolled_panel_layout.addItem(self.sink_panel) else: scrolled_panel = Plasma.TabBar() scrolled_panel.addTab(i18n("Playback"), self.sink_panel) scrolled_panel.addTab(i18n("Record"), self.source_panel) self.source_panel.show() self.scrolled_panel = scrolled_panel self.showsTabs = not self.showsTabs self.scroller.setWidget(self.scrolled_panel) if not startup: self.check_geometries()
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()
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()
def replot_experiments(self): """Replot the whole quality plot. """ self.scene.clear() labels = [] max_dist = numpy.nanmax(list(filter(None, self.distances))) rug_widgets = [] group_pen = QPen(Qt.black) group_pen.setWidth(2) group_pen.setCapStyle(Qt.RoundCap) background_pen = QPen(QColor(0, 0, 250, 150)) background_pen.setWidth(1) background_pen.setCapStyle(Qt.RoundCap) main_widget = QGraphicsWidget() layout = QGraphicsGridLayout() attributes = self.data.domain.attributes if self.data is not None: for (group, indices), dist_vec in zip(self.groups, self.distances): indices_set = set(indices) rug_items = [] if dist_vec is not None: for i, attr in enumerate(attributes): # Is this a within group distance or background in_group = i in indices_set if in_group: rug_item = ClickableRugItem(dist_vec[i] / max_dist, 1.0, self.on_rug_item_clicked) rug_item.setPen(group_pen) tooltip = experiment_description(attr) rug_item.setToolTip(tooltip) rug_item.group_index = indices.index(i) rug_item.setZValue(rug_item.zValue() + 1) else: rug_item = ClickableRugItem(dist_vec[i] / max_dist, 0.85, self.on_rug_item_clicked) rug_item.setPen(background_pen) tooltip = experiment_description(attr) rug_item.setToolTip(tooltip) rug_item.group = group rug_item.index = i rug_item.in_group = in_group rug_items.append(rug_item) rug_widget = RugGraphicsWidget(parent=main_widget) rug_widget.set_rug(rug_items) rug_widgets.append(rug_widget) label = group_label(self.selected_split_by_labels(), group) label_item = QGraphicsSimpleTextItem(label, main_widget) label_item = GraphicsSimpleTextLayoutItem(label_item, parent=layout) label_item.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) labels.append(label_item) for i, (label, rug_w) in enumerate(zip(labels, rug_widgets)): layout.addItem(label, i, 0, Qt.AlignVCenter) layout.addItem(rug_w, i, 1) layout.setRowMaximumHeight(i, 30) main_widget.setLayout(layout) self.scene.addItem(main_widget) self.main_widget = main_widget self.rug_widgets = rug_widgets self.labels = labels self.on_view_resize(self.scene_view.size())
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
def __updateState(self): """ Update the widget with the new source/sink node signal descriptions. """ widget = QGraphicsWidget() widget.setLayout(QGraphicsGridLayout()) # Space between left and right anchors widget.layout().setHorizontalSpacing(50) left_node = EditLinksNode(self, direction=Qt.LeftToRight, node=self.source) left_node.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding) right_node = EditLinksNode(self, direction=Qt.RightToLeft, node=self.sink) right_node.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding) left_node.setMinimumWidth(150) right_node.setMinimumWidth(150) widget.layout().addItem( left_node, 0, 0, ) widget.layout().addItem( right_node, 0, 1, ) title_template = "<center><b>{0}<b></center>" left_title = GraphicsTextWidget(self) left_title.setHtml(title_template.format(escape(self.source.title))) left_title.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) right_title = GraphicsTextWidget(self) right_title.setHtml(title_template.format(escape(self.sink.title))) right_title.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) widget.layout().addItem(left_title, 1, 0, alignment=Qt.AlignHCenter | Qt.AlignTop) widget.layout().addItem(right_title, 1, 1, alignment=Qt.AlignHCenter | Qt.AlignTop) widget.setParentItem(self) max_w = max( left_node.sizeHint(Qt.PreferredSize).width(), right_node.sizeHint(Qt.PreferredSize).width()) # fix same size left_node.setMinimumWidth(max_w) right_node.setMinimumWidth(max_w) left_title.setMinimumWidth(max_w) right_title.setMinimumWidth(max_w) self.layout().addItem(widget) self.layout().activate() self.sourceNodeWidget = left_node self.sinkNodeWidget = right_node self.sourceNodeTitle = left_title self.sinkNodeTitle = right_title
class VeroMix(QGraphicsWidget): sinkOutputChanged = pyqtSignal() def __init__(self,parent): QGraphicsWidget.__init__(self) self.applet = parent self.pa = None self.last_resize_running = datetime.datetime.now() self.last_resize_running_timer_running = False self.card_infos = {} self.ladspa_index = 1 self.setFocusPolicy(Qt.TabFocus) def init(self): self.setAcceptsHoverEvents (True) self.layout = QGraphicsLinearLayout(Qt.Vertical, self) self.layout.setSizePolicy(QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)) self.scroller = LockableScrollWidget(self) self.scroller.setMinimumSize(120,90) self.layout.addItem(self.scroller) if self.applet.formFactor() != Plasma.Planar : self.scroller.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.source_panel = QGraphicsWidget() self.sink_panel = QGraphicsWidget() self.scrolled_panel_layout = QGraphicsLinearLayout(Qt.Vertical) self.scrolled_panel_widget = QGraphicsWidget() self.scrolled_panel_widget.setLayout(self.scrolled_panel_layout) self.scrolled_panel_layout.setSizePolicy(QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)) self.scrolled_panel_layout.setContentsMargins(0,0,0,6) self.showsTabs = not self.applet.useTabs() self.switchView(True) self.source_panel_layout = SortedLayout(Qt.Vertical, False) self.source_panel_layout.setSpacing(0) self.source_panel.setLayout(self.source_panel_layout) self.source_panel_layout.setSizePolicy(QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)) if self.showsTabs: self.source_panel_layout.addStretch() self.sink_panel_layout = SortedLayout(Qt.Vertical, False) self.sink_panel_layout.setSpacing(0) self.sink_panel.setLayout(self.sink_panel_layout) self.sink_panel_layout.setSizePolicy(QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)) self.layout.setContentsMargins(0,0,0,0) self.source_panel_layout.setContentsMargins(0,0,0,0) self.sink_panel_layout.setContentsMargins(0,0,0,0) #QTimer.singleShot(4000, self.start_pa) self.start_pa() self.connect_mediaplayers() def switchView(self, startup=False): if self.showsTabs: self.scrolled_panel_layout = QGraphicsLinearLayout(Qt.Vertical) scrolled_panel = QGraphicsWidget() scrolled_panel.setLayout(self.scrolled_panel_layout) self.scrolled_panel_layout.setSizePolicy(QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)) self.scrolled_panel_layout.addItem(self.source_panel) self.scrolled_panel_layout.addItem(self.sink_panel) else: scrolled_panel = Plasma.TabBar() scrolled_panel.addTab(i18n("Playback"), self.sink_panel) scrolled_panel.addTab(i18n("Record"), self.source_panel) self.source_panel.show() self.scrolled_panel = scrolled_panel self.showsTabs = not self.showsTabs self.scroller.setWidget(self.scrolled_panel) if not startup: self.check_geometries() def on_update_configuration(self): for source in self.source_panel_layout.getChannels().values(): source.on_update_configuration() for sink in self.sink_panel_layout.getChannels().values(): sink.on_update_configuration() self.pa.set_autostart_meters(bool(self.applet.is_meter_visible())) # connect to pulseaudio(dbus) callbacks def start_pa(self): try: self.pa = PulseAudio(self) self.pa.connect_veromix_service() except Exception, e: self.showMessage(KIcon("script-error"), i18n("There is a problem with the backgroud-service. \ <ul> \ <li>If you just upgraded try killing the process named: VeromixServiceMain.py and relaunch this plasmoid</li> \ <li>If you don't know how to do that consider rebooting</li></ul><br/>\ <a href=\"http://code.google.com/p/veromix-plasmoid/wiki/Debugging\">See wiki for more details</a> <span style=\"font-size: small;\">(right click and copy url)</span>.")) print "\nError connecting to veromix-service:\n" , e, "\n" return self.connect(self.pa, SIGNAL("on_sink_input_info(PyQt_PyObject)"), self.on_sink_input_info) self.connect(self.pa, SIGNAL("on_sink_info(PyQt_PyObject)"), self.on_sink_info) self.connect(self.pa, SIGNAL("on_source_output_info(PyQt_PyObject)"), self.on_source_output_info) self.connect(self.pa, SIGNAL("on_source_info(PyQt_PyObject)"), self.on_source_info) self.connect(self.pa, SIGNAL("on_sink_remove(int)"), self.on_remove_sink) self.connect(self.pa, SIGNAL("on_sink_input_remove(int)"), self.on_remove_sink_input) self.connect(self.pa, SIGNAL("on_source_remove(int)"), self.on_remove_source) self.connect(self.pa, SIGNAL("on_source_output_remove(int)"), self.on_remove_source_output) self.connect(self.pa, SIGNAL("on_volume_meter_sink(int, float)"), self.on_volume_meter_sink) self.connect(self.pa, SIGNAL("on_volume_meter_sink_input(int, float)"), self.on_volume_meter_sink_input) self.connect(self.pa, SIGNAL("on_volume_meter_source(int, float)"), self.on_volume_meter_source) self.connect(self.pa, SIGNAL("on_card_info(PyQt_PyObject)"), self.on_card_info) self.connect(self.pa, SIGNAL("on_card_remove(int)"), self.on_remove_card) self.connect(self.pa, SIGNAL("on_module_info(int,PyQt_PyObject,PyQt_PyObject,PyQt_PyObject,PyQt_PyObject)"), self.on_module_info) self.pa.requestInfo()
def replot_experiments(self): """Replot the whole quality plot. """ self.scene.clear() labels = [] max_dist = numpy.nanmax(list(filter(None, self.distances))) rug_widgets = [] group_pen = QPen(Qt.black) group_pen.setWidth(2) group_pen.setCapStyle(Qt.RoundCap) background_pen = QPen(QColor(0, 0, 250, 150)) background_pen.setWidth(1) background_pen.setCapStyle(Qt.RoundCap) main_widget = QGraphicsWidget() layout = QGraphicsGridLayout() attributes = self.data.domain.attributes if self.data is not None: for (group, indices), dist_vec in zip(self.groups, self.distances): indices_set = set(indices) rug_items = [] if dist_vec is not None: for i, attr in enumerate(attributes): # Is this a within group distance or background in_group = i in indices_set if in_group: rug_item = ClickableRugItem( dist_vec[i] / max_dist, 1.0, self.on_rug_item_clicked) rug_item.setPen(group_pen) tooltip = experiment_description(attr) rug_item.setToolTip(tooltip) rug_item.group_index = indices.index(i) rug_item.setZValue(rug_item.zValue() + 1) else: rug_item = ClickableRugItem( dist_vec[i] / max_dist, 0.85, self.on_rug_item_clicked) rug_item.setPen(background_pen) tooltip = experiment_description(attr) rug_item.setToolTip(tooltip) rug_item.group = group rug_item.index = i rug_item.in_group = in_group rug_items.append(rug_item) rug_widget = RugGraphicsWidget(parent=main_widget) rug_widget.set_rug(rug_items) rug_widgets.append(rug_widget) label = group_label(self.selected_split_by_labels(), group) label_item = QGraphicsSimpleTextItem(label, main_widget) label_item = GraphicsSimpleTextLayoutItem(label_item, parent=layout) label_item.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) labels.append(label_item) for i, (label, rug_w) in enumerate(zip(labels, rug_widgets)): layout.addItem(label, i, 0, Qt.AlignVCenter) layout.addItem(rug_w, i, 1) layout.setRowMaximumHeight(i, 30) main_widget.setLayout(layout) self.scene.addItem(main_widget) self.main_widget = main_widget self.rug_widgets = rug_widgets self.labels = labels self.on_view_resize(self.scene_view.size())
class MediaPlayerUI(Channel): def __init__(self,name, veromix, controller): self.controller = controller Channel.__init__(self, veromix) self.controller.data_updated.connect(self.controller_data_updated) self.index = -1 self._state= None self._position = 0 self._artwork = "" self.last_playing_icon = KIcon(self.get_pauseIcon()) self.layout.setContentsMargins(6,0,6,2) self.controller.init_connection() def initArrangement(self): self.svg_path = self.veromix.applet.package().filePath('images', 'buttons.svgz') self.createMiddle() self.createSlider() self.create_prev_panel() self.create_frame() self.create_panel() self.create_prev_button() self.create_play_pause_button() self.create_next_button() self.create_next_panel() self.createPositionLabel() self.createLengthLabel() self.create_expander() def composeArrangement(self): self.layout.addItem(self.frame) self.frame_layout.addItem(self.panel) self.prev_panel_layout.addStretch() self.prev_panel_layout.addItem(self.prev) self.next_panel_layout.addStretch() self.next_panel_layout.addItem(self.next) self.prev_panel.setSizePolicy(QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)) self.next_panel.setSizePolicy(QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)) self.play.setSizePolicy(QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Minimum)) self.middle_layout.addStretch() self.middle_layout.addItem(self.play) self.panel.setContentsMargins(0,0,0,0) self.panel_layout.setContentsMargins(6,0,10,6) self.panel_layout.addStretch() self.panel_layout.addItem(self.prev_panel) self.panel_layout.addItem(self.middle) self.panel_layout.addItem(self.next_panel) self.panel_layout.addStretch() def on_expander_clicked(self): self.middle_layout.removeItem(self.slider) if (self.extended_panel_shown): self.extended_panel_shown = False #self.frame_layout.removeItem(self.extended_panel) self.extended_panel.setParent(None) self.panel_layout.setContentsMargins(6,0,10,6) self.extended_panel = None self.slider= None self.expander.setSvg("widgets/arrows", "left-arrow") else: self.extended_panel_shown = True self.create_settings_widget() #self.frame_layout.addItem(self.extended_panel) self.panel_layout.setContentsMargins(6,0,10,20) self.extended_panel.setPos(0, int(self.frame.size().height() - 15)) #self.expander.setSvg("widgets/arrows", "up-arrow") self.expander.setSvg("widgets/arrows", "down-arrow") self.controller.set_fetch_extended_info(self.extended_panel_shown) self.veromix.check_geometries() def _resize_widgets(self): #self.expander.setPos(int(self.panel.size().width() - self.expander.size().width()) ,self.panel.size().height() - self.expander.size().height()) self.expander.setPos(int(self.panel.size().width() - self.expander.size().width()) ,0) if self.extended_panel: self.extended_panel.resize(QSizeF(int(self.frame.size().width()), -1)) #self.extended_panel.setPos(0, int(self.frame.size().height() - 12)) def create_settings_widget(self): self.createLengthLabel() self.createPositionLabel() self.createSlider() self.extended_panel = QGraphicsWidget(self.frame) self.extended_panel_layout = QGraphicsLinearLayout(Qt.Horizontal) self.extended_panel.setLayout(self.extended_panel_layout) self.extended_panel.setSizePolicy(QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Minimum)) self.position_label.setSizePolicy(QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)) self.length_label.setSizePolicy(QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)) self.extended_panel_layout.addItem(self.position_label) self.extended_panel_layout.addItem(self.slider) self.extended_panel_layout.addItem(self.length_label) def controller_data_updated(self): self.update_state() self.update_cover() if self.extended_panel_shown: self.update_position() self.update_slider() def on_update_configuration(self): self.set_middle_size() if self.veromix.is_albumart_enabled(): self.middle.setIcon(KIcon()) self._on_upate_expander_enabled() def update_with_info(self, info): pass ## update ui def update_state(self): state = self.controller.state() if self._state != state: self._state = state if state == MediaPlayer.Playing: #self.play.setSvg(self.svg_path, "pause-normal") self.play.setIcon(KIcon("media-playback-pause")) if self.veromix.is_albumart_enabled(): self.middle.setIcon(self.last_playing_icon) else: #self.play.setSvg(self.svg_path, "play-normal") self.play.setIcon(KIcon("media-playback-start")) if self.veromix.is_albumart_enabled(): self.middle.setIcon(KIcon(self.get_pauseIcon())) def update_cover(self): if not self.veromix.is_albumart_enabled(): self.middle.setIcon(KIcon()) return True # FIXME val = self.controller._cover_string if self._artwork != val: self._artwork = val if val == None: self.last_playing_icon = KIcon(self.get_pauseIcon()) else: self.last_playing_icon = QIcon(QPixmap(self.controller.artwork())) self.middle.setIcon(self.last_playing_icon) def update_position(self): v = self.controller.position() if v != self._position: self._position = v pos_str = ( '%d:%02d' % (v / 60, v % 60)) self.position_label.setBoldText(pos_str) v = self.controller.length() if v : pos_str = ( '%d:%02d' % (v / 60, v % 60)) self.length_label.setBoldText(pos_str) def update_slider(self): if self.slider and self.extended_panel_shown: if self.controller.state() == MediaPlayer.Stopped: self.slider.setValueFromPulse(0) else: if self.controller.length() > 0 : v = self.controller.position() * 100 / self.controller.length() # if self.slider.check_pulse_timestamp(): self.slider.setValueFromPulse(v) def on_slider_action_triggered(self, action): value = self.slider.nativeWidget().sliderPosition() if value > -1 and (action == 7 or action == 3): self.controller.seek(value) ## initialize ui def context_menu_create_mute(self): pass def on_contextmenu_clicked(self, action): pass def context_menu_create_unlock_channels(self): action_unlock = QAction(i18n("Show position"), 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.on_expander_clicked) def create_next_panel(self): self.next_panel = QGraphicsWidget() self.next_panel_layout = QGraphicsLinearLayout(Qt.Vertical) self.next_panel_layout.setContentsMargins(0,0,0,0) self.next_panel.setLayout(self.next_panel_layout) def createPositionLabel(self): self.position_label = Label() self.position_label.setContentsMargins(0,0,0,0) self.position_label.setSizePolicy(QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding, True)) self.position_label.setAlignment(Qt.AlignRight) def createLengthLabel(self): self.length_label = Label() self.length_label.setContentsMargins(0,0,0,0) self.length_label.setSizePolicy(QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding, True)) self.length_label.setAlignment(Qt.AlignLeft) def create_prev_panel(self): self.prev_panel = Plasma.IconWidget() self.prev_panel_layout = QGraphicsLinearLayout(Qt.Vertical) self.prev_panel_layout.setContentsMargins(0,0,0,0) self.prev_panel.setLayout(self.prev_panel_layout) self.prev_panel.setSizePolicy(QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)) def createMiddle(self): self.middle = Plasma.IconWidget() self.middle_layout = QGraphicsLinearLayout(Qt.Vertical) self.middle_layout.setContentsMargins(0,0,0,0) self.middle.setLayout(self.middle_layout) self.set_middle_size() self.middle.setSizePolicy(QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)) if self.veromix.is_albumart_enabled(): self.middle.setIcon(KIcon(self.get_pauseIcon())) def set_middle_size(self): if self.veromix.is_albumart_enabled(): self.CONTROLSBAR_SIZE = 80 else: self.CONTROLSBAR_SIZE = 30 self.middle.setMinimumHeight(self.CONTROLSBAR_SIZE) self.middle.setPreferredHeight(self.CONTROLSBAR_SIZE) self.middle.setMaximumHeight(self.CONTROLSBAR_SIZE) def create_next_button(self): self.next = MuteButton(self) self.next.setAbsSize(20) #self.next.setSvg(self.svg_path , "next-normal") self.next.setIcon(KIcon("media-skip-forward")) self.connect(self.next, SIGNAL("clicked()"), self.on_next_cb ) def create_prev_button(self): self.prev = MuteButton(self) self.prev.setAbsSize(20) #self.prev.setSvg(self.svg_path, "prev-normal") self.prev.setIcon(KIcon("media-skip-backward")) self.connect(self.prev, SIGNAL("clicked()"), self.on_prev_cb ) def create_play_pause_button(self): self.play = MuteButton(self) self.play.setAbsSize(-1) #self.play.setSvg(self.svg_path, "stop-normal") self.play.setIcon(KIcon("media-playback-stop")) self.connect(self.play, SIGNAL("clicked()"), self.on_play_cb ) def createMute(self): pass # callbacks def on_mute_cb(self): pass def on_next_cb(self): self.controller.next_track() def on_prev_cb(self): self.controller.prev_track() def on_play_cb(self): if self.controller.state() == MediaPlayer.Playing: self.controller.pause() else: self.controller.play() # helpers def name(self): return self.controller_name() def controller_name(self): return self.controller.name() def get_pauseIcon(self): name = self.get_application_name() app = self.veromix.query_application(str(name)) if app == None: return name return app def get_application_name(self): return self.controller.get_application_name() def matches(self, sink): sink = self.get_assotiated_sink_input() if sink == None: return False return True def get_assotiated_sink_input(self): name = str(self.get_application_name()).lower() for sink in self.veromix.get_sinkinput_widgets(): if str(name).lower().find(sink.name()) >= 0: return sink for sink in self.veromix.get_sinkinput_widgets(): if str(sink.name()).lower().find(name) >= 0 : return sink return None ## overrides def isNowplaying(self): return True def isSinkOutput(self): return False def isSinkInput(self): return False def createSlider(self): Channel.createSlider(self) self.slider.setMaximum(100) self.slider.volumeChanged.disconnect(self.on_slider_cb) self.slider.nativeWidget ().actionTriggered.connect(self.on_slider_action_triggered) ## testing def is_nowplaying_player(self): return self.controller.is_mpris2_player() def is_mpris2_player(self): return self.controller.is_mpris2_player()