Source code for schrodinger.ui.sequencealignment.sequence_area

"""
This class implements a sequence area widget that performs actual display
and manipulation of the sequences and annotation data.

Copyright Schrodinger, LLC. All rights reserved.
"""

# Contributors: Piotr Rotkiewicz

import math
from past.utils import old_div

from schrodinger.Qt import QtCore
from schrodinger.Qt import QtGui
from schrodinger.Qt import QtWidgets

from . import constants
from . import maestro as maestro_helpers


[docs]class SequenceArea(QtWidgets.QWidget): """ The sequence area displays sequence rows and associated annotations. It is a scrollable widget. The presence of horizontal and vertical scrollbars depends on current sequence viewer mode (wrapped or not wrapped). """
[docs] def __init__(self, parent=None): # Initialize base class. QtWidgets.QWidget.__init__(self, parent) self.viewer = None #Parent sequence viewer widget. self.init_row = None # Initial clicked row. self.init_pos = None # Initial clicked row position. self.last_pos = None # Last pointed row position. self.init_res = None self.last_found_position = None # Where a residue was found. self.selecting_columns = False self.cursor_enabled = False # text edit cursor self.cursor_row = None # of text cursor self.cursor_pos = None # of text cursor self.is_dragging_rect = False # is a rectangle dragged? self.drag_coords = (None, None, None, None, None) #of dragged rect. self.last_constraint_pos = None # Previously clicked constraint pos. self.last_constraint_seq = None self.last_tooltip_pos = None # of mouse self.last_tooltip_row = None # of mouse self.last_key = None # Last key pressed. self.unselect = False # Ctrl-selection. (depends on init clicked res) #: Used for Shift-click selection self.min_row = None self.max_row = None self.min_pos = None self.max_pos = None self.position_moved = False self.horizontal_scroll_bar = QtWidgets.QScrollBar(self) self.vertical_scroll_bar = QtWidgets.QScrollBar(self) self.horizontal_scroll_bar.setOrientation(QtCore.Qt.Horizontal) self.horizontal_scroll_bar.valueChanged.connect( self.horizontalScrollBarChanged) self.vertical_scroll_bar.setOrientation(QtCore.Qt.Vertical) self.vertical_scroll_bar.valueChanged.connect( self.verticalScrollBarChanged) # Set mouse tracking True to receive mouse events even when the mouse # button is released (important for mouse hover tooltips). self.setMouseTracking(True) self.setFocusPolicy(QtCore.Qt.WheelFocus) # of widget self.ignore_click = False self.emphasized = False self.selection_changed = False self.alignment_changed = False self.auto_scroll = False
[docs] def focusInEvent(self, event): if event.reason() == QtCore.Qt.ActiveWindowFocusReason: self.ignore_click = True
[docs] def horizontalScrollBarChanged(self, value): """ Horizontal scrollbar callback. :type value: int :param value: new scrollbar value """ self.viewer.left_column = value self.viewer.generateRows() self.viewer.update()
[docs] def verticalScrollBarChanged(self, value): """ Vertical scrollbar callback. :type value: int :param value: new scrollbar value """ self.viewer.top_row = value self.viewer.update()
[docs] def mouseToCoords(self, x, y): """ Translates mouse coordinates in global space to sequence area discrete coordinates (row, position). :type x: int :param x: x coordinate :type y: int :param y: y coordinate :rtype: (int, int) :return: (row, position) coordinates """ xoffset = 0 if self.viewer.display_boundaries: xoffset += 6 * self.viewer.font_width yoffset = 0 if self.viewer.has_header_row: yoffset = 20 return (old_div((x - self.viewer.margin - xoffset), self.viewer.cell_width), old_div((y - self.viewer.margin - yoffset), self.viewer.font_height))
[docs] def rowPosToCoords(self, row, pos, visible_only=True): """ Translates (row, position) coordinates to widget coordinates. This method is used for drawing auxiliary objects such as text edit cursor. :type row: (`Sequence`, int, int, int) :param row: row :type pos: int :param pos: row position :rtype: (int, int) :return: pair of widget coordinates, or (None, None) if the conversion fails. """ rows = self.viewer.rows offset = 0 if self.viewer.display_boundaries: offset += 6 * self.viewer.font_width y_position = self.viewer.margin if self.viewer.has_header_row: y_position += 20 height = self.height() reference = self.viewer.sequence_group.reference if not reference: reference = self.viewer.sequence_group.profile row_idx = 0 num_persistent_rows = 0 if not self.viewer.wrapped: for tmp_row in rows: num_persistent_rows += 1 seq, start, end, row_height = tmp_row if not (seq.type == constants.SEQ_RULER or seq.global_sequence or seq == reference): break top_row = self.viewer.top_row + num_persistent_rows row_idx = 0 for tmp_row in rows: row_idx = row_idx + 1 seq, start, end, row_height = tmp_row if self.viewer.wrapped or not (seq.type == constants.SEQ_RULER or seq.global_sequence or seq == reference): if row_idx < top_row: continue if visible_only and y_position > height: break seq, start, end, row_height = tmp_row if tmp_row == row: return ((pos - start) * self.viewer.cell_width + self.viewer.margin + offset, y_position) # Display the sequence if it is visible. if seq and seq.visible: y_position += row_height * self.viewer.font_height return (None, None)
[docs] def mouseToRowPosition(self, x, y): """ Converts mouse coordinates to (row, position, residue) tuple. :type x: int :param x: x coordinate :type y: int :param y: y coordinate :rtype: (row, int, `Residue`) :return: row, position pair or (None, None, None) if the conversion fails. """ rows = self.viewer.rows if rows is None or len(rows) == 0: return (None, None, None) row_idx = 0 current_row = 0 coord_x, coord_y = self.mouseToCoords(x, y) found = False reference = self.viewer.sequence_group.reference if not reference: reference = self.viewer.sequence_group.profile row_idx = 0 num_persistent_rows = 0 if not self.viewer.wrapped: for row in rows: num_persistent_rows += 1 seq, start, end, row_height = row if not (seq.type == constants.SEQ_RULER or seq.global_sequence or seq == reference): break top_row = self.viewer.top_row + num_persistent_rows row_idx = 0 for row in rows: row_idx = row_idx + 1 seq, start, end, row_height = row if self.viewer.wrapped or not (seq.type == constants.SEQ_RULER or seq.global_sequence or seq == reference): if row_idx < top_row: continue for h in range(row_height): if coord_y == current_row: found = True break current_row = current_row + 1 if found: break residue = None position = None if found: position = coord_x + start self.last_found_position = position if position >= 0 and position < seq.length() and position < end: residue = seq.residues[position] else: return (None, None, None) return (row, self.last_found_position, residue)
[docs] def mouseDoubleClickEvent(self, event): """ Handles Qt double click event. """ # Compute sequence area coordinates. row, pos, res = self.mouseToRowPosition(event.x(), event.y()) if row and pos and res: sequence, start, end, length = row if sequence and sequence.parent_sequence and \ sequence.type == constants.SEQ_SECONDARY: code = res.code parent_residues = sequence.parent_sequence.residues if not (event.modifiers() & QtCore.Qt.ControlModifier): sequence.parent_sequence.unselectResidues() pos = init_pos = sequence.residues.index(res) while pos >= 0 and (sequence.residues[pos].code == code or sequence.residues[pos].is_gap): parent_residues[pos].selected = True pos -= 1 pos = init_pos while pos < len(parent_residues) and \ (sequence.residues[pos].code == code or \ sequence.residues[pos].is_gap): parent_residues[pos].selected = True pos += 1 self.viewer.selection_changed = True self.viewer.updateView()
[docs] def selectExtent(self, row, pos, reset=False): """ Select minimum residue extent between previous selection and current position. """ if not row: return row_idx = self.viewer.rows.index(row) if reset or self.min_row is None: self.min_row = self.max_row = row_idx self.min_pos = self.max_pos = pos return if row_idx < self.min_row: self.min_row = row_idx if row_idx > self.max_row: self.max_row = row_idx if pos < self.min_pos: self.min_pos = pos if pos > self.max_pos: self.max_pos = pos self.viewer.sequence_group.selectResidues( self.viewer.rows[self.min_row], self.min_pos, self.viewer.rows[self.max_row], self.max_pos, True) self.selection_changed = True
[docs] def mousePressEvent(self, event): """ Handles Qt mouse press event. :type event: QMouseEvent :param event: Qt mouse event. """ if self.ignore_click: return self.last_found_position = None self.position_moved = False self.selection_changed = False self.alignment_changed = False # Compute sequence area coordinates. row, pos, res = self.mouseToRowPosition(event.x(), event.y()) self.last_tooltip_pos = pos self.last_tooltip_row = row # Middle button centers on a clicked residue in Maestro workspace. # Otherwise, it doesn't have any effect. if event.button() == QtCore.Qt.MidButton: if res and res.sequence and res.sequence.from_maestro and maestro_helpers.hasMaestro( ): maestro_helpers.maestroCenterOnResidue(self.viewer, self, res) return # Right button popups a context menu and returns, unless in # Insert/Remove Gaps mode. if event.button() == QtCore.Qt.RightButton and \ self.viewer.mode != constants.MODE_INSERT_GAPS: if self.viewer.sequence_popup_menu: self.viewer.sequence_popup_menu(event.globalPos(), res) return # When clicked on a reference ligand, # add a ligand-query constraint and return if row and self.viewer.build_mode: seq, start, end, height = row if seq.parent_sequence == self.viewer.sequence_group.reference and \ seq.annotation_type == constants.ANNOTATION_LIGAND: if res and not res.is_gap: # Toggle color of the ligand residue # to indicate a constrained position if hasattr(res, 'query_constraint') and \ res.query_constraint: res.color = (255, 255, 255) res.query_constraint = False else: res.color = (255, 0, 0) res.query_constraint = True return # In case Shift is pressed, select residues between this and # previously clicked residue. if event.modifiers() & QtCore.Qt.ShiftModifier and \ self.viewer.mode != constants.MODE_GRAB_AND_DRAG: if row: seq, start, end, height = row if seq.type == constants.SEQ_RULER: self.viewer.sequence_group.selectColumns(self.prev_pos, pos) self.updateColumns(self.prev_pos, pos) elif self.viewer.mode == constants.MODE_SELECT_AND_SLIDE or \ self.viewer.mode == constants.MODE_SELECT_ONLY or \ (self.viewer.build_mode and self.viewer.sequence_group.build_mode == \ constants.PRIME_MODE_COMPOSITE): self.selectExtent(row, pos) self.selection_changed = True self.update() return else: self.selectExtent(row, pos, reset=True) # Store initial row and position. # @note: If the rows are going to be regenerated between press/release # events, these variables will be not valid. if (not ((event.modifiers() & QtCore.Qt.ShiftModifier) and self.init_row and self.init_pos)) or \ self.viewer.mode == constants.MODE_GRAB_AND_DRAG: self.init_row = row self.init_pos = pos self.init_res = res self.last_pos = pos self.prev_pos = pos if res and res.selected: self.unselect = True else: self.unselect = False # Will be set to True if selecting columns. self.selecting_columns = False # Set cursor shape according to the current mode. if self.viewer.mode == constants.MODE_GRAB_AND_DRAG: self.setCursor(QtCore.Qt.ArrowCursor) # Store current state for undo. if row: seq, start, end, height = row # In case we are editing a child sequence, use parent # sequence for undo. if seq: if seq.parent_sequence: seq = seq.parent_sequence if seq in self.viewer.sequence_group.sequences: index = self.viewer.sequence_group.sequences.index(seq) self.viewer.undo_stack.storeStateSequence( self.viewer.sequence_group, index, label="Edit Sequence") elif self.viewer.mode == constants.MODE_SELECT_AND_SLIDE or \ self.viewer.mode == constants.MODE_SELECT_ONLY: self.setCursor(QtCore.Qt.CrossCursor) self.selection_changed = True # If clicked on the ruler, start selecting columns. if row and self.viewer.mode != constants.MODE_VIEW_ONLY: seq, start, end, height = row if seq.type == constants.SEQ_RULER: self.selecting_columns = True self.setCursor(QtCore.Qt.ArrowCursor) is_selected = self.viewer.sequence_group.isColumnSelected(pos) if not (event.modifiers() & QtCore.Qt.ControlModifier or event.modifiers() & QtCore.Qt.ShiftModifier): self.viewer.sequence_group.unselectAll() if is_selected: self.viewer.sequence_group.selectColumns(pos, pos, select=False) self.updateColumns(pos, pos) else: self.viewer.sequence_group.selectColumns(pos, pos) self.updateColumns(pos, pos) self.selection_changed = True self.update() return # Initially, disable the mouse cursor. self.cursor_enabled = False if self.viewer.mode == constants.MODE_VIEW_ONLY: pass elif self.viewer.mode == constants.MODE_INSERT_GAPS: self.setCursor(QtCore.Qt.PointingHandCursor) if res and res.sequence and \ (res.sequence.type == constants.SEQ_AMINO_ACIDS or res.sequence.type == constants.SEQ_SECONDARY): if seq.parent_sequence: seq = seq.parent_sequence index = self.viewer.sequence_group.sequences.index(seq) if not seq.selected: # Insert / remove gaps in only one sequence. if event.buttons() == QtCore.Qt.LeftButton: self.viewer.undo_stack.storeStateSequence( self.viewer.sequence_group, index, label="Insert Gap") self.viewer.sequence_group.insertGap( self.init_row, self.init_pos) self.viewer.sequence_group.updateHistory( self.init_pos, self.init_pos) self.viewer.updateView() elif event.buttons() == QtCore.Qt.RightButton: if res.is_gap: self.viewer.undo_stack.storeStateSequence( self.viewer.sequence_group, index, label="Remove Gap") self.viewer.sequence_group.removeGap( self.init_row, self.init_pos) self.viewer.sequence_group.updateHistory( self.init_pos, self.init_pos) self.viewer.updateView() else: # Insert / remove gaps in all selected sequences. if event.buttons() == QtCore.Qt.LeftButton: self.viewer.undo_stack.storeStateGroupDeep( self.viewer.sequence_group, label="Insert Gap") for row in self.viewer.rows: seq, start, end, height = row if seq.selected and not seq.parent_sequence: self.viewer.sequence_group.insertGap( row, self.init_pos) if seq.type == constants.SEQ_SEPARATOR: break self.viewer.updateView() elif event.buttons() == QtCore.Qt.RightButton: self.viewer.undo_stack.storeStateGroupDeep( self.viewer.sequence_group, label="Remove Gap") for row in self.viewer.rows: seq, start, end, height = row if seq.selected and not seq.parent_sequence: self.viewer.sequence_group.removeGap( row, self.init_pos) if seq.type == constants.SEQ_SEPARATOR: break self.viewer.updateView() elif ((self.viewer.mode == constants.MODE_SELECT_AND_SLIDE or self.viewer.mode == constants.MODE_EDIT) and res and res.selected and not (event.modifiers() & QtCore.Qt.ControlModifier)): self.startDraggingRect(row, pos) elif (not res) and \ not (event.modifiers() & QtCore.Qt.ControlModifier or event.modifiers() & QtCore.Qt.ShiftModifier) and \ not (self.viewer.set_constraints or self.viewer.set_query_constraints): self.viewer.sequence_group.unselectAll(make_active=bool(not res)) self.selection_changed = True self.update() return if not res: return if self.viewer.build_mode and \ self.viewer.sequence_group.build_mode == constants.PRIME_MODE_COMPOSITE: res.selected = True self.update() return if (self.viewer.mode == constants.MODE_SELECT_AND_SLIDE or self.viewer.mode == constants.MODE_SELECT_ONLY): if not (event.modifiers() & QtCore.Qt.ControlModifier or event.modifiers() & QtCore.Qt.ShiftModifier): new_state = True if self.viewer.mode != constants.MODE_SELECT_AND_SLIDE: new_state = not res.selected self.viewer.sequence_group.unselectAll() res.selected = new_state if res.sequence.parent_sequence: res.sequence.parent_sequence.residues[ res.sequence.residues.index(res)].selected = new_state self.update() return if (event.modifiers() & QtCore.Qt.ControlModifier): res.selected = not res.selected self.update()
[docs] def startDraggingRect(self, row, pos): """ Begins rectangle dragging. Determines boundaries of the area that needs to be dragged and calculates coordinates of the auxiliary rectangle. The initial coordinates (row and position) should be inside the area that needs to be dragged. This method assumes the area is rectangular. Otherwise, results are non-predictable - usually a rectangular superset of irregular area will be dragged. :type row: (`Sequence`, int, int ,int) :param row: sequence viewer row in the dragged area :type pos: int :param pos: row position """ seq, start, end, height = row if seq and seq.residues: # Find horizontal extends min_pos = pos while min_pos >= 0 and \ seq.residues[min_pos].selected: min_pos -= 1 max_pos = pos while max_pos < len(seq.residues) and \ seq.residues[max_pos].selected: max_pos += 1 min_pos += 1 max_pos -= 1 min_row = max_row = row row_index = last_row_index = self.viewer.rows.index(row) # Find top row while row_index > 0: row_index -= 1 seq, start, end, height = self.viewer.rows[row_index] if seq.parent_sequence: seq = seq.parent_sequence if len(seq.residues) <= pos or not seq.residues[pos].selected: break last_row_index = row_index min_row = self.viewer.rows[last_row_index] # Find bottom row row_index = last_row_index = self.viewer.rows.index(row) while row_index < len(self.viewer.rows) - 1: row_index += 1 seq, start, end, height = self.viewer.rows[row_index] if seq.parent_sequence: seq = seq.parent_sequence if len(seq.residues) <= pos or not seq.residues[pos].selected: break last_row_index = row_index max_row = self.viewer.rows[last_row_index] self.is_dragging_rect = True self.drag_coords = (min_row, max_row, min_pos, max_pos, min_pos) x0, y0 = self.rowPosToCoords(min_row, min_pos) x1, y1 = self.rowPosToCoords(max_row, max_pos) self.setCursor(QtCore.Qt.ClosedHandCursor) self.update()
[docs] def mouseReleaseEvent(self, event): """ Handles Qt mouse release event. :type event: QMouseEvent :param event: Qt mouse event. """ self.auto_scroll = False if self.ignore_click: self.ignore_click = False return seq = None if self.is_dragging_rect: self.is_dragging_rect = False min_row, max_row, min_pos, max_pos, init_pos = self.drag_coords if min_pos != init_pos: self.viewer.undo_stack.storeStateGroupDeep( self.viewer.sequence_group, label="Slide Selected Block") self.viewer.sequence_group.grabAndDragBlock( self.viewer.rows, min_row, init_pos, max_row, min_pos, abs(max_pos - min_pos), int(self.viewer.lock_downstream)) self.drag_coords = (None, None, None, None, None) self.viewer.sequence_group.updateHistory(init_pos, min_pos) self.alignment_changed = True self.viewer.updateView() else: self.drag_coords = (None, None, None, None, None) self.update() # Translate mouse cursor coordinates to row and position. row, pos, res = self.mouseToRowPosition(event.x(), event.y()) if self.viewer.set_constraints or self.viewer.set_query_constraints: if row and not self.position_moved: seq, start, end, height = row if self.last_constraint_seq and not ( self.viewer.set_query_constraints and \ pos == self.last_constraint_pos): self.viewer.sequence_group.selectResidues( row, pos, row, pos, True) self.viewer.sequence_group.expandSelectionRef() self.viewer.sequence_group.addConstraint( self.last_constraint_seq, self.last_constraint_pos, seq, pos, for_prime=self.viewer.set_query_constraints) self.viewer.sequence_group.unselectAll() self.viewer.updateView() maestro_helpers.synchronizePropertiesWithMaestro( self.viewer, selection=True) self.last_constraint_pos = None self.last_constraint_seq = None self.viewer.displayMessage("Pick first query residue " "to add or remove a constraint.") else: # self.viewer.sequence_group.unselectAll() self.viewer.sequence_group.selectResidues( row, pos, row, pos, True) self.viewer.sequence_group.expandSelectionRef() self.last_constraint_seq = seq self.last_constraint_pos = pos self.viewer.displayMessage("Pick second query residue " "to add or remove a constraint.") self.viewer.update() if res and res.selected and \ (self.viewer.mode == constants.MODE_SELECT_AND_SLIDE or self.viewer.mode == constants.MODE_SELECT_ONLY): self.setCursor(QtCore.Qt.OpenHandCursor) self.last_pos = pos else: if res and self.viewer.mode == constants.MODE_EDIT and \ (res.sequence.type == constants.SEQ_AMINO_ACIDS or (res.sequence.type == constants.SEQ_ANNOTATION and (res.sequence.annotation_type == constants.ANNOTATION_SSP or res.sequence.annotation_type == constants.ANNOTATION_CUSTOM))): if self.is_dragging_rect: self.setCursor(QtCore.Qt.ClosedHandCursor) else: self.setCursor(QtCore.Qt.IBeamCursor) elif res and self.viewer.mode == constants.MODE_INSERT_GAPS: self.setCursor(QtCore.Qt.PointingHandCursor) else: self.setCursor(QtCore.Qt.ArrowCursor) # Track changes. if self.viewer.mode == constants.MODE_GRAB_AND_DRAG: self.viewer.sequence_group.updateHistory(self.init_pos, pos) # When mouse button is pushed and released in the same position, # move the edit cursor there. if self.viewer.mode == constants.MODE_EDIT: if res and row == self.init_row and pos == self.init_pos and \ (res.sequence.type == constants.SEQ_AMINO_ACIDS or res.sequence.type == constants.SEQ_CUSTOM or (res.sequence.type == constants.SEQ_ANNOTATION and res.sequence.annotation_type == constants.ANNOTATION_SSP)): self.cursor_row = row if not self.viewer.wrapped: self.cursor_pos = pos - self.viewer.left_column else: self.cursor_pos = pos cursor_x, cursor_y = \ self.rowPosToCoords(row, pos) if cursor_x and cursor_y: self.cursor_enabled = True else: self.cursor_row = None self.cursor_pos = None self.update() if self.viewer.build_mode and \ self.viewer.sequence_group.build_mode == constants.PRIME_MODE_COMPOSITE: self.viewer.sequence_group.markTemplateRegion() self.update() self.viewer.selection_changed = True self.viewer.updateView(generate_rows=False) # Update profile in case identity is calculated in selected columns. if self.viewer.sequence_group.identity_in_columns: self.viewer.updateView() # Show mouse hover tooltip. self.showToolTip(event.globalPos(), pos, res, seq) if self.selection_changed: self.viewer.selection_changed = True self.viewer.updateView(generate_rows=False, update_colors=False) if self.alignment_changed: self.viewer.alignment_changed = True self.alignment_changed = False self.viewer.updateView()
[docs] def keyReleaseEvent(self, event): """ Handles Qt key release events. :type event: QKeyEvent :param event: Qt key release event """ if event.type() == QtCore.QEvent.KeyRelease: key = event.key() if key == QtCore.Qt.Key_Shift: self.init_row = None self.init_pos = None self.init_res = None
[docs] def keyPressEvent(self, event): """ Handles Qt key press events. :type event: QKeyEvent :param event: Qt key press event """ if self.viewer.mode != constants.MODE_EDIT: return if self.cursor_row is not None and self.cursor_pos is not None: seq, start, end, row_height = self.cursor_row if self.cursor_row not in self.viewer.rows: self.cursor_row = None self.cursor_pos = None self.update() return cursor_row_index = self.viewer.rows.index(self.cursor_row) if event.type() == QtCore.QEvent.KeyPress: key = event.key() if seq.parent_sequence: seq = seq.parent_sequence if seq not in self.viewer.sequence_group.sequences: return seq_index = self.viewer.sequence_group.sequences.index(seq) if key == QtCore.Qt.Key_Space: if seq.type == constants.SEQ_CUSTOM: seq.residues[self.cursor_pos + self.viewer.left_column].code = ' ' self.cursor_pos += 1 self.viewer.update() return # Shift key is used to invert current # grab-and-drag behavior invert = bool(event.modifiers() & QtCore.Qt.ShiftModifier) if self.last_key != key: self.viewer.undo_stack.storeStateSequence( self.viewer.sequence_group, seq_index, label="Edit Sequence") if not self.viewer.wrapped: self.viewer.sequence_group.grabAndDrag( self.cursor_row, self.cursor_pos + start, self.cursor_row, self.cursor_pos + start + 1, int(self.viewer.lock_downstream) ^ int(invert), gap_insert_mode=True) self.alignment_changed = True self.cursor_pos += 1 if self.cursor_pos >= self.viewer.max_columns - 1: self.cursor_pos = self.viewer.max_columns - 1 if event.modifiers() & QtCore.Qt.ControlModifier: self.viewer.left_column += 10 else: self.viewer.left_column += 1 else: self.viewer.sequence_group.grabAndDrag( self.cursor_row, self.cursor_pos, self.cursor_row, self.cursor_pos + 1, int(self.viewer.lock_downstream) ^ int(invert), gap_insert_mode=True) self.alignment_changed = True self.cursor_pos += 1 if self.cursor_pos >= end: self.cursor_pos = end - 1 self.viewer.updateView(repaint=False) self.cursor_row = self.viewer.rows[cursor_row_index] self.viewer.update() elif key == QtCore.Qt.Key_Backspace: # Do not delete first position if len(seq.residues) <= 1 or \ self.cursor_pos == start: return # Shift key is used to invert current grab-and-drag # behavior. invert = bool(event.modifiers() & QtCore.Qt.ShiftModifier) if self.last_key != key: self.viewer.undo_stack.storeStateSequence( self.viewer.sequence_group, seq_index, label="Edit Sequence") if not self.viewer.wrapped: if seq.residues[self.cursor_pos + start - 1].is_gap: self.viewer.sequence_group.grabAndDrag( self.cursor_row, self.cursor_pos + start, self.cursor_row, self.cursor_pos + start - 1, True) self.alignment_changed = True self.cursor_pos -= 1 if self.cursor_pos < 0: self.cursor_pos = 0 self.viewer.left_column -= 1 if self.viewer.left_column < 0: self.viewer.left_column = 0 elif (not seq.from_maestro) or self.viewer.mutate: seq.residues.remove(seq.residues[self.cursor_pos + start - 1]) for child in seq.children: child.residues.remove( child.residues[self.cursor_pos + start - 1]) self.cursor_pos -= 1 if self.cursor_pos < 0: self.cursor_pos = 0 self.viewer.left_column -= 1 if self.viewer.left_column < 0: self.viewer.left_column = 0 else: if seq.residues[self.cursor_pos - 1].is_gap: self.viewer.sequence_group.grabAndDrag( self.cursor_row, self.cursor_pos, self.cursor_row, self.cursor_pos - 1, True) self.alignment_changed = True self.cursor_pos -= 1 seq, start, end, height = self.cursor_row if self.cursor_pos < start: self.cursor_pos = start elif (not seq.from_maestro) or self.viewer.mutate: seq.residues.remove(seq.residues[self.cursor_pos - 1]) for child in seq.children: child.residues.remove( child.residues[self.cursor_pos - 1]) self.cursor_pos -= 1 seq, start, end, height = self.cursor_row if self.cursor_pos < start: self.cursor_pos = start self.viewer.updateView(repaint=False) self.cursor_row = self.viewer.rows[cursor_row_index] self.viewer.update() elif key == QtCore.Qt.Key_Delete: # Do not delete last residue if len(seq.residues) <= 1 or \ self.cursor_pos >= end - 1: return invert = bool(event.modifiers() & QtCore.Qt.ShiftModifier) if self.last_key != key: self.viewer.undo_stack.storeStateSequence( self.viewer.sequence_group, seq_index, label="Edit Sequence") if not self.viewer.wrapped: if seq.residues[self.cursor_pos + start].is_gap: self.viewer.sequence_group.grabAndDrag( self.cursor_row, self.cursor_pos + start + 1, self.cursor_row, self.cursor_pos + start, True) self.alignment_changed = True elif (not seq.from_maestro) or self.viewer.mutate: seq.residues.remove(seq.residues[self.cursor_pos + start]) for child in seq.children: child.residues.remove( child.residues[self.cursor_pos + start]) else: if seq.residues[self.cursor_pos].is_gap: self.viewer.sequence_group.grabAndDrag( self.cursor_row, self.cursor_pos + 1, self.cursor_row, self.cursor_pos, True) self.alignment_changed = True elif (not seq.from_maestro) or self.viewer.mutate: seq.residues.remove(seq.residues[self.cursor_pos]) for child in seq.children: child.residues.remove( child.residues[self.cursor_pos]) self.viewer.updateView(repaint=False) self.cursor_row = self.viewer.rows[cursor_row_index] self.viewer.update() elif key == QtCore.Qt.Key_Left: if event.modifiers() & QtCore.Qt.ControlModifier: self.cursor_pos -= 10 else: self.cursor_pos -= 1 if not self.viewer.wrapped: if self.cursor_pos < 0: self.cursor_pos = 0 if event.modifiers() & QtCore.Qt.ControlModifier: self.viewer.left_column -= 10 else: self.viewer.left_column -= 1 if self.viewer.left_column < 0: self.viewer.left_column = 0 index = self.viewer.rows.index(self.cursor_row) self.viewer.generateRows() self.cursor_row = self.viewer.rows[index] else: seq, start, end, height = self.cursor_row if self.cursor_pos < start: self.cursor_pos = start if event.modifiers() & QtCore.Qt.ShiftModifier: if self.shift_press_pos != self.cursor_pos: self.viewer.sequence_group.unselectAll() self.viewer.sequence_group.selectResidues( self.shift_press_row, self.shift_press_pos, self.cursor_row, self.cursor_pos, True) self.selection_changed = True self.update() self.update() elif key == QtCore.Qt.Key_Right: if event.modifiers() & QtCore.Qt.ControlModifier: self.cursor_pos += 10 else: self.cursor_pos += 1 if not self.viewer.wrapped: if self.cursor_pos >= self.viewer.max_columns - 1: self.cursor_pos = self.viewer.max_columns - 1 if event.modifiers() & QtCore.Qt.ControlModifier: self.viewer.left_column += 10 else: self.viewer.left_column += 1 index = self.viewer.rows.index(self.cursor_row) self.viewer.generateRows() self.cursor_row = self.viewer.rows[index] else: seq, start, end, height = self.cursor_row if self.cursor_pos >= end: self.cursor_pos = end - 1 if event.modifiers() & QtCore.Qt.ShiftModifier: if self.shift_press_pos != self.cursor_pos: self.viewer.sequence_group.unselectAll() self.viewer.sequence_group.selectResidues( self.shift_press_row, self.shift_press_pos, self.cursor_row, self.cursor_pos, True) self.selection_changed = True self.update() self.update() elif key == QtCore.Qt.Key_Down: row = self.cursor_row seq, prev_start, prev_end, height = row index = self.viewer.rows.index(row) while index < len(self.viewer.rows): index += 1 if index < len(self.viewer.rows): row = self.viewer.rows[index] seq, start, end, height = row if seq.type == constants.SEQ_AMINO_ACIDS or \ seq.type == constants.SEQ_CUSTOM or \ (seq.type == constants.SEQ_ANNOTATION and seq.annotation_type == constants.ANNOTATION_SSP): self.cursor_row = row offset = self.cursor_pos - prev_start self.cursor_pos = start + offset if self.cursor_pos >= end: self.cursor_pos = end - 1 break if event.modifiers() & QtCore.Qt.ShiftModifier: if self.shift_press_row != self.cursor_row: self.viewer.sequence_group.unselectAll() self.viewer.sequence_group.selectResidues( self.shift_press_row, self.shift_press_pos, self.cursor_row, self.cursor_pos, True) self.selection_changed = True self.update() elif key == QtCore.Qt.Key_Up: row = self.cursor_row seq, prev_start, prev_end, height = row index = self.viewer.rows.index(row) while index >= 0: index -= 1 if index >= 0: row = self.viewer.rows[index] seq, start, end, height = row if seq.type == constants.SEQ_AMINO_ACIDS or \ seq.type == constants.SEQ_CUSTOM or \ (seq.type == constants.SEQ_ANNOTATION and seq.annotation_type == constants.ANNOTATION_SSP): self.cursor_row = row offset = self.cursor_pos - prev_start self.cursor_pos = start + offset if self.cursor_pos >= end: self.cursor_pos = end - 1 break if event.modifiers() & QtCore.Qt.ShiftModifier: if self.shift_press_row != self.cursor_row: self.viewer.sequence_group.unselectAll() self.viewer.sequence_group.selectResidues( self.shift_press_row, self.shift_press_pos, self.cursor_row, self.cursor_pos, True) self.selection_changed = True self.update() elif key == QtCore.Qt.Key_Home: if self.viewer.wrapped: seq, start, end, height = self.cursor_row self.cursor_pos = start else: if self.cursor_pos == 0 and self.last_key == key: index = self.viewer.rows.index(self.cursor_row) self.horizontal_scroll_bar.setValue(0) # self.viewer.generateRows() self.cursor_row = self.viewer.rows[index] self.cursor_pos = 0 self.update() elif key == QtCore.Qt.Key_End: if self.viewer.wrapped: seq, start, end, height = self.cursor_row self.cursor_pos = end - 1 else: if self.cursor_pos == self.viewer.max_columns - 1 and \ self.last_key == key: index = self.viewer.rows.index(self.cursor_row) self.horizontal_scroll_bar.setValue(seq.length()) self.cursor_pos = seq.length() - 5 # self.viewer.generateRows() self.cursor_row = self.viewer.rows[index] self.cursor_pos = self.viewer.max_columns - 1 self.update() elif key == QtCore.Qt.Key_Shift: self.shift_press_row = self.cursor_row self.shift_press_pos = self.cursor_pos elif seq.type == constants.SEQ_CUSTOM: modified = False seq, start, end, height = self.cursor_row if event.text(): code = event.text()[0] if code: offset = 0 if not self.viewer.wrapped: offset = self.viewer.left_column seq.residues[self.cursor_pos + offset].code = code modified = True if modified: # Sequence modified, update cursor position. if not self.viewer.wrapped: self.cursor_pos += 1 if self.cursor_pos >= self.viewer.max_columns: self.cursor_pos -= 1 self.viewer.left_column += 1 else: self.cursor_pos += 1 if self.cursor_pos >= end: self.cursor_pos = end - 1 self.viewer.generateRows() self.viewer.updateView() elif (key >= QtCore.Qt.Key_A and key <= QtCore.Qt.Key_Z) or \ key == QtCore.Qt.Key_Minus or \ (key >= QtCore.Qt.Key_0 and key <= QtCore.Qt.Key_9): # Do not type ouside of the sequence boundary if self.cursor_pos >= end: return modified = False if seq.type == constants.SEQ_AMINO_ACIDS: if (not seq.from_maestro) or self.viewer.mutate: new_code = chr(key).upper() if new_code in list(constants.AMINO_ACIDS): seq, start, end, height = self.cursor_row self.viewer.undo_stack.storeStateSequence( self.viewer.sequence_group, seq_index, label="Mutate Residue") res = seq.residues[self.cursor_pos + self.viewer.left_column] maestro_helpers.maestroMutateResidue( self.viewer, res, new_code) modified = True self.viewer.updateView() elif seq.type == constants.SEQ_ANNOTATION and \ seq.annotation_type == constants.ANNOTATION_SSP: if key == QtCore.Qt.Key_H or key == QtCore.Qt.Key_E or\ key == QtCore.Qt.Key_Minus: self.viewer.undo_stack.storeStateSequence( self.viewer.sequence_group, seq_index, label="Edit Secondary Structure") res = seq.residues[self.cursor_pos + self.viewer.left_column] if key == QtCore.Qt.Key_H: res.code = 'H' res.color = (240, 96, 64) elif key == QtCore.Qt.Key_E: res.code = 'E' res.color = (128, 240, 240) else: res.code = '-' res.color = (32, 32, 32) res.inverted = False modified = True if modified: # Sequence modified, update cursor position. if not self.viewer.wrapped: self.cursor_pos += 1 if self.cursor_pos >= self.viewer.max_columns: self.cursor_pos -= 1 self.viewer.left_column += 1 else: self.cursor_pos += 1 if self.cursor_pos >= end: self.cursor_pos = end - 1 self.viewer.generateRows() self.viewer.updateView() if self.selection_changed: self.viewer.selection_changed = True self.viewer.updateView(generate_rows=False, update_colors=False) # Remember previous key. self.last_key = key
[docs] def showToolTip(self, mouse_pos, seq_pos, res, sequence): """ Displays mouse move tooltip depending on the viewer mode and underlying object. :type mouse_pos: QPoint :param mouse_pos: Global position of the mouse cursor. :type seq_pos: int :param seq_pos: Sequence position where the mouse cursor is. :type res: `Residue` :param res: Residue beneath the mouse cursor. :type sequence: `Sequence` :param sequence: Sequence beneath the mouse cursor. """ # If the tooltips are disabled, just return. if not self.viewer.has_tooltips: return tip = "" QtWidgets.QToolTip.hideText() if res and res.sequence and seq_pos >= 0: sequence = res.sequence if sequence.parent_sequence: tip += sequence.parent_sequence.short_name if sequence.parent_sequence.chain_id != " ": tip += "_" + sequence.parent_sequence.chain_id else: tip += sequence.short_name if sequence.chain_id != " ": tip += "_" + sequence.chain_id tip += "\n" if res.is_gap: if res.code == constants.LOCKED_GAP_SYMBOL: tip += str(seq_pos + 1) + ": " + "Locked gap" else: tip += str(seq_pos + 1) + ": " + "Gap" else: if res.sequence.type == constants.SEQ_AMINO_ACIDS: aa_name = "" if res.name: aa_name = res.name tip += str(seq_pos + 1) + ": " + aa_name + str(res.num) + \ res.icode elif res.sequence.type == constants.SEQ_CONSENSUS: tip += str(seq_pos + 1) + ": " tip += "%1.3f %%" % (100.0 * res.value) tip += "\n" for aa in res.profile_codes: if aa in constants.AMINO_ACIDS: tip += constants.AMINO_ACIDS[aa][0] tip += " " elif res.sequence.parent_sequence and \ seq_pos < res.sequence.parent_sequence.length(): tip += str(seq_pos + 1) + " : " parent_res = res.sequence.parent_sequence.residues[seq_pos] if parent_res.sequence.type == constants.SEQ_AMINO_ACIDS: if parent_res.code in constants.AMINO_ACIDS: aa_name = constants.AMINO_ACIDS[parent_res.code][0] else: aa_name = "" tip += aa_name + str(parent_res.num) + \ parent_res.icode + " : " elif parent_res.sequence.type == constants.SEQ_CONSENSUS: tip += str(seq_pos + 1) + ": " tip += "%1.3f %%" % (100.0 * res.value) tip += "\n" for aa in parent_res.profile_codes: if aa in constants.AMINO_ACIDS: tip += constants.AMINO_ACIDS[aa][0] tip += " " if res.sequence.type == constants.SEQ_ANNOTATION: if res.sequence.annotation_type == \ constants.ANNOTATION_HELIX_PROPENSITY: tip += "\nred - helix-forming\n" + \ "magenta - weak helix-forming\n" + \ "gray - ambivalent\n" + "blue - helix-breaking" elif res.sequence.annotation_type == \ constants.ANNOTATION_STRAND_PROPENSITY: tip += "\nblue - strand-forming\n" + \ "gray - ambivalent\n" + "red - strand-breaking" elif res.sequence.annotation_type == \ constants.ANNOTATION_TURN_PROPENSITY: tip += "\ncyan - turn-forming\n" + \ "magenta - turn-breaking\n" + \ "gray - ambivalent" elif res.sequence.annotation_type == \ constants.ANNOTATION_HELIX_TERMINATORS: tip += "\ngreen - helix-starting\n" + \ "red - helix-ending\n" + "gray - ambivalent" elif res.sequence.annotation_type == \ constants.ANNOTATION_EXPOSURE_TENDENCY: tip += "\nblue - surface\n" + \ "gray - ambiguous\n" + "orange - buried" elif res.sequence.annotation_type == \ constants.ANNOTATION_STERIC_GROUP: tip += "\nred - small, non-interfering\n" + \ "magenta - ambiguous\n" + \ "sticky polar\n" + "blue - aromatic" elif res.sequence.annotation_type == \ constants.ANNOTATION_SIDECHAIN_CHEMISTRY: tip += "\nred - acidic, hydrophilic\n" + \ "blue - basic, hydrophilic\n" + \ "green - neutral, hydrophobic, aliphatic\n" + \ "orange - neutral, hydrophobic, aromatic\n" + \ "cyan - neutral, hydrophilic\n" + \ "yellow - primary thiol\n" + \ "dark gray - imino acid" elif res.sequence.annotation_type == \ constants.ANNOTATION_HYDROPHOBICITY or \ res.sequence.annotation_type == \ constants.ANNOTATION_MEAN_HYDRO: tip += "\nHydrophobicity: %1.3f\n" % (res.value) tip += "Hydrophobicity in 5-residue window: %1.3f" % \ (res.avg_value) elif res.sequence.annotation_type == constants.ANNOTATION_PI or \ res.sequence.annotation_type == constants.ANNOTATION_MEAN_PI: tip += "\nIsoelectric Point: %1.3f\n" % (res.value) tip += "Isoelectric Point in 5-residue window: %1.3f" % ( res.avg_value) elif res.sequence.annotation_type == constants.ANNOTATION_BFACTOR: if (res.value): tip += "\nB-factor: %1.3f" % (res.value) elif res.sequence.type == constants.SEQ_SECONDARY or \ res.sequence.annotation_type == constants.ANNOTATION_STRUCTURE: residues = res.sequence.residues parent_residues = parent_res.sequence.residues while seq_pos >= 0 and \ (residues[seq_pos].code == res.code or residues[seq_pos].is_gap): seq_pos -= 1 if seq_pos < res.sequence.length() - 1: seq_pos += 1 init_pos_str = "" if parent_residues[seq_pos].code in list( constants.AMINO_ACIDS): init_pos_str = constants.AMINO_ACIDS[ parent_residues[seq_pos].code][0] + \ str(parent_residues[seq_pos].num) + \ parent_residues[seq_pos].icode while seq_pos < res.sequence.length() and \ (residues[seq_pos].code == res.code or residues[seq_pos].is_gap): seq_pos += 1 if seq_pos > 0: seq_pos -= 1 end_pos_str = "" if parent_residues[seq_pos].code in list( constants.AMINO_ACIDS): end_pos_str = constants.AMINO_ACIDS[ parent_residues[seq_pos].code][0] + \ str(parent_residues[seq_pos].num) + \ parent_residues[seq_pos].icode if res.code == 'H': tip += "\nHelix " elif res.code == 'E': tip += "\nStrand " else: tip += "\nLoop " tip += init_pos_str + " - " + end_pos_str else: tip += str(seq_pos + 1) QtWidgets.QToolTip.showText(mouse_pos, tip) elif sequence: if sequence.type == constants.SEQ_RULER and seq_pos is not None: if seq_pos >= 0: tip += "Alignment Index: " + str(seq_pos + 1) else: tip = "" else: if sequence.parent_sequence: tip += sequence.parent_sequence.short_name + "\n" tip += sequence.short_name QtWidgets.QToolTip.showText(mouse_pos, tip) else: QtWidgets.QToolTip.hideText()
[docs] def wheelEvent(self, event): """ Handles Qt mouse wheel event. :type event: QWheelEvent :param event: Qt mouse wheel event. """ if self.viewer.wrapped: self.vertical_scroll_bar.wheelEvent(event) else: self.horizontal_scroll_bar.wheelEvent(event) event.accept()
[docs] def autoScroll(self): if not self.auto_scroll: return if not self.last_mouse_move_event: return self.mouseMoveEvent(self.last_mouse_move_event)
[docs] def mouseMoveEvent(self, event): """ Handles mouse move event for the sequence viewer widget. :type event: QMouseEvent :param event: Qt mouse move event. """ row, pos, res = self.mouseToRowPosition(event.x(), event.y()) if (row is None) or (pos < 0) or (pos is None): QtWidgets.QToolTip.hideText() if pos is None: return self.auto_scroll = False update_tooltip = False if row != self.last_tooltip_row or pos != self.last_tooltip_pos: update_tooltip = True self.last_tooltip_row = row self.last_tooltip_pos = pos if pos != self.init_pos or row != self.init_row: self.position_moved = True if self.viewer.feedback_label: self.viewer.feedback_label.setText("") if res and res.sequence: if self.viewer.feedback_label: text = "" if res.is_gap: text = " Mouse over gap" elif res.sequence.isValidProtein(): text = " Mouse over residue " text += str(res.num) + res.icode text += "(" + res.code + "/" + res.name + "/" if res.code in list(constants.AMINO_ACIDS): text += constants.AMINO_ACIDS[res.code][1] if constants.AMINO_ACIDS[res.code][0] != res.name: text += " variant" text += ")" text += " in sequence " + res.sequence.short_name if res.sequence.name != res.sequence.short_name: text += " (" + \ res.sequence.name.replace("\n", " ") + ")" text += ", chain '" + res.sequence.chain_id + "'." self.viewer.feedback_label.setText(text) if self.viewer.build_mode and \ self.viewer.sequence_group.build_mode == \ constants.PRIME_MODE_COMPOSITE: self.setCursor(QtCore.Qt.IBeamCursor) elif self.viewer.mode == constants.MODE_EDIT and \ (res.sequence.type == constants.SEQ_AMINO_ACIDS or res.sequence.type == constants.SEQ_CUSTOM or (res.sequence.type == constants.SEQ_ANNOTATION and res.sequence.annotation_type == constants.ANNOTATION_SSP)): if self.is_dragging_rect: self.setCursor(QtCore.Qt.ClosedHandCursor) else: self.setCursor(QtCore.Qt.IBeamCursor) else: self.setCursor(QtCore.Qt.PointingHandCursor) else: self.setCursor(QtCore.Qt.ArrowCursor) if event.buttons() == QtCore.Qt.NoButton or \ self.viewer.mode == constants.MODE_SELECT_AND_SLIDE or \ (self.viewer.mode == constants.MODE_EDIT and self.is_dragging_rect): if not self.is_dragging_rect: if row: seq, start, end, height = row if seq and update_tooltip: self.showToolTip(event.globalPos(), pos, res, seq) else: self.showToolTip(None, None, None, None) else: min_row, max_row, min_pos, max_pos, init_pos = self.drag_coords d_pos = pos - self.last_pos if d_pos != 0 and \ min_pos + d_pos >= 0: seq0, start, end, height = min_row seq1, start, end, height = max_row min_pos += d_pos max_pos += d_pos self.drag_coords = (min_row, max_row, min_pos, max_pos, init_pos) self.update() if self.viewer.has_tooltips: tip = "Sliding block:\n[%s: %d, %s: %d] to " \ "\n[%s: %d, %s: %d]" % \ (seq0.short_name, init_pos + 1, seq1.short_name, init_pos + (max_pos - min_pos) + 1, seq0.short_name, min_pos + 1, seq1.short_name, max_pos + 1) if update_tooltip: QtWidgets.QToolTip.showText(event.globalPos(), tip) self.last_pos = pos if event.buttons() & QtCore.Qt.LeftButton: if self.selecting_columns: if not event.modifiers() & QtCore.Qt.ControlModifier: self.viewer.sequence_group.unselectAll() self.viewer.sequence_group.selectColumns(self.init_pos, pos) result = self.updateColumns(self.init_pos, pos) self.update() if result and not self.viewer.wrapped: self.last_pos = pos self.auto_scroll = True new_pos = event.pos() self.last_mouse_move_event = QtGui.QMouseEvent( event.type(), new_pos, event.button(), event.buttons(), event.modifiers()) QtCore.QTimer.singleShot(100, self.autoScroll) return if self.init_res: if self.viewer.mode == constants.MODE_SELECT_AND_SLIDE or \ self.viewer.mode == constants.MODE_SELECT_ONLY or \ (self.viewer.build_mode and self.viewer.sequence_group.build_mode == constants.PRIME_MODE_COMPOSITE) or \ (self.viewer.mode == constants.MODE_GRAB_AND_DRAG and event.modifiers() & QtCore.Qt.ControlModifier): if self.is_dragging_rect: self.setCursor(QtCore.Qt.ClosedHandCursor) else: if row != self.init_row or pos != self.init_pos: self.setCursor(QtCore.Qt.CrossCursor) select = True if not event.modifiers( ) & QtCore.Qt.ControlModifier: self.viewer.sequence_group.unselectAll() else: select = not self.unselect if self.viewer.build_mode and \ self.viewer.sequence_group.build_mode == constants.PRIME_MODE_COMPOSITE: row = self.init_row self.viewer.sequence_group.selectResidues( self.init_row, self.init_pos, row, pos, select) start = self.init_pos end = pos if start > end: tmp = end end = start start = tmp max_columns = self.viewer.maxColumns() if end > self.viewer.left_column + max_columns: self.viewer.sequence_area.horizontal_scroll_bar.setValue( self.viewer.sequence_area. horizontal_scroll_bar.value() + 1) self.auto_scroll = True elif start < self.viewer.left_column: self.viewer.sequence_area.horizontal_scroll_bar.setValue( self.viewer.sequence_area. horizontal_scroll_bar.value() - 1) self.auto_scroll = True if self.auto_scroll: self.last_pos = pos new_pos = event.pos() self.last_mouse_move_event = QtGui.QMouseEvent( event.type(), new_pos, event.button(), event.buttons(), event.modifiers()) QtCore.QTimer.singleShot(100, self.autoScroll) self.last_pos = pos self.update() return diff = pos - self.last_pos if diff > 0: diff = 1 elif diff < 0: diff = -1 self.last_pos = pos self.update() if diff and not self.viewer.wrapped: self.auto_scroll = True new_pos = event.pos() new_pos.setX(new_pos.x() + diff) self.last_mouse_move_event = QtGui.QMouseEvent( event.type(), new_pos, event.button(), event.buttons(), event.modifiers()) QtCore.QTimer.singleShot(100, self.autoScroll) elif self.viewer.mode == constants.MODE_GRAB_AND_DRAG: # Shift key is used to invert current # grab-and-drag behavior invert = bool(event.modifiers() & QtCore.Qt.ShiftModifier) alt = bool(event.modifiers() & QtCore.Qt.AltModifier) # Grab-and-drag the alignment. self.viewer.sequence_group.grabAndDrag( self.init_row, self.last_pos, row, pos, int(self.viewer.lock_downstream) ^ int(invert), slide_sequence=alt) self.last_pos = pos self.setCursor(QtCore.Qt.ArrowCursor) self.alignment_changed = True self.viewer.updateView() elif self.viewer.mode == constants.MODE_EDIT and \ not self.is_dragging_rect: if self.init_row != row or \ self.init_pos != pos: self.setCursor(QtCore.Qt.CrossCursor) select = True if event.modifiers() & QtCore.Qt.ControlModifier: select = not self.unselect else: self.viewer.sequence_group.unselectAll() self.viewer.sequence_group.selectResidues( self.init_row, self.init_pos, row, pos, select) self.update()
[docs] def setViewer(self, viewer): """ Sets a parent sequence viewer widget for this sequence area. """ self.viewer = viewer
[docs] def resizeEvent(self, event): """ Qt resize event handler. When the widget is resized, the sequence viewer has to re-generate rows and update itself. :type event: QResizeEvent :param event: Qt resize event """ if self.viewer.wrapped: self.horizontal_scroll_bar.hide() else: self.horizontal_scroll_bar.show() self.horizontal_scroll_bar.setGeometry(0, self.height() - 15, self.width() - 15, 15) self.vertical_scroll_bar.setGeometry(self.width() - 15, 0, 15, self.height()) self.viewer.max_columns = self.viewer.maxColumns() self.viewer.generateRows() self.viewer.update() if self.viewer.feedback_label: max_width = self.viewer.width() - 150 if max_width < 1: max_width = 1 self.viewer.feedback_label.setMaximumWidth(max_width)
[docs] def paintAuxRectangle(self, painter): """ Paints the auxiliary rectangle on a specified painter. The aux rectangle is used to mark a sequence area in "select and slide" sequence viewer mode. :type painter: QPainter :param painter: target Qt painter """ min_row, max_row, min_pos, max_pos, init_pos = self.drag_coords if not min_row or not max_row: return x0, y0 = self.rowPosToCoords(min_row, min_pos) x1, y1 = self.rowPosToCoords(max_row, max_pos) if not (x0 and y0 and x1 and y1): return pen = QtGui.QPen(QtCore.Qt.white) pen.setWidth(5) painter.setPen(pen) painter.setBrush(QtCore.Qt.NoBrush) w = x1 - x0 + self.viewer.cell_width + 2 h = y1 - y0 + self.viewer.font_height + 2 x0 -= 1 y0 -= 1 painter.drawRect(x0, y0, w, h) pen = QtGui.QPen(QtGui.QColor(64, 64, 192)) pen.setWidth(2) painter.setPen(pen) painter.drawRect(x0, y0, w + 1, h + 1)
[docs] def paintUserAnnotations(self, painter): """ Renders user-defined annotations. """ if not self.viewer.sequence_group.user_annotations: return x_position = self.viewer.margin if self.viewer.display_boundaries: x_position += 6 * self.viewer.font_width painter.setClipRect(x_position, 0, self.viewer.maxColumns() * self.viewer.cell_width, self.height()) painter.setClipping(True) for ann in self.viewer.sequence_group.user_annotations: name, start, end, text, color = ann if name == "region": first_row = last_row = None region_start = 0 if not self.viewer.wrapped: region_start = self.viewer.left_column region_end = region_start + self.viewer.maxColumns() for row in self.viewer.rows: seq, d, d, row_height = row if seq.isValidProtein(): if not first_row: first_row = row if (seq.type == constants.SEQ_SEPARATOR or seq.type == constants.SEQ_CUSTOM) or \ row == self.viewer.rows[-1]: if seq.type != constants.SEQ_SEPARATOR: seq, d, d, row_height = row if seq.type not in (constants.SEQ_SEPARATOR, constants.SEQ_EMPTY, constants.SEQ_CUSTOM): last_row = row if first_row and last_row and \ not ((region_start < start and region_end < start) or (region_start > end and region_end > end)): # Draw here x0, y0 = self.rowPosToCoords( first_row, start, False) # - region_start) x1, y1 = self.rowPosToCoords( last_row, end, False) # - region_start) if x0 is not None and y0 is not None and \ x1 is not None and y1 is not None: r, g, b = color seq, d, d, row_height = last_row pen = QtGui.QPen(QtGui.QColor(r, g, b)) pen.setWidth(3) painter.setPen(pen) painter.setBrush(QtCore.Qt.NoBrush) w = x1 - x0 + self.viewer.cell_width + 2 h = y1 - y0 + row_height * \ self.viewer.font_height + 2 x0 -= 1 y0 -= 1 painter.drawRect(x0, y0, w, h) first_row = last_row = None region_start += self.viewer.maxColumns() region_end += self.viewer.maxColumns() last_row = row elif name == "rectangle": first_row = last_row = None region_start = 0 if not self.viewer.wrapped: region_start = self.viewer.left_column region_end = region_start + self.viewer.maxColumns() first, start = start last, end = end final_row = self.viewer.rows[-1] for row in self.viewer.rows: seq, d, d, row_height = row draw = False if seq.type == constants.SEQ_SEPARATOR or row == final_row: draw = True if seq == first: first_row = row if seq == last: last_row = row if draw and first_row and last_row: s1, d, d, d = first_row s2, d, d, d = last_row # Draw here x0, y0 = self.rowPosToCoords(first_row, start, False) # - region_start) x1, y1 = self.rowPosToCoords(last_row, end, False) # - region_start) if x0 is not None and y0 is not None and \ x1 is not None and y1 is not None: r, g, b = color seq, d, d, row_height = last_row pen = QtGui.QPen(QtGui.QColor(r, g, b)) pen.setWidth(3) painter.setPen(pen) painter.setBrush(QtCore.Qt.NoBrush) w = x1 - x0 + self.viewer.cell_width + 2 h = y1 - y0 + row_height * \ self.viewer.font_height + 2 x0 -= 1 y0 -= 1 painter.drawRect(x0, y0, w, h) first_row = last_row = None region_start += self.viewer.maxColumns() region_end += self.viewer.maxColumns() painter.setClipping(False)
[docs] def paintCursor(self, painter): """ Draw edit mode cursor at present cursor position. :param painter: painter used to draw the cursor :type painter: QPainter """ if self.cursor_enabled: if self.cursor_row is not None and \ self.cursor_pos is not None: cursor_x, cursor_y = \ self.rowPosToCoords(self.cursor_row, self.viewer.left_column + self.cursor_pos) if cursor_x and cursor_y: pen = QtGui.QPen(QtCore.Qt.white) pen.setWidth(3) painter.setPen(pen) painter.setBrush(QtCore.Qt.NoBrush) painter.drawRect(cursor_x, cursor_y, self.viewer.cell_width + 1, self.viewer.font_height + 1) pen = QtGui.QPen(QtCore.Qt.red) pen.setWidth(1) painter.setPen(pen) painter.drawRect(cursor_x, cursor_y, self.viewer.cell_width + 1, self.viewer.font_height + 1)
[docs] def paintEvent(self, event): """ Qt paint event handler. Calls self.paintSequenceArea to paint the widget contents. :type event: QPaintEvent :param event: Qt paint event """ painter = QtGui.QPainter(self) if constants.MSV_DEBUG: self.paintSequenceArea(painter, event) else: try: self.paintSequenceArea(painter, event) except: # This should never happen, however, just to be safe # we ignore the painting exception. pass
[docs] def paintRowBackground(self, painter, row, x_pos, y_pos, draw_text, selection_only=False, use_parent=False): """ Paints a row background. """ seq, start, end, row_height = row if end - start <= 0: return if use_parent and seq.parent_sequence: seq = seq.parent_sequence residues = seq.residues use_colors = self.viewer.use_colors length = seq.length() seq_position = start font_height = self.viewer.font_height cell_width = self.viewer.cell_width # The row image is scaled and used to display row background colors row_image = QtGui.QImage(end - start, 1, QtGui.QImage.Format_RGB32) if self.viewer.build_mode and \ self.viewer.sequence_group.build_mode == constants.PRIME_MODE_COMPOSITE: structures = self.viewer.sequence_group.getStructureList( omit_reference=True) br = self.viewer.background_color.red() bg = self.viewer.background_color.green() bb = self.viewer.background_color.blue() while seq_position < end and seq_position < length: residue = residues[seq_position] # Initially, set to background color. r = br g = bg b = bb if residue.selected: r = constants.SELECTION_COLOR[0] g = constants.SELECTION_COLOR[1] b = constants.SELECTION_COLOR[2] elif self.viewer.build_mode and \ self.viewer.sequence_group.build_mode == \ constants.PRIME_MODE_COMPOSITE: # Display template coverage here. if residue.model: r, g, b = constants.TEMPLATE_COLORS[ structures.index(seq) % constants.N_TEMPLATE_COLORS] elif use_colors and not residue.is_gap and residue.active: if not selection_only and not residue.selected: r, g, b = residue.color if residue.marked_color: if residue.marked_color == 'bold': pass elif residue.marked_color == 'italic': pass else: r, g, b = residue.marked_color if residue.structureless and \ seq.has_structure: # Desaturate color of structureless residues r = old_div(r, 4) + 3 * br / 4 g = old_div(g, 4) + 3 * bg / 4 b = old_div(b, 4) + 3 * bb / 4 rgb_color = QtGui.qRgb(r, g, b) row_image.setPixel(seq_position - start, 0, rgb_color) seq_position += 1 img_height = font_height * row_height img_offset = 0 if seq.annotation_type == constants.ANNOTATION_CUSTOM: img_height = 2 img_offset = 1 scaled_image = row_image.scaled((end - start) * cell_width, img_height) painter.drawImage(x_pos, y_pos + img_offset, scaled_image)
[docs] def paintRowText(self, painter, row, row_position, x_pos, y_pos, x_offset): """ Paints the row text contents. """ seq, start, end, row_height = row if end - start <= 0: return residues = seq.residues use_colors = self.viewer.use_colors seq_position = start font_width = self.viewer.font_width cell_width = self.viewer.cell_width display_dots = self.viewer.display_dots auto_color = self.viewer.auto_color profile = self.viewer.sequence_group.profile reference = self.viewer.sequence_group.reference if not reference: reference = profile reference_length = reference.length() x_position = self.viewer.margin if self.viewer.display_boundaries: x_position += 6 * font_width y_offset = -2 seq_position = start last_color = None text = "" draw_3letter_codes = False if self.viewer.zoom_factor > 3 and \ seq.type == constants.SEQ_AMINO_ACIDS: draw_3letter_codes = True painter.setFont(self.viewer.original_font) x_pos = x_position - 2 #y_offset = 0 else: x_pos = x_position + x_offset + 1 y_pos = row_position + y_offset painter.setPen(self.viewer.inv_background_color) if draw_3letter_codes: while seq_position < end: residue = residues[seq_position] is_active = residue.active code = residue.code if residue.structureless and \ seq.has_structure: is_active = False if display_dots and \ seq.type == constants.SEQ_AMINO_ACIDS and \ seq_position < reference_length and \ not residue.is_gap and \ seq != reference and \ code == reference.residues[seq_position].code: code = ' . ' elif code in list(constants.AMINO_ACIDS): code = constants.AMINO_ACIDS[code][0] code = " " + code + " " if residue.is_gap: code = " " + code * 3 + " " if residue.is_gap or not use_colors: color = self.viewer.inv_background_color elif residue.selected: color = QtCore.Qt.white elif not is_active: color = QtCore.Qt.gray elif residue.inverted: color = QtCore.Qt.black if auto_color: if residue.marked_color and \ residue.marked_color not in ("bold", "italic"): r, g, b = residue.marked_color else: r, g, b = residue.color if QtGui.qGray(r, g, b) < 127: color = QtCore.Qt.white else: r, g, b = residue.color color = QtGui.QColor(r, g, b) if seq.type == constants.SEQ_CUSTOM or \ seq.annotation_type == constants.ANNOTATION_CUSTOM: color = self.viewer.inv_background_color painter.setPen(color) painter.drawText(x_pos, y_pos, code) x_pos += cell_width seq_position += 1 else: while seq_position < end: residue = residues[seq_position] is_active = residue.active code = residue.code if residue.structureless and \ seq.has_structure: is_active = False if draw_3letter_codes: if code in list(constants.AMINO_ACIDS): code = constants.AMINO_ACIDS[code][0] code = "." + code + "." if residue.is_gap: code = ".." + code + ".." if display_dots and \ seq.type == constants.SEQ_AMINO_ACIDS and \ seq_position < reference_length and \ not residue.is_gap and \ seq != reference and \ code == reference.residues[seq_position].code: code = '.' if residue.is_gap or not use_colors: color = self.viewer.inv_background_color elif residue.selected: color = QtCore.Qt.white elif not is_active: color = QtCore.Qt.gray elif residue.inverted: color = QtCore.Qt.black if auto_color: if residue.marked_color and \ residue.marked_color not in ("bold", "italic"): r, g, b = residue.marked_color else: r, g, b = residue.color if QtGui.qGray(r, g, b) < 127: color = QtCore.Qt.white else: r, g, b = residue.color color = QtGui.QColor(r, g, b) if seq.type == constants.SEQ_CUSTOM or \ seq.annotation_type == constants.ANNOTATION_CUSTOM: color = self.viewer.inv_background_color if color != last_color: if text: painter.drawText(x_pos, y_pos, text) x_pos += cell_width * len(text) text = "" painter.setPen(color) last_color = color text += code seq_position += 1 if text: painter.drawText(x_pos, y_pos, text) if draw_3letter_codes: painter.setFont(self.viewer.font())
[docs] def paintRowSSA(self, painter, row, row_position): seq, start, end, row_height = row if end - start <= 0: return residues = seq.residues seq_position = start font_height = self.viewer.font_height font_width = self.viewer.font_width cell_width = self.viewer.cell_width profile = self.viewer.sequence_group.profile reference = self.viewer.sequence_group.reference if not reference: reference = profile x_position = self.viewer.margin if self.viewer.display_boundaries: x_position += 6 * font_width seq_position = start color_c = self.viewer.inv_background_color y_position = row_position - font_height painter.setRenderHint(QtGui.QPainter.Antialiasing, False) mid_pos = int(0.5 * font_height) half_height = int(0.25 * font_height) if half_height % 2 == 0: half_height += 1 while seq_position < end: if seq.parent_sequence and \ len(seq.parent_sequence.residues) > seq_position: parent_residue = seq.parent_sequence.residues[seq_position] else: parent_residue = None residue = residues[seq_position] if not residue.is_gap: if residue.code == 'H': next_res = seq.nextUngappedResidue(seq_position) previous_res = seq.previousUngappedResidue(seq_position) if (previous_res and previous_res.code != 'H') \ or not previous_res: painter.drawImage(x_position, y_position, self.ssa_image_helix_start) elif next_res and next_res.code != 'H': painter.drawImage(x_position, y_position, self.ssa_image_helix_end) else: painter.drawImage(x_position, y_position, self.ssa_image_helix_mid) elif residue.code == 'E': next_res = seq.nextUngappedResidue(seq_position) previous_res = seq.previousUngappedResidue(seq_position) if (previous_res and previous_res.code != 'E') \ or not previous_res: painter.drawImage(x_position, y_position, self.ssa_image_sheet_start) elif next_res and next_res.code != 'E': painter.drawImage(x_position, y_position, self.ssa_image_sheet_end) else: painter.drawImage(x_position, y_position, self.ssa_image_sheet_mid) else: if parent_residue and \ parent_residue.structureless: # Light-gray for structureless residues painter.setPen(QtGui.QColor(192, 192, 192)) else: painter.setPen(color_c) painter.drawLine(x_position, y_position + mid_pos, x_position + cell_width - 1, y_position + mid_pos) x_position += cell_width seq_position += 1
[docs] def paintRowSSBond(self, painter, row, row_position): """ Paints disulfide bond row (both assignment and prediction). """ seq, start, end, row_height = row if end - start <= 0: return residues = seq.residues seq_position = start font_height = self.viewer.font_height font_width = self.viewer.font_width cell_width = self.viewer.cell_width profile = self.viewer.sequence_group.profile reference = self.viewer.sequence_group.reference if not reference: reference = profile x_position = self.viewer.margin if self.viewer.display_boundaries: x_position += 6 * font_width seq_position = start y_position = row_position - font_height painter.setRenderHint(QtGui.QPainter.Antialiasing, False) pen = QtGui.QPen(self.viewer.inv_background_color) pen.setStyle(QtCore.Qt.SolidLine) pen.setWidthF(1 + (font_width * 0.1)) painter.setPen(pen) painter.setBrush(QtCore.Qt.NoBrush) y0 = y_position + 0.5 * font_height painter.setClipRect(x_position, 0, self.viewer.maxColumns() * cell_width, self.height()) painter.setClipping(True) seq_position = start bonds = [] if seq.annotation_type == constants.ANNOTATION_SSBOND: bond_list = seq.parent_sequence.ssb_bond_list else: bond_list = seq.bond_list for bi, bond in enumerate(bond_list): first, second = bond first_res = seq.parent_sequence.getResidue(first, ungapped=True) second_res = seq.parent_sequence.getResidue(second, ungapped=True) if first_res and second_res and \ first_res in seq.parent_sequence.residues and \ second_res in seq.parent_sequence.residues: r1 = seq.parent_sequence.residues.index(first_res) r2 = seq.parent_sequence.residues.index(second_res) bonds.append((r1, r2)) while seq_position < end and seq_position < len(residues): residue = residues[seq_position] if residue and \ not residue.is_gap: for bi, bond in enumerate(bonds): if seq.annotation_type == constants.ANNOTATION_CCB: g = int(255.0 * float(bi) / len(bonds)) color = QtGui.QColor(g, g, g) pen.setColor(color) painter.setPen(pen) r1, r2 = bond y0 = y_position + 0.4 * font_height * (bi + 1) if seq_position > r1 and seq_position < r2: painter.drawLine(x_position, y0, x_position + cell_width - 1, y0) if r1 == seq_position: painter.drawLine(x_position + 0.5 * cell_width, y0, x_position + cell_width - 1, y0) painter.drawLine(x_position + 0.5 * cell_width, y_position, x_position + 0.5 * cell_width, y0) if r2 == seq_position: painter.drawLine(x_position, y0, x_position + 0.5 * cell_width, y0) painter.drawLine(x_position + 0.5 * cell_width, y_position, x_position + 0.5 * cell_width, y0) x_position += cell_width seq_position += 1 painter.setRenderHint(QtGui.QPainter.Antialiasing, False) painter.setClipping(False)
[docs] def paintRowConstraints(self, painter, row, row_position): """ Paints constraints row (both alignment and Prime). """ seq, start, end, row_height = row font_height = self.viewer.font_height font_width = self.viewer.font_width cell_width = self.viewer.cell_width profile = self.viewer.sequence_group.profile reference = self.viewer.sequence_group.reference if not reference: reference = profile x_position = self.viewer.margin if self.viewer.display_boundaries: x_position += 6 * font_width y_position = row_position - font_height painter.setRenderHint(QtGui.QPainter.Antialiasing, True) pen = painter.pen() if self.viewer.set_query_constraints: pencolor = QtGui.QColor(255, 127, 127) penlightcolor = QtGui.QColor(255, 255, 127) else: pencolor = QtGui.QColor(127, 127, 255) penlightcolor = QtGui.QColor(127, 255, 255) pen.setStyle(QtCore.Qt.SolidLine) pen.setWidthF(1.0) painter.setPen(pen) painter.setBrush(QtCore.Qt.NoBrush) painter.setClipRect(x_position, 0, self.viewer.maxColumns() * cell_width, self.height()) painter.setClipping(True) for constraint in seq.constraint_list: cstart, cend, target, prime = constraint if self.viewer.wrapped: if (cstart < start and cend < start) or \ (cstart > end and cend > end): continue if prime != self.viewer.set_query_constraints: continue if prime: cstart_res = reference.getResidue(cstart, ungapped=True) cend_res = reference.getResidue(cend, ungapped=True) else: cstart_res = reference.getResidue(cstart, ungapped=True) cend_res = target.getResidue(cend, ungapped=True) if cstart_res and cend_res: cstart = reference.residues.index(cstart_res) if prime: cend = reference.residues.index(cend_res) else: cend = target.residues.index(cend_res) if not self.viewer.wrapped: cstart -= self.viewer.left_column cend -= self.viewer.left_column else: cstart -= start cend -= start for it in [3, 1]: pen.setWidthF(float(it)) if it == 3: color = pencolor else: color = penlightcolor pen.setColor(color) painter.setPen(pen) y0 = y_position + 3 y1 = y_position + int(1.0 * row_height * font_height - 3) painter.drawLine(x_position + cstart * cell_width + 1, y0, x_position + (cstart + 1) * cell_width - 2, y0) if prime: painter.drawLine( x_position + cend * cell_width + 1, y0, x_position + (cend + 1) * cell_width - 2, y0) else: painter.drawLine( x_position + cend * cell_width + 1, y1, x_position + (cend + 1) * cell_width - 2, y1) if math.fabs(cstart - cend) < 2: painter.drawLine( x_position + (cstart + 0.5) * cell_width, y0, x_position + (cend + 0.5) * cell_width, y1) else: path = QtGui.QPainterPath() if prime: path.moveTo(x_position + (cend + 0.5) * cell_width, y0) path.arcTo(x_position + (cstart + 0.5) * cell_width, y0 + (y1 - y0) * 0.5, (cend - cstart) * cell_width, y0 - y1, 0, 180) else: path.moveTo( x_position + (cstart + 0.5) * cell_width, y0) path.cubicTo(x_position + cstart * cell_width, y1, x_position + cend * cell_width, y0, x_position + (cend + 0.5) * cell_width, y1) painter.drawPath(path) painter.setRenderHint(QtGui.QPainter.Antialiasing, False) painter.setClipping(False)
[docs] def paintRowAnnotationPlot(self, painter, row, row_position): """ Paints annotation plot. """ seq, start, end, row_height = row if end - start <= 0: return residues = seq.residues seq_position = start font_height = self.viewer.font_height font_width = self.viewer.font_width cell_width = self.viewer.cell_width profile = self.viewer.sequence_group.profile reference = self.viewer.sequence_group.reference if not reference: reference = profile x_position = self.viewer.margin if self.viewer.display_boundaries: x_position += 6 * font_width seq_position = start y_position = row_position - font_height + 1 min_y_position = y_position + 2 max_y_position = y_position - 2 + seq.height * font_height mid_y_position = 0.5 * (max_y_position - min_y_position) + y_position if seq.plot_style == constants.PLOT_LINE: painter.setPen(QtGui.QColor(QtCore.Qt.gray)) painter.pen().setStyle(QtCore.Qt.DashLine) painter.drawLine(x_position, mid_y_position, x_position + cell_width - 2 * self.viewer.margin, mid_y_position) painter.pen().setStyle(QtCore.Qt.SolidLine) range = seq.max_avg_value - seq.min_avg_value plot_height = (max_y_position - min_y_position) if range > 0.0: if seq.plot_style == constants.PLOT_HISTOGRAM: r, g, b = seq.plot_color color = QtGui.QColor(r, g, b) painter.setPen(color) painter.setBrush(color) if seq.max_avg_value > 0.0 and seq.min_avg_value < 0.0: max_val = max(math.fabs(seq.max_avg_value), math.fabs(seq.min_avg_value)) range = 2.0 * max_val if range == 0.0: inv_range = 1.0 else: inv_range = old_div(1.0, max_val) half_height = 0.5 * plot_height while seq_position < end: residue = residues[seq_position] if residue and not residue.is_gap: y_pos = int(half_height * (inv_range * (residue.avg_value))) if y_pos < 0: painter.drawRect(x_position + 1, mid_y_position, cell_width - 2, -y_pos) else: painter.drawRect(x_position + 1, mid_y_position - y_pos, cell_width - 2, y_pos) x_position += cell_width seq_position += 1 else: inv_range = old_div(1.0, range) while seq_position < end: residue = residues[seq_position] if residue and not residue.is_gap: y_pos = math.ceil( plot_height * (inv_range * (residue.avg_value - seq.min_avg_value))) + 1 painter.drawRect( x_position + 1, y_position + plot_height - y_pos + 2, cell_width - 2, y_pos) x_position += cell_width seq_position += 1 elif seq.plot_style == constants.PLOT_LINE: r, g, b = seq.plot_color painter.setPen(QtGui.QColor(QtGui.QColor(r, g, b))) painter.pen().setStyle(QtCore.Qt.SolidLine) if seq.max_avg_value > 0.0 and seq.min_avg_value < 0.0: max_val = max(math.fabs(seq.max_avg_value), math.fabs(seq.min_avg_value)) range = 2.0 * max_val if range == 0.0: inv_range = 1.0 else: inv_range = old_div(1.0, max_val) half_height = 0.5 * plot_height while seq_position < end: residue = residues[seq_position] if residue and not residue.is_gap: prev_y_pos = y_position + plot_height * \ (1.0 - inv_range * (residue.previous_avg_value - seq.min_avg_value)) + 1 y_pos = y_position + plot_height * \ (1.0 - inv_range * (residue.avg_value - seq.min_avg_value)) + 1 next_y_pos = y_position + plot_height * \ (1.0 - inv_range * (residue.next_avg_value - seq.min_avg_value)) + 1 prev_y_pos = y_pos + 0.5 * (prev_y_pos - y_pos) next_y_pos = y_pos + 0.5 * (next_y_pos - y_pos) painter.drawLine(x_position, prev_y_pos, x_position + 0.5 * cell_width, y_pos) painter.drawLine(x_position + 0.5 * cell_width, y_pos, x_position + cell_width, next_y_pos) x_position += cell_width seq_position += 1
[docs] def cacheSSAImages(self): """ Paints a secondary structure annotation row. """ font_height = self.viewer.font_height cell_width = self.viewer.cell_width mid_pos = int(0.5 * font_height) half_height = int(0.25 * font_height) hh = half_height color_h = QtGui.QColor(constants.SS_COLOR_HELIX[0], constants.SS_COLOR_HELIX[1], constants.SS_COLOR_HELIX[2]) color_e = QtGui.QColor(constants.SS_COLOR_EXTENDED[0], constants.SS_COLOR_EXTENDED[1], constants.SS_COLOR_EXTENDED[2]) color_c = self.viewer.inv_background_color # QtGui.QColor( # constants.SS_COLOR_COIL[0], # constants.SS_COLOR_COIL[1], # constants.SS_COLOR_COIL[2]) self.ssa_image_helix_start = QtGui.QImage(cell_width, font_height, QtGui.QImage.Format_ARGB32) self.ssa_image_helix_start.fill(QtGui.qRgba(0, 0, 0, 0)) painter = QtGui.QPainter(self.ssa_image_helix_start) painter.setPen(color_h) painter.setBrush(QtGui.QBrush(color_h)) painter.drawRect(half_height, mid_pos - half_height, cell_width - half_height, 2 * half_height) painter.setPen(color_h.darker()) painter.drawLine(half_height, mid_pos - half_height, cell_width, mid_pos - half_height) painter.drawLine(half_height, mid_pos + half_height, cell_width, mid_pos + half_height) painter.setPen(color_h.darker()) painter.setBrush(QtGui.QBrush(QtCore.Qt.white)) painter.setRenderHint(QtGui.QPainter.Antialiasing, True) painter.drawEllipse(0, mid_pos - half_height + 1.0, 2.0 * half_height - 1.0, 2.0 * half_height - 1.0) painter.setRenderHint(QtGui.QPainter.Antialiasing, False) painter.setPen(color_c) painter.drawLine(0, mid_pos, half_height, mid_pos) painter.end() self.ssa_image_helix_mid = QtGui.QImage(cell_width, font_height, QtGui.QImage.Format_ARGB32) self.ssa_image_helix_mid.fill(QtGui.qRgba(0, 0, 0, 0)) painter = QtGui.QPainter(self.ssa_image_helix_mid) painter.setPen(color_h) painter.setBrush(QtGui.QBrush(color_h)) painter.drawRect(0, mid_pos - half_height, cell_width, 2 * half_height) painter.setPen(color_h.darker()) painter.drawLine(0, mid_pos - half_height, cell_width, mid_pos - half_height) painter.drawLine(0, mid_pos + half_height, cell_width, mid_pos + half_height) painter.end() self.ssa_image_helix_end = QtGui.QImage(cell_width, font_height, QtGui.QImage.Format_ARGB32) self.ssa_image_helix_end.fill(QtGui.qRgba(0, 0, 0, 0)) painter = QtGui.QPainter(self.ssa_image_helix_end) painter.setPen(color_h.darker()) painter.setBrush(QtGui.QBrush(color_h)) painter.setRenderHint(QtGui.QPainter.Antialiasing, True) painter.drawEllipse(cell_width - 2 * half_height, mid_pos - half_height + 1.0, 2.0 * half_height - 1.0, 2.0 * half_height - 1.0) painter.setRenderHint(QtGui.QPainter.Antialiasing, False) painter.setPen(color_h) painter.drawRect(0, mid_pos - half_height + 1, cell_width - half_height, 2 * half_height - 2) painter.setPen(color_h.darker()) painter.drawLine(0, mid_pos - half_height, cell_width - half_height, mid_pos - half_height) painter.drawLine(0, mid_pos + half_height, cell_width - half_height, mid_pos + half_height) painter.end() self.ssa_image_sheet_start = QtGui.QImage(cell_width, font_height, QtGui.QImage.Format_ARGB32) self.ssa_image_sheet_start.fill(QtGui.qRgba(0, 0, 0, 0)) painter = QtGui.QPainter(self.ssa_image_sheet_start) painter.setPen(color_e) painter.setBrush(QtGui.QBrush(color_e)) painter.drawRect(0, mid_pos - hh, cell_width, 2 * hh) painter.setPen(color_e.darker()) painter.drawLine(0, mid_pos - hh, cell_width, mid_pos - hh) painter.drawLine(0, mid_pos + hh, cell_width, mid_pos + hh) painter.drawLine(0, mid_pos - hh, 0, mid_pos + hh) painter.end() self.ssa_image_sheet_mid = QtGui.QImage(cell_width, font_height, QtGui.QImage.Format_ARGB32) self.ssa_image_sheet_mid.fill(QtGui.qRgba(0, 0, 0, 0)) painter = QtGui.QPainter(self.ssa_image_sheet_mid) painter.setPen(color_e) painter.setBrush(QtGui.QBrush(color_e)) painter.drawRect(0, mid_pos - hh, cell_width, 2 * hh) painter.setPen(color_e.darker()) painter.drawLine(0, mid_pos - hh, cell_width - 1, mid_pos - hh) painter.drawLine(0, mid_pos + hh, cell_width - 1, mid_pos + hh) painter.end() self.ssa_image_sheet_end = QtGui.QImage(cell_width, font_height, QtGui.QImage.Format_ARGB32) self.ssa_image_sheet_end.fill(QtGui.qRgba(0, 0, 0, 0)) painter = QtGui.QPainter(self.ssa_image_sheet_end) painter.setPen(color_e.darker()) painter.setBrush(QtGui.QBrush(color_e)) painter.drawLine(0, mid_pos, 0, mid_pos - half_height - 2) painter.drawLine(0, mid_pos, 0, mid_pos + half_height + 2) painter.setRenderHint(QtGui.QPainter.Antialiasing, True) painter.drawLine(cell_width, mid_pos, 0, mid_pos - half_height - 2) painter.drawLine(cell_width, mid_pos, 0, mid_pos + half_height + 2) painter.drawPolygon(QtCore.QPoint(0, mid_pos - half_height - 2), QtCore.QPoint(cell_width, mid_pos), QtCore.QPoint(0, mid_pos + half_height + 2)) painter.setRenderHint(QtGui.QPainter.Antialiasing, False) painter.setPen(color_e) painter.drawLine(0, mid_pos - half_height + 1, 0, mid_pos + half_height - 1) painter.end()
[docs] def paintSequenceArea(self, painter, event, clip=True, custom_width=None, custom_height=None): """ Actually paints the sequence area widget. :note: This code should be re-factored. Each component of the sequence area should have its own painting method. :type painter: QPainter :param painter: target Qt painter :type event: QPaintEvent :param event: Qt paint event """ my_font = self.viewer.font() test_str = 'X' * 100 font_metrics = QtGui.QFontMetrics(my_font) ratio = 100.0 * float(self.viewer.cell_width) / \ float((font_metrics.width(test_str)) * 0.01) my_font.setLetterSpacing(QtGui.QFont.PercentageSpacing, ratio) painter.setFont(my_font) rows = self.viewer.rows font_width = self.viewer.font_width font_height = self.viewer.font_height font_xheight = self.viewer.font_xheight cell_width = self.viewer.cell_width width = self.width() height = self.height() if custom_width is not None: width = custom_width if custom_height is not None: height = custom_height if self.emphasized: painter.setPen( QtGui.QPen(QtGui.QColor(QtGui.qRgb(127, 127, 255)), 3)) else: painter.setPen( QtGui.QPen(QtGui.QBrush(self.viewer.background_color), 1)) painter.setBrush(QtGui.QBrush(self.viewer.background_color)) if clip: if self.viewer.wrapped: painter.drawRect(1, 1, width - 19, height - 4) painter.setClipRect(3, 3, width - 19, height - 6) else: painter.drawRect(1, 1, width - 19, height - 19) painter.setClipRect(3, 3, width - 19, height - 19) painter.setPen(self.viewer.inv_background_color) # This flag is True if current zoom level allows for text drawing. draw_text = True # Calculate a horizontal text offset. x_offset = int(old_div((cell_width - font_width), 2)) if x_offset < 0: x_offset = 0 draw_text = False y_offset = 0 self.viewer.updateStatusBar() sep_scale = old_div(float(self.viewer.separator_scale), 100.0) profile = self.viewer.sequence_group.profile if not profile: return reference = self.viewer.sequence_group.reference if not reference: reference = profile y_position = self.viewer.margin if self.viewer.has_header_row: header_height = 2.0 * self.viewer.margin + font_height painter.setPen(QtGui.QPen(QtGui.QBrush(QtCore.Qt.black), 1)) painter.setBrush(QtGui.QBrush(QtGui.QColor(224, 224, 224))) painter.drawRect(0, 0, width, header_height) x_position = self.viewer.margin if self.viewer.display_boundaries: x_position += 6 * font_width painter.drawLine(x_position, 0, x_position, header_height) painter.setFont(self.viewer.header_font) painter.drawText(x_position + 3, font_height + self.viewer.margin, "Sequence") if rows: seq, start, end, row_height = rows[0] x_position += (end - start) * font_width painter.drawLine(x_position, 0, x_position, header_height) x_position = width - self.viewer.margin - 6 * font_width if self.viewer.display_identity or \ self.viewer.display_similarity or \ self.viewer.display_homology or self.viewer.display_score: txt = "ID%" if self.viewer.display_similarity: txt = "SIM%" elif self.viewer.display_homology: txt = "HOM%" elif self.viewer.display_score: txt = "SCORE" painter.drawText(x_position + 3, font_height + self.viewer.margin, txt) painter.drawLine(x_position, 0, x_position, header_height) y_position += header_height painter.setFont(my_font) row_idx = 0 num_persistent_rows = 0 if not self.viewer.wrapped: for row in rows: num_persistent_rows += 1 # Draw a single row. seq, start, end, row_height = row if not (seq.type == constants.SEQ_RULER or \ seq.global_sequence or seq == reference): break top_row = self.viewer.top_row + num_persistent_rows for row in rows: row_idx += 1 # Draw a single row. seq, start, end, row_height = row if self.viewer.wrapped or \ not (seq.type == constants.SEQ_RULER or seq.global_sequence or seq == reference): if row_idx < top_row and clip: continue if y_position > height and clip: break # Draw the sequence only if it is visible. if seq and seq.visible: try: row_position = y_position + font_height x_position = self.viewer.margin if self.viewer.display_boundaries: x_position += 6 * font_width seq_position = start residues = seq.residues if residues and len(residues) > 0: if seq.type == constants.SEQ_CUSTOM: self.paintRowText(painter, row, row_position, x_position, y_position, x_offset) elif seq.type == constants.SEQ_SECONDARY: self.paintRowBackground(painter, row, x_position, y_position, draw_text, selection_only=True, use_parent=True) self.paintRowSSA(painter, row, row_position) elif seq.type == constants.SEQ_ANNOTATION and \ seq.annotation_type != constants.ANNOTATION_SSBOND and \ seq.annotation_type != constants.ANNOTATION_LIGAND and \ seq.annotation_type != constants.ANNOTATION_SSP and \ seq.annotation_type != constants.ANNOTATION_ACC and \ seq.annotation_type != constants.ANNOTATION_DIS and \ seq.annotation_type != constants.ANNOTATION_DOM and \ seq.annotation_type != constants.ANNOTATION_CCB and \ seq.annotation_type != constants.ANNOTATION_RESNUM and \ seq.annotation_type != constants.ANNOTATION_CUSTOM and \ seq.annotation_type != constants.ANNOTATION_PFAM and \ seq.plot_style != constants.PLOT_COLOR_BLOCKS: self.paintRowBackground(painter, row, x_position, y_position, draw_text, selection_only=True, use_parent=True) self.paintRowAnnotationPlot(painter, row, row_position) elif seq.annotation_type != constants.ANNOTATION_SSBOND and \ seq.annotation_type != constants.ANNOTATION_CCB and \ seq.annotation_type != constants.ANNOTATION_RESNUM: # Draw row's background. self.paintRowBackground(painter, row, x_position, y_position, draw_text) if draw_text: self.paintRowText(painter, row, row_position, x_position, y_position, x_offset) if seq.type == constants.SEQ_ANNOTATION and \ (seq.annotation_type == constants.ANNOTATION_CCB or seq.annotation_type == constants.ANNOTATION_SSBOND): self.paintRowBackground(painter, row, x_position, y_position, draw_text, selection_only=True, use_parent=True) self.paintRowSSBond(painter, row, row_position) elif seq.type == constants.SEQ_CONSTRAINTS: self.paintRowConstraints(painter, row, row_position) elif seq.type == constants.SEQ_SEPARATOR and sep_scale > 0.0: painter.setBrush(QtGui.QColor(QtCore.Qt.gray)) pen = QtGui.QPen(QtCore.Qt.gray) pen.setStyle(QtCore.Qt.DotLine) painter.setPen(pen) ypos = y_position + int( sep_scale * (font_height - font_xheight)) - 2 painter.drawLine(0, ypos, width, ypos) painter.pen().setStyle(QtCore.Qt.SolidLine) elif seq.type == constants.SEQ_LOGO: self.paintRowLogo(painter, row, row_position) elif seq.annotation_type == constants.ANNOTATION_RESNUM: self.paintRowBackground(painter, row, x_position, y_position, draw_text, selection_only=True, use_parent=True) painter.setPen(self.viewer.inv_background_color) # Use a slightly smaller font size. self.viewer.ruler_font.setLetterSpacing( QtGui.QFont.AbsoluteSpacing, 0) painter.setFont(self.viewer.ruler_font) while seq_position < end and \ seq_position < seq.parent_sequence.length(): position = seq_position + 1 pres = seq.parent_sequence.residues[seq_position] resn = pres.num if (not pres.is_gap) and (resn % 5 == 0): res_s = str(resn) + pres.icode res_s = res_s.rstrip() if len(res_s) + seq_position < end: x_pos = x_position + x_offset if not draw_text: x_pos -= self.viewer.margin painter.drawText(x_pos, row_position - 2, res_s) l = len(res_s) seq_position = seq_position + l x_position = x_position + l * cell_width seq_position = seq_position + 1 x_position = x_position + cell_width # Restore a previous font size. painter.setFont(my_font) # self.viewer.font()) elif seq.type == constants.SEQ_RULER: # Draw a ruler row. painter.setPen(self.viewer.inv_background_color) # Use a slightly smaller font size. self.viewer.ruler_font.setLetterSpacing( QtGui.QFont.AbsoluteSpacing, 0) painter.setFont(self.viewer.ruler_font) while seq_position < end: x_pos = x_position + x_offset if not draw_text: x_pos -= self.viewer.margin position = seq_position + 1 if position == 1: painter.drawText(x_pos, row_position, '1') painter.drawLine(x_pos + 0.5 * font_width, row_position + 3, x_pos + 0.5 * font_width, row_position + font_height) elif position % 5: if self.viewer.zoom_factor >= 1.0: painter.drawText(x_pos, row_position + font_height, '.') elif position % 10: if self.viewer.zoom_factor >= 0.5: painter.drawLine( x_pos + 0.5 * font_width, row_position + 0.5 * font_height, x_pos + 0.5 * font_width, row_position + font_height) else: painter.drawText(x_pos, row_position + font_height, '.') else: if self.viewer.zoom_factor >= 0.5 or \ (self.viewer.zoom_factor >= 0.33 and not (position % 20)) or \ (self.viewer.zoom_factor < 0.33 and not (position % 50)): painter.drawText(x_pos, row_position, str(position)) painter.drawLine(x_pos + 0.5 * font_width, row_position + 3, x_pos + 0.5 * font_width, row_position + font_height) seq_position = seq_position + 1 x_position = x_position + cell_width # Restore a previous font size. painter.setFont(my_font) # self.viewer.font()) # Display right column. if (seq.type == constants.SEQ_AMINO_ACIDS or seq.type == constants.SEQ_CONSENSUS or (seq.parent_sequence and self.viewer.group_annotations)): offset = 0 y_offset = -1 if seq.type != constants.SEQ_AMINO_ACIDS and \ seq.type != constants.SEQ_CONSENSUS: seq = seq.parent_sequence self.viewer.original_font.setLetterSpacing( QtGui.QFont.AbsoluteSpacing, 0) painter.setFont(self.viewer.original_font) if (seq.type == constants.SEQ_AMINO_ACIDS or seq.type == constants.SEQ_CONSENSUS) and \ not seq.parent_sequence and \ (self.viewer.display_identity or self.viewer.display_similarity or self.viewer.display_homology or self.viewer.display_score): id_text = " 0%" if self.viewer.display_identity: if seq.identity: id_text = "%3.0f%%" % (100.0 * seq.identity) elif self.viewer.display_similarity: if seq.similarity: id_text = "%3.0f%%" % (100.0 * seq.similarity) elif self.viewer.display_homology: if seq.homology: id_text = "%3.0f%%" % (100.0 * seq.homology) else: if seq.score: id_text = "%4.0f" % (seq.score) painter.setPen(self.viewer.inv_background_color) offset = 6 * font_width painter.drawText( width - offset, y_position + font_height + y_offset, id_text) if self.viewer.display_boundaries and \ seq.isValidProtein(): start_id = "%5s" % seq.ungappedId(start, start, end) end_id = " %s" % seq.ungappedId( end - 1, start, end, backwards=True) painter.setPen(self.viewer.inv_background_color) painter.drawText( self.viewer.margin + font_width - 2, y_position + font_height + y_offset, start_id) offset = 6 * font_width + (end - start) * cell_width painter.drawText( self.viewer.margin + offset, y_position + font_height + y_offset, end_id) painter.setFont(my_font) # self.viewer.font()) except: print("Painter exception at row", row_idx, row, seq, seq.name) raise # Move the the next row position. if seq.type != constants.SEQ_SEPARATOR: y_position = y_position + font_height * row_height else: y_position = y_position + int( sep_scale * font_height * row_height) self.paintCursor(painter) self.paintUserAnnotations(painter) self.paintAuxRectangle(painter) if self.emphasized: painter.setClipping(False) painter.setPen(QtGui.QPen(QtGui.QColor(QtGui.qRgb(0, 255, 255)), 1)) painter.setBrush(QtGui.QBrush(QtCore.Qt.NoBrush)) if self.viewer.wrapped: painter.drawRect(1, 1, width - 19, height - 4) else: painter.drawRect(1, 1, width - 19, height - 19)
[docs] def updateColumns(self, start, end): if start is None or end is None: return False if start > end: tmp = end end = start start = tmp if start < 0: start = 0 if self.viewer: max_columns = self.viewer.maxColumns() if end > self.viewer.left_column + max_columns: self.viewer.sequence_area.horizontal_scroll_bar.setValue( self.viewer.sequence_area.horizontal_scroll_bar.value() + 1) return True if start < self.viewer.left_column: self.viewer.sequence_area.horizontal_scroll_bar.setValue( self.viewer.sequence_area.horizontal_scroll_bar.value() - 1) return True return False