def main(argv=[]): app = QApplication(argv) w = ToolBox() style = app.style() icon = QIcon(style.standardIcon(QStyle.SP_FileIcon)) p1 = QLabel("A Label") p2 = QListView() p3 = QLabel("Another\nlabel") p4 = QSpinBox() i1 = w.addItem(p1, "Tab 1", icon) i2 = w.addItem(p2, "Tab 2", icon, "The second tab") i3 = w.addItem(p3, "Tab 3") i4 = w.addItem(p4, "Tab 4") p6 = QTextBrowser() p6.setHtml( "<h1>Hello Visitor</h1>" "<p>Are you interested in some of our wares?</p>" ) w.insertItem(2, p6, "Dear friend") w.show() return app.exec_()
class OWMarkerGenes(widget.OWWidget): name = "Marker Genes" icon = 'icons/OWMarkerGenes.svg' priority = 130 replaces = ['orangecontrib.single_cell.widgets.owmarkergenes.OWMarkerGenes'] class Warning(widget.OWWidget.Warning): using_local_files = widget.Msg("Can't connect to serverfiles. Using cached files.") class Outputs: genes = widget.Output("Genes", Table) want_main_area = True want_control_area = True auto_commit = Setting(True) selected_source = Setting("") selected_organism = Setting("") selected_root_attribute = Setting(0) settingsHandler = MarkerGroupContextHandler() # noqa: N815 selected_genes = settings.ContextSetting([]) settings_version = 2 _data = None _available_sources = None def __init__(self) -> None: super().__init__() # define the layout main_area = QWidget(self.mainArea) self.mainArea.layout().addWidget(main_area) layout = QGridLayout() main_area.setLayout(layout) layout.setContentsMargins(4, 4, 4, 4) # filter line edit self.filter_line_edit = QLineEdit() self.filter_line_edit.setPlaceholderText("Filter marker genes") layout.addWidget(self.filter_line_edit, 0, 0, 1, 3) # define available markers view self.available_markers_view = TreeView() box = gui.vBox(self.mainArea, "Available markers", addToLayout=False) box.layout().addWidget(self.available_markers_view) layout.addWidget(box, 1, 0, 2, 1) # create selected markers view self.selected_markers_view = TreeView() box = gui.vBox(self.mainArea, "Selected markers", addToLayout=False) box.layout().addWidget(self.selected_markers_view) layout.addWidget(box, 1, 2, 2, 1) self.available_markers_view.otherView = self.selected_markers_view self.selected_markers_view.otherView = self.available_markers_view # buttons box = gui.vBox(self.mainArea, addToLayout=False, margin=0) layout.addWidget(box, 1, 1, 1, 1) self.move_button = gui.button(box, self, ">", callback=self._move_selected) self._init_description_area(layout) self._init_control_area() self._load_data() def _init_description_area(self, layout: QLayout) -> None: """ Function define an info area with description of the genes and add it to the layout. """ box = gui.widgetBox(self.mainArea, "Description", addToLayout=False) self.descriptionlabel = QTextBrowser( openExternalLinks=True, textInteractionFlags=(Qt.TextSelectableByMouse | Qt.LinksAccessibleByMouse) ) box.setMaximumHeight(self.descriptionlabel.fontMetrics().height() * (NUM_LINES_TEXT + 3)) # description filed self.descriptionlabel.setText("Select a gene to see information.") self.descriptionlabel.setFrameStyle(QTextBrowser.NoFrame) # no (white) text background self.descriptionlabel.viewport().setAutoFillBackground(False) box.layout().addWidget(self.descriptionlabel) layout.addWidget(box, 3, 0, 1, 3) def _init_control_area(self) -> None: """ Function defines dropdowns and the button in the control area. """ box = gui.widgetBox(self.controlArea, 'Database', margin=0) self.source_index = -1 self.db_source_cb = gui.comboBox(box, self, 'source_index') self.db_source_cb.activated[int].connect(self._set_db_source_index) box = gui.widgetBox(self.controlArea, 'Organism', margin=0) self.organism_index = -1 self.group_cb = gui.comboBox(box, self, 'organism_index') self.group_cb.activated[int].connect(self._set_group_index) box = gui.widgetBox(self.controlArea, 'Group by', margin=0) self.group_by_cb = gui.comboBox( box, self, 'selected_root_attribute', items=GROUP_BY_ITEMS, callback=self._setup ) gui.rubber(self.controlArea) gui.auto_commit(self.controlArea, self, "auto_commit", "Commit") def sizeHint(self): return super().sizeHint().expandedTo(QSize(900, 500)) @property def available_sources(self) -> dict: return self._available_sources @available_sources.setter def available_sources(self, value: dict) -> None: """ Set _available_sources variable, add them to dropdown, and select the source that was previously. """ self._available_sources = value items = sorted(list(value.keys()), reverse=True) # panglao first try: idx = items.index(self.selected_source) except ValueError: idx = -1 self.db_source_cb.clear() self.db_source_cb.addItems(items) if idx != -1: self.source_index = idx self.selected_source = items[idx] elif items: self.source_index = min(max(self.source_index, 0), len(items) - 1) self._set_db_source_index(self.source_index) @property def data(self) -> Table: return self._data @data.setter def data(self, value: Table): """ Set the source data. The data is then filtered on the first meta column (group). Select set dropdown with the groups and select the one that was selected previously. """ self._data = value domain = value.domain if domain.metas: group = domain.metas[0] groupcol, _ = value.get_column_view(group) if group.is_string: group_values = list(set(groupcol)) elif group.is_discrete: group_values = group.values else: raise TypeError("Invalid column type") group_values = sorted(group_values) # human first try: idx = group_values.index(self.selected_organism) except ValueError: idx = -1 self.group_cb.clear() self.group_cb.addItems(group_values) if idx != -1: self.organism_index = idx self.selected_organism = group_values[idx] elif group_values: self.organism_index = min(max(self.organism_index, 0), len(group_values) - 1) self._set_group_index(self.organism_index) def _load_data(self) -> None: """ Collect available data sources (marker genes data sets). """ self.Warning.using_local_files.clear() found_sources = {} try: found_sources.update(serverfiles.ServerFiles().allinfo(SERVER_FILES_DOMAIN)) except requests.exceptions.ConnectionError: found_sources.update(serverfiles.allinfo(SERVER_FILES_DOMAIN)) self.Warning.using_local_files() self.available_sources = {item.get('title').split(': ')[-1]: item for item in found_sources.values()} def _source_changed(self) -> None: """ Respond on change of the source and download the data. """ if self.available_sources: file_name = self.available_sources[self.selected_source]['filename'] try: serverfiles.update(SERVER_FILES_DOMAIN, file_name) except requests.exceptions.ConnectionError: # try to update file. Ignore network errors. pass try: file_path = serverfiles.localpath_download(SERVER_FILES_DOMAIN, file_name) except requests.exceptions.ConnectionError as err: # Unexpected error. raise err self.data = Table.from_file(file_path) def _setup(self) -> None: """ Setup the views with data. """ self.closeContext() self.selected_genes = [] self.openContext((self.selected_organism, self.selected_source)) data_not_selected, data_selected = self._filter_data_group(self.data) # add model to available markers view group_by = GROUP_BY_ITEMS[self.selected_root_attribute] tree_model = TreeModel(data_not_selected, group_by) proxy_model = FilterProxyModel(self.filter_line_edit) proxy_model.setSourceModel(tree_model) self.available_markers_view.setModel(proxy_model) self.available_markers_view.selectionModel().selectionChanged.connect( partial(self._on_selection_changed, self.available_markers_view) ) tree_model = TreeModel(data_selected, group_by) proxy_model = FilterProxyModel(self.filter_line_edit) proxy_model.setSourceModel(tree_model) self.selected_markers_view.setModel(proxy_model) self.selected_markers_view.selectionModel().selectionChanged.connect( partial(self._on_selection_changed, self.selected_markers_view) ) self.selected_markers_view.model().sourceModel().data_added.connect(self._selected_markers_changed) self.selected_markers_view.model().sourceModel().data_removed.connect(self._selected_markers_changed) # update output and messages self._selected_markers_changed() def _filter_data_group(self, data: Table) -> Tuple[Table, Tuple]: """ Function filter the table based on the selected group (Mouse, Human) and divide them in two groups based on selected_data variable. Parameters ---------- data Table to be filtered Returns ------- data_not_selected Data that will initially be in available markers view. data_selected Data that will initially be in selected markers view. """ group = data.domain.metas[0] gvec = data.get_column_view(group)[0] if group.is_string: mask = gvec == self.selected_organism else: mask = gvec == self.organism_index data = data[mask] # divide data based on selected_genes variable (context) unique_gene_names = np.core.defchararray.add( data.get_column_view("Entrez ID")[0].astype(str), data.get_column_view("Cell Type")[0].astype(str) ) mask = np.isin(unique_gene_names, self.selected_genes) data_not_selected = data[~mask] data_selected = data[mask] return data_not_selected, data_selected def commit(self) -> None: rows = self.selected_markers_view.model().sourceModel().rootItem.get_data_rows() if len(rows) > 0: metas = [r.metas for r in rows] data = Table.from_numpy(self.data.domain, np.empty((len(metas), 0)), metas=np.array(metas)) # always false for marker genes data tables in single cell data.attributes[GENE_AS_ATTRIBUTE_NAME] = False # set taxonomy id in data.attributes data.attributes[TAX_ID] = MAP_GROUP_TO_TAX_ID.get(self.selected_organism, '') # set column id flag data.attributes[GENE_ID_COLUMN] = "Entrez ID" data.name = 'Marker Genes' else: data = None self.Outputs.genes.send(data) def _update_description(self, view: TreeView) -> None: """ Upate the description about the gene. Only in case when one gene is selected. """ selection = self._selected_rows(view) qmodel = view.model().sourceModel() if len(selection) > 1 or len(selection) == 0 or qmodel.node_from_index(selection[0]).data_row is None: self.descriptionlabel.setText("Select a gene to see information.") else: data_row = qmodel.node_from_index(selection[0]).data_row self.descriptionlabel.setHtml( f"<b>Gene name:</b> {data_row['Name']}<br/>" f"<b>Entrez ID:</b> {data_row['Entrez ID']}<br/>" f"<b>Cell Type:</b> {data_row['Cell Type']}<br/>" f"<b>Function:</b> {data_row['Function']}<br/>" f"<b>Reference:</b> <a href='{data_row['URL']}'>{data_row['Reference']}</a>" ) def _update_data_info(self) -> None: """ Updates output info in the control area. """ sel_model = self.selected_markers_view.model().sourceModel() self.info.set_output_summary(f"Selected: {str(len(sel_model))}") # callback functions def _selected_markers_changed(self) -> None: """ This function is called when markers in the selected view are added or removed. """ rows = self.selected_markers_view.model().sourceModel().rootItem.get_data_rows() self.selected_genes = [row["Entrez ID"].value + row["Cell Type"].value for row in rows] self._update_data_info() self.commit() def _on_selection_changed(self, view: TreeView) -> None: """ When selection in one of the view changes in a view button should change a sign in the correct direction and other view should reset the selection. Also gene description is updated. """ self.move_button.setText(">" if view is self.available_markers_view else "<") if view is self.available_markers_view: self.selected_markers_view.clearSelection() else: self.available_markers_view.clearSelection() self._update_description(view) def _set_db_source_index(self, source_index: int) -> None: """ Set the index of selected database source - index in a dropdown. """ self.source_index = source_index self.selected_source = self.db_source_cb.itemText(source_index) self._source_changed() def _set_group_index(self, group_index: int) -> None: """ Set the index of organism - index in a dropdown. """ self.organism_index = group_index self.selected_organism = self.group_cb.itemText(group_index) self._setup() def _move_selected(self) -> None: """ Move selected genes when button clicked. """ if self._selected_rows(self.selected_markers_view): self._move_selected_from_to(self.selected_markers_view, self.available_markers_view) elif self._selected_rows(self.available_markers_view): self._move_selected_from_to(self.available_markers_view, self.selected_markers_view) # support functions for callbacks def _move_selected_from_to(self, src: TreeView, dst: TreeView) -> None: """ Function moves items from src model to dst model. """ selected_items = self._selected_rows(src) src_model = src.model().sourceModel() dst_model = dst.model().sourceModel() # move data as mimeData from source to destination tree view mime_data = src_model.mimeData(selected_items) # remove nodes from the source view src_model.remove_node_list(selected_items) dst_model.dropMimeData(mime_data, Qt.MoveAction, -1, -1) @staticmethod def _selected_rows(view: TreeView) -> List[QModelIndex]: """ Return the selected rows in the view. """ rows = view.selectionModel().selectedRows() return list(map(view.model().mapToSource, rows)) @classmethod def migrate_settings(cls, settings, version=0): def migrate_to_version_2(): settings["selected_source"] = settings.pop("selected_db_source", "") settings["selected_organism"] = settings.pop("selected_group", "") if "context_settings" in settings: for co in settings["context_settings"]: co.values["selected_genes"] = [g[0] + g[1] for g in co.values["selected_genes"]] if version < 2: migrate_to_version_2()