"""
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 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 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 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 paintRowLogo(self, painter, row, row_position):
"""
Paints the row text contents.
"""
seq, start, end, row_height = row
residues = seq.residues
length = seq.length()
seq_position = start
font_height = self.viewer.font_height
font_width = self.viewer.font_width
cell_width = self.viewer.cell_width
hscale = old_div(float(cell_width), float(font_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_offset = -2
seq_position = start
text = ""
draw_3letter_codes = False
if self.viewer.zoom_factor > 4 and \
seq.type == constants.SEQ_AMINO_ACIDS:
draw_3letter_codes = True
painter.setFont(self.viewer.original_font)
x_pos = x_position
y_offset = 0
else:
x_pos = x_position + 1
y_pos = row_position + y_offset + font_height * (row_height - 1)
painter.setPen(QtCore.Qt.gray)
residues = self.viewer.sequence_group.profile.residues
length = len(residues)
while seq_position < end and seq_position < length:
residue = residues[seq_position]
profile_res = profile.residues[seq_position]
r, g, b = profile.residues[seq_position].colorType()
color = QtGui.QColor(r, g, b)
painter.setPen(color)
pos = 0.0
rlist = []
for pair in residue.frequencies[0:5]:
c, f = pair
if f == 0.0:
break
tmpcode = profile_res.code
profile_res.code = c
r, g, b = profile_res.colorType()
profile_res.code = tmpcode
color = QtGui.QColor(r, g, b)
f *= residue.bits * 0.9
rlist.append((c, pos, f, color))
pos += f
if f < 0.5 or pos > 3.0:
break
painter.save()
painter.translate(x_pos, y_pos)
painter.scale(hscale, 1.0)
for index, letter in enumerate(rlist):
c, pos, scale, color = letter
if index and scale <= 0.5:
break
painter.setPen(color)
painter.save()
if hscale < 1.0:
painter.setBrush(color)
painter.drawRect(0.0, 0.0, cell_width,
-scale * 0.7 * font_height)
else:
painter.scale(1.0, scale)
painter.drawText(0.0, 0.0, c)
painter.restore()
painter.translate(0.0, -0.66 * (font_height + 2) * scale)
painter.restore()
seq_position = seq_position + 1
x_pos += cell_width
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