def load(self): """Load all fields from the model """ self.clear() self._checkable_items.clear() self.setColumnCount(2) self.setHorizontalHeaderLabels(["name", "description"]) # Load fields from variant categories self.appendRow(self._load_fields("variants")) # Load fields from annotations categories self.appendRow(self._load_fields("annotations")) # Create and load fields from samples categories samples_items = QStandardItem("samples") samples_items.setIcon(FIcon(0xF0B9C)) font = QFont() samples_items.setFont(font) for sample in sql.get_samples(self.conn): sample_item = self._load_fields("samples", parent_name=sample["name"]) sample_item.setText(sample["name"]) sample_item.setIcon(FIcon(0xF0B9C)) samples_items.appendRow(sample_item) self.appendRow(samples_items)
def load_fields(self, category, parent_name=None): root_item = QStandardItem(category) root_item.setColumnCount(2) root_item.setIcon(FIcon(0xF0256)) font = QFont() root_item.setFont(font) type_icons = { "int": 0xF03A0, "str": 0xF100D, "float": 0xF03A0, "bool": 0xF023B } for field in sql.get_field_by_category(self.conn, category): field_name = QStandardItem(field["name"]) descr = QStandardItem(field["description"]) descr.setToolTip(field["description"]) field_name.setCheckable(True) if field["type"] in type_icons.keys(): field_name.setIcon(FIcon(type_icons[field["type"]])) root_item.appendRow([field_name, descr]) self.checkable_items.append(field_name) if category == "samples": field_name.setData( {"name": ("sample", parent_name, field["name"])}) else: field_name.setData(field) return root_item
def __init__(self, conn=None, parent=None): super().__init__(parent) self.setWindowTitle(self.tr("Columns")) self.view = QTreeView() self.toolbar = QToolBar() # conn is always None here but initialized in on_open_project() self.model = FieldsModel(conn) # setup proxy ( for search option ) self.proxy_model = QSortFilterProxyModel() self.proxy_model.setSourceModel(self.model) self.proxy_model.setRecursiveFilteringEnabled(True) # Search is case insensitive self.proxy_model.setFilterCaseSensitivity(Qt.CaseInsensitive) # Search in all columns self.proxy_model.setFilterKeyColumn(-1) self.view.setModel(self.proxy_model) self.view.setIconSize(QSize(16, 16)) self.view.header().setSectionResizeMode(QHeaderView.ResizeToContents) self.view.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.view.setEditTriggers(QAbstractItemView.NoEditTriggers) self.search_edit = QLineEdit() # self.view.setIndentation(0) self.view.header().setVisible(False) layout = QVBoxLayout() layout.addWidget(self.toolbar) layout.addWidget(self.view) layout.addWidget(self.search_edit) layout.setContentsMargins(0, 0, 0, 0) self.setLayout(layout) self.model.itemChanged.connect(self.on_fields_changed) # Setup toolbar self.toolbar.setIconSize(QSize(16, 16)) self.toolbar.addAction( FIcon(0xF0615), self.tr("Collapse"), self.view.collapseAll ) self.toolbar.addAction(FIcon(0xF0616), self.tr("Expand"), self.view.expandAll) # setup search edit search_act = self.toolbar.addAction( FIcon(0xF0969), self.tr("Search fields by keywords...") ) search_act.setCheckable(True) search_act.toggled.connect(self.on_search_pressed) search_act.setShortcut(QKeySequence.Find) self.search_edit.setVisible(False) self.search_edit.setPlaceholderText(self.tr("Search by keywords... ")) self.search_edit.textChanged.connect(self.proxy_model.setFilterRegExp) self._is_refreshing = ( False # Help to avoid loop between on_refresh and on_fields_changed )
def _fill_completer(self): """Create Completer with his model Fill the model with the SQL keywords and database fields """ # preload samples , selection and wordset samples = [i["name"] for i in sql.get_samples(self.conn)] selections = [i["name"] for i in sql.get_selections(self.conn)] wordsets = [i["name"] for i in sql.get_wordsets(self.conn)] # keywords = [] self.text_edit.completer.model.clear() self.text_edit.completer.model.beginResetModel() # register keywords for keyword in self.text_edit.syntax.sql_keywords: self.text_edit.completer.model.add_item(keyword, "VQL keywords", FIcon(0xF0169), "#f6ecf0") for selection in selections: self.text_edit.completer.model.add_item(selection, "Source table", FIcon(0xF04EB), "#f6ecf0") for wordset in wordsets: self.text_edit.completer.model.add_item(f"WORDSET['{wordset}']", "WORDSET", FIcon(0xF04EB), "#f6ecf0") for field in sql.get_fields(self.conn): name = field["name"] description = "<b>{}</b> ({}) from {} <br/><br/> {}".format( field["name"], field["type"], field["category"], field["description"]) color = style.FIELD_TYPE.get(field["type"], "str")["color"] icon = FIcon( style.FIELD_TYPE.get(field["type"], "str")["icon"], "white") if field["category"] == "variants" or field[ "category"] == "annotations": self.text_edit.completer.model.add_item( name, description, icon, color) if field["category"] == "samples": # Overwrite name for sample in samples: name = "sample['{}'].{}".format(sample, field["name"]) description = "<b>{}</b> ({}) from {} {} <br/><br/> {}".format( field["name"], field["type"], field["category"], sample, field["description"], ) self.text_edit.completer.model.add_item( name, description, icon, color) self.text_edit.completer.model.endResetModel()
def data(self, index: QModelIndex(), role=Qt.DisplayRole): """Return the data stored under the given role for the item referred to by the index (row, col) Overrided from QAbstractTableModel :param index: :param role: :type index: :type role: :return: None if no valid index :rtype: """ if not index.isValid(): return None if role == Qt.DisplayRole: if index.column() == 0: return self.records[index.row()]["name"] if index.column() == 1: return self.records[index.row()]["count"] if role == Qt.DecorationRole: if index.column() == 0: return QIcon(FIcon(0xF04F1)) return None
def __init__(self, parent=None): super().__init__(parent) self.setWindowTitle(self.tr("VQL Editor")) # Top toolbar self.top_bar = QToolBar() self.top_bar.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) self.run_action = self.top_bar.addAction(FIcon(0xF040A), self.tr("Run"), self.run_vql) self.run_action.setShortcuts( [Qt.CTRL + Qt.Key_R, QKeySequence.Refresh]) self.run_action.setToolTip( self.tr("Run VQL query (%s)" % self.run_action.shortcut().toString())) # Syntax highlighter and autocompletion self.text_edit = CodeEdit() # Error handling self.log_edit = QLabel() self.log_edit.setMinimumHeight(40) self.log_edit.setStyleSheet( "QWidget{{background-color:'{}'; color:'{}'}}".format( style.WARNING_BACKGROUND_COLOR, style.WARNING_TEXT_COLOR)) self.log_edit.hide() self.log_edit.setFrameStyle(QFrame.StyledPanel | QFrame.Raised) main_layout = QVBoxLayout() main_layout.addWidget(self.top_bar) main_layout.addWidget(self.text_edit) main_layout.addWidget(self.log_edit) main_layout.setContentsMargins(0, 0, 0, 0) main_layout.setSpacing(0) self.setLayout(main_layout)
def add_list_widget_item(self, db_name: str, url: str): """Add an item to the QListWidget of the current view""" # Key is the name of the database, value is its url item = QListWidgetItem(db_name) item.setIcon(FIcon(0xF0866)) item.setData(Qt.UserRole, str(url)) item.setToolTip(str(url)) self.view.addItem(item)
def __init__(self): super().__init__() self.setWindowTitle(self.tr("Memory")) self.setWindowIcon(FIcon(0xF070F)) layout = QFormLayout() self.spinbox = QSpinBox() self.spinbox.setRange(0, 100000) layout.addRow(self.tr("Cache size"), self.spinbox)
def __init__(self, parent=None, conn=None): """ Args: parent (QMainWindow): Mainwindow of Cutevariant, passed during plugin initialization. conn (sqlite3.connexion): Sqlite3 connexion """ super().__init__(parent) self.setWindowTitle(self.tr("Source editor")) # conn is always None here but initialized in on_open_project() self.model = SourceModel(conn) self.view = QTableView() self.view.setModel(self.model) self.view.setSelectionBehavior(QAbstractItemView.SelectRows) self.view.setSelectionMode(QAbstractItemView.SingleSelection) self.view.horizontalHeader().show() self.view.horizontalHeader().setStretchLastSection(False) self.view.horizontalHeader().setSectionResizeMode( 0, QHeaderView.ResizeToContents) self.view.horizontalHeader().setSectionResizeMode( 1, QHeaderView.Stretch) self.view.horizontalHeader().hide() self.toolbar = QToolBar() self.toolbar.setIconSize(QSize(16, 16)) self.view.verticalHeader().hide() self.view.verticalHeader().setDefaultSectionSize(26) self.view.setShowGrid(False) self.view.setAlternatingRowColors(True) layout = QVBoxLayout() layout.addWidget(self.view) layout.addWidget(self.toolbar) layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) self.setLayout(layout) # Used to block signals during the insertions (if set to True) self.is_loading = False # Map the operations of context menu with an internal id not visible # from the user # This id is used by _create_set_operation_menu # Keys: user text; values: set operators # See menu_setup() self.set_operations_mapping = dict() # call on_current_row_changed when item selection changed self.view.selectionModel().currentRowChanged.connect( self.on_current_row_changed) self.toolbar.addAction(FIcon(0xF0453), self.tr("Reload"), self.load)
def load(self): """Load all columns avaible into the model""" self.clear() self.checkable_items.clear() self.appendRow(self.load_fields("variants")) self.appendRow(self.load_fields("annotations")) samples_items = QStandardItem("samples") samples_items.setIcon(FIcon(0xF0B9C)) font = QFont() samples_items.setFont(font) for sample in sql.get_samples(self.conn): sample_item = self.load_fields("samples", parent_name=sample["name"]) sample_item.setText(sample["name"]) sample_item.setIcon(FIcon(0xF0B9C)) samples_items.appendRow(sample_item) self.appendRow(samples_items)
def menu_setup(self, locked_selection=False): """Setup popup menu :key locked_selection: Allow to mask edit/remove actions (default False) Used on special selections like the default one (named variants). :type locked_selection: <boolean> """ menu = QMenu() if not locked_selection: menu.addAction(FIcon(0xF0900), self.tr("Edit"), self.edit_selection) # Create action for bed menu.addAction( FIcon(0xF0219), self.tr("Intersect with BED file ..."), self.create_selection_from_bed, ) # Set operations on selections: create mapping and actions set_icons_ids = (0xF0779, 0xF077C, 0xF0778) set_texts = (self.tr("Intersect"), self.tr("Difference"), self.tr("Union")) set_internal_ids = ("&", "-", "|") # Map the operations with an internal id not visible for the user # This id is used by _create_set_operation_menu # Keys: user text; values: internal ids self.set_operations_mapping = dict(zip(set_texts, set_internal_ids)) # Create actions [ menu.addMenu(self._create_set_operation_menu(FIcon(icon_id), text)) for icon_id, text in zip(set_icons_ids, set_texts) ] if not locked_selection: menu.addSeparator() menu.addAction(FIcon(0xF0A7A), self.tr("Remove"), self.remove_selection) return menu
def _load_fields(self, category: str, parent_name: str = None) -> QStandardItem: """Load fields from database and create a QStandardItem Args: category (str): category name : eg. variants / annotations / samples parent_name (str, optional): name of the parent item Returns: QStandardItem """ root_item = QStandardItem(category) root_item.setColumnCount(2) root_item.setIcon(FIcon(0xF0256)) font = QFont() root_item.setFont(font) for field in sql.get_field_by_category(self.conn, category): field_name = QStandardItem(field["name"]) descr = QStandardItem(field["description"]) descr.setToolTip(field["description"]) field_name.setCheckable(True) field_type = style.FIELD_TYPE.get(field["type"]) field_name.setIcon( FIcon(field_type["icon"], "white", field_type["color"])) root_item.appendRow([field_name, descr]) self._checkable_items.append(field_name) if category == "samples": field_name.setData( {"name": ("sample", parent_name, field["name"])}) else: field_name.setData(field) return root_item
def on_group_changed(self): """Set group by fields when group by button is clicked""" is_checked = self.groupby_action.isChecked() is_grouped = self._is_grouped() if is_checked and not is_grouped: # Group it self.groupby_action.setIcon(FIcon(0xF14E1)) self.groupby_action.setToolTip(self.tr("Ungroup variants")) # Recall previous/default group self._set_groups(self.last_group) else: # Ungroup it self.groupby_action.setIcon(FIcon(0xF14E0)) self.groupby_action.setToolTip( self.tr("Group variants according to choosen columns")) # Save current group self.last_group = self.groupby_left_pane.group_by if not is_checked: # Reset to default group (chr set in _show_group_dialog) self._set_groups([]) self.load() self._refresh_vql_editor()
def set_message(self, message: str): """Show message error at the bottom of the view Args: message (str): Error message """ if self.log_edit.isHidden(): self.log_edit.show() icon_64 = FIcon(0xF0027, style.WARNING_TEXT_COLOR).to_base64(18, 18) self.log_edit.setText("""<div height=100%> <img src="data:image/png;base64,{}" align="left"/> <span> {} </span> </div>""".format(icon_64, message))
def __init__(self): super().__init__() self.setWindowTitle(self.tr("Cross databases links")) self.setWindowIcon(FIcon(0xF070F)) help_label = QLabel( self. tr("Allow to set predefined masks for urls pointing to various databases of variants.\n" "Shortcuts will be visible from contextual menu over current variant.\n" "Set a link as default makes possible to open alink by double clicking on the view." )) self.view = QListWidget() self.add_button = QPushButton(self.tr("Add")) self.edit_button = QPushButton(self.tr("Edit")) self.set_default_button = QPushButton(self.tr("Set as default")) self.set_default_button.setToolTip( self.tr("Double click will open this link")) self.remove_button = QPushButton(self.tr("Remove")) v_layout = QVBoxLayout() v_layout.addWidget(self.add_button) v_layout.addWidget(self.edit_button) v_layout.addStretch() v_layout.addWidget(self.set_default_button) v_layout.addWidget(self.remove_button) h_layout = QHBoxLayout() h_layout.addWidget(self.view) h_layout.addLayout(v_layout) main_layout = QVBoxLayout() main_layout.addWidget(help_label) main_layout.addLayout(h_layout) self.setLayout(main_layout) # Signals self.add_button.clicked.connect(self.add_url) self.edit_button.clicked.connect(self.edit_item) self.view.itemDoubleClicked.connect(self.add_url) self.set_default_button.clicked.connect(self.set_default_link) self.remove_button.clicked.connect(self.remove_item)
def add_list_widget_item(self, db_name: str, url: str, is_default=False, is_browser=False): """Add an item to the QListWidget of the current view""" # Key is the name of the database, value is its url item = QListWidgetItem(db_name) item.setIcon(FIcon(0xF0866)) item.setData(Qt.UserRole, str(url)) # UserRole = Link item.setData(Qt.UserRole + 1, bool(is_default)) # UserRole+1 = is default link item.setData(Qt.UserRole + 2, bool(is_browser)) # UserRole+1 = is default link item.setToolTip(str(url)) font = item.font() font.setBold(is_default) item.setFont(font) self.view.addItem(item)
def add_available_formatters(self): """Populate the formatters Also recall previously selected formatters via config file. Default formatter is "SeqoneFormatter". """ # Get previously selected formatter formatter_name = self.settings.value("ui/formatter", "CutestyleFormatter") # Add formatters to combobox, a click on it will instantiate the class selected_formatter_index = 0 for index, obj in enumerate(formatter.find_formatters()): self.formatter_combo.addItem(FIcon(0xF03D8), obj.DISPLAY_NAME, obj) if obj.__name__ == formatter_name: selected_formatter_index = index self.top_bar.addWidget(self.formatter_combo) self.formatter_combo.currentTextChanged.connect( self.on_formatter_changed) self.formatter_combo.setToolTip(self.tr("Change current style")) # Set the previously used/default formatter self.formatter_combo.setCurrentIndex(selected_formatter_index)
def __init__(self, parent=None): super().__init__(parent) self.setWindowTitle(self.tr("Info variants")) # Current variant => set by on_refresh and on_open_project self.current_variant = None self.view = QTabWidget() self.toolbar = QToolBar() self.toolbar.setIconSize(QSize(16, 16)) # Build comments tab self.edit_panel = EditPanel() self.edit_panel.saved.connect(self.on_save_variant) self.view.addTab(self.edit_panel, self.tr("User")) # Build variant tab self.variant_view = DictWidget() self.variant_view.set_header_visible(True) self.view.addTab(self.variant_view, self.tr("Variant")) # Build transcript tab self.transcript_combo = QComboBox() self.transcript_view = DictWidget() self.transcript_view.set_header_visible(True) tx_layout = QVBoxLayout() tx_layout.addWidget(self.transcript_combo) tx_layout.addWidget(self.transcript_view) tx_widget = QWidget() tx_widget.setLayout(tx_layout) self.view.addTab(tx_widget, self.tr("Transcripts")) self.transcript_combo.currentIndexChanged.connect(self.on_transcript_changed) # Build Samples tab self.sample_combo = QComboBox() self.sample_view = DictWidget() self.sample_view.set_header_visible(True) tx_layout = QVBoxLayout() tx_layout.addWidget(self.sample_combo) tx_layout.addWidget(self.sample_view) tx_widget = QWidget() tx_widget.setLayout(tx_layout) self.view.addTab(tx_widget, self.tr("Samples")) self.sample_combo.currentIndexChanged.connect(self.on_sample_changed) # Build genotype tab self.genotype_view = QListWidget() self.genotype_view.setIconSize(QSize(20, 20)) self.view.addTab(self.genotype_view, self.tr("Genotypes")) v_layout = QVBoxLayout() v_layout.setContentsMargins(0, 0, 0, 0) v_layout.addWidget(self.view) self.setLayout(v_layout) # # Create menu # TODO: restore this # self.context_menu = VariantPopupMenu() # # Ability to trigger the menu # self.view.setContextMenuPolicy(Qt.CustomContextMenu) # self.view.customContextMenuRequested.connect(self.show_menu) # self.add_tab("variants") # Cache all database fields and their descriptions for tooltips # Field names as keys, descriptions as values self.fields_descriptions = None # Cache genotype icons # Values in gt field as keys (str), FIcon as values self.genotype_icons = { key: FIcon(val) for key, val in cm.GENOTYPE_ICONS.items() }
def __init__(self, parent=None): super().__init__(parent) self.setWindowIcon(FIcon(0xF035C)) self.setWindowTitle("Variant view") self.add_settings_widget(LinkSettings())
def __init__(self, parent=None): super().__init__(parent) # Create 2 Panes self.splitter = QSplitter(Qt.Horizontal) self.main_right_pane = VariantView(parent=self) # No popup menu on this one self.groupby_left_pane = VariantView(parent=self, show_popup_menu=False) self.groupby_left_pane.hide() # Force selection of first item after refresh self.groupby_left_pane.row_to_be_selected = 0 self.splitter.addWidget(self.groupby_left_pane) self.splitter.addWidget(self.main_right_pane) # Make resizable TODO : ugly ... Make it nicer # def _resize1_section(l, o, n): # self.groupby_left_pane.view.horizontalHeader().resizeSection(l, n) # def _resize2_section(l, o, n): # self.main_right_pane.view.horizontalHeader().resizeSection(l, n) # self.main_right_pane.view.horizontalHeader().sectionResized.connect(_resize1_section) # self.groupby_left_pane.view.horizontalHeader().sectionResized.connect( # _resize2_section # ) # self.groupby_left_pane.view.setHorizontalHeader(self.main_right_pane.view.horizontalHeader()) # Top toolbar self.top_bar = QToolBar() # PS: Actions with QAction::LowPriority will not show the text labels self.top_bar.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) # checkable group action self.groupby_action = self.top_bar.addAction(FIcon(0xF14E0), self.tr("Group by"), self.on_group_changed) self.groupby_action.setToolTip( self.tr("Group variants according to choosen columns")) self.groupby_action.setCheckable(True) self.groupby_action.setChecked(False) # groupbylist self.groupbylist_action = self.top_bar.addAction("chr,pos,ref") self.groupbylist_action.setVisible(False) self.groupbylist_action.triggered.connect(self._show_group_dialog) self.top_bar.addSeparator() # Save selection self.save_action = self.top_bar.addAction(FIcon(0xF0F87), self.tr("Save selection"), self.on_save_selection) self.save_action.setToolTip( self.tr("Save the current variants into a new selection")) # self.save_action.setPriority(QAction.LowPriority) # Refresh UI button action = self.top_bar.addAction(FIcon(0xF0450), self.tr("Refresh"), self.on_refresh) action.setToolTip(self.tr("Refresh the current list of variants")) # action.setPriority(QAction.LowPriority) # Formatter tools self.top_bar.addSeparator() spacer = QWidget() spacer.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) self.top_bar.addWidget(spacer) # Add formatters to combobox self.formatter_combo = QComboBox() self.settings = QSettings() self.add_available_formatters() # Error handling self.log_edit = QLabel() self.log_edit.setMaximumHeight(30) self.log_edit.setStyleSheet( "QWidget{{background-color:'{}'; color:'{}'}}".format( style.WARNING_BACKGROUND_COLOR, style.WARNING_TEXT_COLOR)) self.log_edit.hide() self.log_edit.setFrameStyle(QFrame.StyledPanel | QFrame.Raised) # Setup layout main_layout = QVBoxLayout() main_layout.addWidget(self.top_bar) main_layout.addWidget(self.splitter) main_layout.addWidget(self.log_edit) self.setLayout(main_layout) # Make connection self.main_right_pane.view.selectionModel().currentRowChanged.connect( lambda x, _: self.on_variant_clicked(x)) self.groupby_left_pane.view.selectionModel().currentRowChanged.connect( lambda x, _: self.on_variant_clicked(x)) self.groupby_left_pane.no_variant.connect(self.on_no_variant) # Connect errors from async runnables self.main_right_pane.runnable_exception.connect(self.set_message) self.groupby_left_pane.runnable_exception.connect(self.set_message) # Default group self.last_group = ["chr"] # Save fields between group/ungroup self.save_fields = list() self.save_filters = list()
def contextMenuEvent(self, event: QContextMenuEvent): """Override: Show contextual menu over the current variant""" if not self.show_popup_menu: return menu = QMenu(self) pos = self.view.viewport().mapFromGlobal(event.globalPos()) current_index = self.view.indexAt(pos) if not current_index.isValid(): return current_variant = self.model.variant(current_index.row()) full_variant = sql.get_one_variant(self.conn, current_variant["id"]) # Update variant with currently displayed fields (not in variants table) full_variant.update(current_variant) # Copy action: Copy the variant reference ID in to the clipboard formatted_variant = "{chr}:{pos}-{ref}-{alt}".format(**full_variant) menu.addAction( FIcon(0xF014C), formatted_variant, functools.partial(QApplication.instance().clipboard().setText, formatted_variant), ) # Create favorite action fav_action = menu.addAction( self.tr("&Unmark favorite") if bool(full_variant["favorite"]) else self.tr("&Mark as favorite")) fav_action.setCheckable(True) fav_action.setChecked(bool(full_variant["favorite"])) fav_action.toggled.connect(self.update_favorites) # Create classication action class_menu = menu.addMenu(self.tr("Classification")) for key, value in cm.CLASSIFICATION.items(): action = class_menu.addAction(FIcon(cm.CLASSIFICATION_ICONS[key]), value) action.setData(key) on_click = functools.partial(self.update_classification, current_index, key) action.triggered.connect(on_click) # Create external links links_menu = menu.addMenu(self.tr("External links")) self.settings.beginGroup("plugins/variant_view/links") # Display only external links with placeholders that can be mapped for key in self.settings.childKeys(): format_string = self.settings.value(key) # Get placeholders field_names = { name for text, name, spec, conv in string.Formatter().parse( format_string) } if field_names & full_variant.keys(): # Full or partial mapping => accepted link links_menu.addAction( key, functools.partial( QDesktopServices.openUrl, QUrl(format_string.format(**full_variant), QUrl.TolerantMode), ), ) self.settings.endGroup() # Comment action on_edit = functools.partial(self.edit_comment, current_index) menu.addAction(self.tr("&Edit comment ..."), on_edit) # Edit menu menu.addSeparator() menu.addAction(FIcon(0xF018F), self.tr("&Copy"), self.copy_to_clipboard, QKeySequence.Copy) menu.addAction( FIcon(0xF0486), self.tr("&Select all"), self.select_all, QKeySequence.SelectAll, ) # Display menu.exec_(event.globalPos())
def __init__(self, parent=None, show_popup_menu=True): """ Args: parent: parent widget show_popup_menu (boolean, optional: If False, disable the context menu over variants. For example the group pane should be disabled in order to avoid partial/false informations to be displayed in this menu. Hacky Note: also used to rename variants to groups in the page box... """ super().__init__(parent) self.parent = parent self.show_popup_menu = show_popup_menu self.view = LoadingTableView() self.bottom_bar = QToolBar() self.settings = QSettings() # self.view.setFrameStyle(QFrame.NoFrame) self.view.setAlternatingRowColors(True) self.view.horizontalHeader().setStretchLastSection(True) self.view.setSelectionMode(QAbstractItemView.SingleSelection) self.view.verticalHeader().hide() self.view.setSortingEnabled(True) self.view.setSelectionBehavior(QAbstractItemView.SelectRows) self.view.setSelectionMode(QAbstractItemView.ExtendedSelection) ## self.view.setIndentation(0) self.view.setIconSize(QSize(22, 22)) self.view.horizontalHeader().setSectionsMovable(True) # Setup model self.model = VariantModel() self.view.setModel(self.model) # Setup delegate self.delegate = VariantDelegate() self.view.setItemDelegate(self.delegate) # setup toolbar spacer = QWidget() spacer.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) self.page_box = QLineEdit() self.page_box.setValidator(QIntValidator()) self.page_box.setFixedWidth(50) self.page_box.setValidator(QIntValidator()) if LOGGER.getEffectiveLevel() == DEBUG: # Display SQL query on debug mode only self.bottom_bar.addAction(FIcon(0xF0866), self.tr("Show SQL query"), self.on_show_sql) # Display nb of variants/groups and pages self.info_label = QLabel() self.bottom_bar.addWidget(self.info_label) self.bottom_bar.addWidget(spacer) self.bottom_bar.setIconSize(QSize(16, 16)) self.bottom_bar.setMaximumHeight(30) self.bottom_bar.setContentsMargins(0, 0, 0, 0) self.pagging_actions = [] self.pagging_actions.append( self.bottom_bar.addAction(FIcon(0xF0600), "<<", self.on_page_clicked)) self.pagging_actions.append( self.bottom_bar.addAction(FIcon(0xF0141), "<", self.on_page_clicked)) self.bottom_bar.addWidget(self.page_box) self.pagging_actions.append( self.bottom_bar.addAction(FIcon(0xF0142), ">", self.on_page_clicked)) self.pagging_actions.append( self.bottom_bar.addAction(FIcon(0xF0601), ">>", self.on_page_clicked)) self.page_box.returnPressed.connect(self.on_page_changed) main_layout = QVBoxLayout() main_layout.setContentsMargins(0, 0, 0, 0) main_layout.addWidget(self.view) main_layout.addWidget(self.bottom_bar) self.setLayout(main_layout) # Async stuff # broadcast focus signal self.row_to_be_selected = None # Get the status of async load: started/finished self.model.loading.connect(self._set_loading) # Queries are finished (yes its redundant with loading signal...) self.model.load_finished.connect(self.loaded) # Connect errors from async runnables self.model.runnable_exception.connect(self.runnable_exception)
class CutestyleFormatter(Formatter): DISPLAY_NAME = "Cute style" BASE_COLOR = {"A": "green", "C": "red", "T": "red"} SO_COLOR = { # https://natsukis.livejournal.com/2048.html "missense_variant": "#bb96ff", "synonymous_variant": "#67eebd", "stop_gained": "#ed6d79", "stop_lost": "#ed6d79", "frameshift_variant": "#ff89b5", } ACMG_ICON = { "0": FIcon(0xF03A1, "lightgray"), "1": FIcon(0xF03A4, "#71e096"), "2": FIcon(0xF03A7, "#71e096"), "3": FIcon(0xF03AA, "#f5a26f"), "4": FIcon(0xF03AD, "#ed6d79"), "5": FIcon(0xF03B1, "#ed6d79"), } IMPACT_COLOR = { "HIGH": "#ff4b5c", "LOW": "#056674", "MODERATE": "#ecad7d", "MODIFIER": "#ecad7d", } FAV_ICON = {0: FIcon(0xF00C3), 1: FIcon(0xF00C0)} # Cache genotype icons # Values in gt field as keys (str), FIcon as values GENOTYPE_ICONS = { key: FIcon(val) for key, val in cm.GENOTYPE_ICONS.items() } def __init__(self): super().__init__() def paint(self, painter: QPainter, option: QStyleOptionViewItem, index: QModelIndex): """Apply graphical formatting to each item in each displayed column in the view""" brush = QBrush() pen = QPen() font = QFont() if option.state & QStyle.State_Selected: text_color = option.palette.color(QPalette.Normal, QPalette.BrightText) else: text_color = option.palette.color(QPalette.Normal, QPalette.Text) is_selected = option.state & QStyle.State_Selected # Default theme color pen.setColor(text_color) field_name = self.field_name(index).lower() value = self.value(index) # Colour bases (default color is the one of the current theme) if (field_name == "ref" or field_name == "alt") and (value in ("A", "C", "G", "T") and not is_selected): pen.setColor( self.BASE_COLOR.get(value, option.palette.color(QPalette.WindowText))) if field_name == "impact" and not is_selected: font.setBold(True) pen.setColor( self.IMPACT_COLOR.get(value, self.IMPACT_COLOR["MODIFIER"])) if field_name == "gene" and not is_selected: pen.setColor("#6a9fca") if field_name == "classification": icon = self.ACMG_ICON.get(str(value), self.ACMG_ICON["0"]) self.draw_icon(painter, option.rect, icon) return if field_name == "favorite": icon = self.FAV_ICON.get(int(value), self.FAV_ICON[0]) self.draw_icon(painter, option.rect, icon) return if field_name == "hgvs_c": font.setBold(True) m = re.search(r"([cnm]\..+)", str(value)) if m: value = m.group(1) if field_name == "hgvs_p": font.setBold(True) m = re.search(r"(p\..+)", str(value)) if m: value = m.group(1) if re.match(r"sample\[.+\]\.gt", field_name): icon = self.GENOTYPE_ICONS.get(int(value), self.GENOTYPE_ICONS[-1]) self.draw_icon(painter, option.rect, icon) return if field_name == "consequence": values = str(self.value(index)).split("&") metrics = QFontMetrics(font) x = option.rect.x() + 5 # y = option.rect.center().y() for value in values: width = metrics.width(value) height = metrics.height() rect = QRect(x, 0, width + 15, height + 10) rect.moveCenter(option.rect.center()) rect.moveLeft(x) painter.setFont(font) painter.setClipRect(option.rect, Qt.IntersectClip) painter.setBrush( QBrush(QColor(self.SO_COLOR.get(value, "#90d4f7")))) painter.setPen(Qt.NoPen) painter.drawRoundedRect(rect, 3, 3) painter.setPen(QPen(QColor("white"))) painter.drawText(rect, Qt.AlignCenter | Qt.AlignVCenter, value) x += width + 20 painter.setClipping(False) return if field_name == "rsid": self.draw_url(painter, option.rect, value, QUrl("http://www.google.fr"), index) return painter.setBrush(brush) painter.setPen(pen) painter.setFont(font) painter.drawText(option.rect, option.displayAlignment, value)
def __init__(self, parent=None): super().__init__(parent) # Create fav button self.fav_button = QToolButton() self.fav_button.setCheckable(True) self.fav_button.setAutoRaise(True) self.fav_button.clicked.connect(self._form_changed) icon = QIcon() icon.addPixmap(FIcon(0xF00C3).pixmap(32, 32), QIcon.Normal, QIcon.Off) icon.addPixmap(FIcon(0xF00C0).pixmap(32, 32), QIcon.Normal, QIcon.On) self.fav_button.setIcon(icon) # Create classification combobox self.class_edit = QComboBox() self.class_edit.setFrame(False) self.class_edit.currentIndexChanged.connect(self._form_changed) for key in style.CLASSIFICATION: self.class_edit.addItem( FIcon( style.CLASSIFICATION[key]["icon"], style.CLASSIFICATION[key]["color"], ), style.CLASSIFICATION[key]["name"], ) # Create comment form . This is a stack widget with a PlainText editor # and a Markdown preview as QTextBrowser self.comment_edit = QPlainTextEdit() self.comment_edit.setPlaceholderText("Write a comment in markdown ...") self.comment_edit.textChanged.connect(self._form_changed) self.comment_preview = QTextBrowser() self.comment_preview.setPlaceholderText("Press edit to add a comment ...") self.comment_preview.setAlignment(Qt.AlignTop | Qt.AlignLeft) self.comment_preview.setOpenExternalLinks(True) self.comment_preview.setFrameStyle(QFrame.NoFrame) # Create stacked self.stack = QStackedWidget() self.stack.addWidget(self.comment_edit) self.stack.addWidget(self.comment_preview) self.stack.setCurrentIndex(1) # Build layout self.setFrameShape(QFrame.StyledPanel) # header layout title_layout = QHBoxLayout() title_layout.addWidget(self.class_edit) title_layout.addWidget(self.fav_button) # Button layout self.switch_button = QPushButton(self.tr("Edit comment...")) self.switch_button.setFlat(True) self.save_button = QPushButton(self.tr("Save")) self.save_button.setFlat(True) bar_layout = QHBoxLayout() bar_layout.addWidget(self.switch_button) bar_layout.addStretch() bar_layout.addWidget(self.save_button) # Main layout v_layout = QVBoxLayout() v_layout.addLayout(title_layout) v_layout.addWidget(self.stack) v_layout.addLayout(bar_layout) self.setLayout(v_layout) # Create connection self.switch_button.clicked.connect(self.switch_mode) self.save_button.clicked.connect(self._on_save) self._form_changed.connect(lambda: self.save_button.setEnabled(True))