import os
from past.utils import old_div
from schrodinger.Qt import QtCore
from schrodinger.Qt import QtGui
from schrodinger.Qt import QtWidgets
try:
from schrodinger.infra.mm import mmfile_schrodinger_appdata_dir
except:
mmfile_schrodinger_appdata_dir = None
try:
from schrodinger.maestro import maestro
except:
maestro = None
try:
from schrodinger.protein.sequence import find_generalized_pattern
except:
find_generalized_pattern = None
MSV_PATTERNS = None
FIND_COMBO_BOX = None
FIND_LINE_EDIT = None
FIND_TEXT_CHANGED = None
PATTERN_EDIT_DIALOG = None
DEAMIDATION_PATTERN = "Deamidation Site"
OXIDATION_PATTERN = "Oxidation Site"
GLYCOSYLATION_N_PATTERN = "Glycosylation Site"
PROTEOLYSIS_PATTERN = "Proteolysis Site"
DEAMIDATION_RGB = (255, 0, 0)
OXIDATION_RGB = (0, 255, 0)
PROTEOLYSIS_RGB = (0, 0, 255)
GLYCOSYLATION_N_RGB = (255, 0, 255)
pre_defined_pattern_colors = {
DEAMIDATION_PATTERN: DEAMIDATION_RGB,
GLYCOSYLATION_N_PATTERN: GLYCOSYLATION_N_RGB,
PROTEOLYSIS_PATTERN: PROTEOLYSIS_RGB,
OXIDATION_PATTERN: OXIDATION_RGB
}
[docs]def get_pre_defined_pattern_colors():
"""
Return the global dictionary containing colors (rgb values) corresponding
to the pre-defined patterns.
This function is used by `reactive_protein_residues_gui.py`.
"""
global pre_defined_pattern_colors
return pre_defined_pattern_colors
def _set_pre_defined_pattern_color(pattern_type, color):
"""
Set the color (rgb values) for a pre-defined pattern.
:param pattern_type: pattern type or name
:type pattern_type: str
:param color: rgb values of a color
:type color: tuple(int, int, int)
"""
global pre_defined_pattern_colors
if pattern_type not in pre_defined_pattern_colors:
return
pre_defined_pattern_colors[pattern_type] = color
[docs]def writeMSVPatterns(patterns):
if mmfile_schrodinger_appdata_dir:
settings_path = mmfile_schrodinger_appdata_dir()
settings_path = settings_path + os.sep + "msv"
if not os.path.exists(settings_path):
os.makedirs(settings_path)
try:
settings_file = settings_path + os.sep + "patterns"
file = open(settings_file, "w")
for name, pattern, hotspot, color in patterns:
r, g, b = color
rs = ('0' + hex(r)[2:])[-2:]
gs = ('0' + hex(g)[2:])[-2:]
bs = ('0' + hex(b)[2:])[-2:]
color_str = '#' + rs + gs + bs
if hotspot == "None":
file.write(name + "\n" + pattern + color_str + "\n")
else:
file.write(name + "\n" + pattern + ":" + hotspot +
color_str + "\n")
file.close()
except:
return False # Error
return True
[docs]def readMSVPatterns():
global MSV_PATTERNS
default_patterns = [
(DEAMIDATION_PATTERN, "x-[NQ]-[GASHMYD]", "2", DEAMIDATION_RGB),
(GLYCOSYLATION_N_PATTERN, "N-{P}-[ST]-{P}", "1", GLYCOSYLATION_N_RGB),
(PROTEOLYSIS_PATTERN, "x-D-x", "2", PROTEOLYSIS_RGB),
(OXIDATION_PATTERN, "[HMCWY]", "1", OXIDATION_RGB)
]
patterns = []
if mmfile_schrodinger_appdata_dir:
settings_path = mmfile_schrodinger_appdata_dir()
try:
settings_file = os.path.join(settings_path, "msv", "patterns")
if not os.path.isfile(settings_file):
settings_file = os.path.join(settings_path, "msv_patterns")
if not os.path.isfile(settings_file):
raise RuntimeError("No settings file")
fh = open(settings_file)
lines = fh.readlines()
fh.close()
if not lines:
raise RuntimeError("Settings file was empty")
for index in range(old_div(len(lines), 2)):
pattern = lines[2 * index + 1].rstrip()
hotspot = "None"
color = (255, 0, 0)
if '#' in pattern:
try:
pos = pattern.find('#')
color_str = pattern[pos + 1:]
pattern = pattern[:pos]
color = (int(color_str[0:2],
16), int(color_str[2:4],
16), int(color_str[4:6], 16))
except:
color = (255, 0, 0)
if ':' in pattern:
try:
pos = pattern.find(':')
hotspot = pattern[pos + 1:]
pattern = pattern[:pos]
except:
pass
name = lines[2 * index].rstrip()
patterns.append((name, pattern, hotspot, color))
_set_pre_defined_pattern_color(name, color)
except RuntimeError as err:
patterns = default_patterns
writeMSVPatterns(patterns)
MSV_PATTERNS = patterns
return patterns
[docs]def findItemChanged(index):
global MSV_PATTERNS
global FIND_COMBO_BOX
global FIND_LINE_EDIT
global FIND_TEXT_CHANGED
global PATTERN_EDIT_DIALOG
index -= 2
if index < 0:
return
if index < len(MSV_PATTERNS):
FIND_COMBO_BOX.setCurrentIndex(0)
FIND_LINE_EDIT.setText(MSV_PATTERNS[index][1])
FIND_TEXT_CHANGED()
else:
FIND_COMBO_BOX.setCurrentIndex(0)
PATTERN_EDIT_DIALOG.setPatterns(MSV_PATTERNS)
PATTERN_EDIT_DIALOG.display()
[docs]class PatternEditDialog(QtWidgets.QDialog):
"""
This class implements simple pattern editor dialog.
"""
[docs] def __init__(self, parent):
# Initialize base class.
QtWidgets.QDialog.__init__(self, parent)
self.setWindowTitle("Edit Patterns")
self.ok_pressed = False
self.okButton = QtWidgets.QPushButton("OK")
self.cancelButton = QtWidgets.QPushButton("Cancel")
self.newButton = QtWidgets.QPushButton("Add New Pattern")
self.deleteButton = QtWidgets.QPushButton("Delete Selected Rows")
self.spacer = QtWidgets.QWidget()
self.spacer.setSizePolicy(QtWidgets.QSizePolicy.Expanding,
QtWidgets.QSizePolicy.Preferred)
self.buttonLayout = QtWidgets.QHBoxLayout()
self.buttonLayout.addWidget(self.newButton)
self.buttonLayout.addWidget(self.deleteButton)
self.buttonLayout.addWidget(self.spacer)
self.buttonLayout.addWidget(self.okButton)
self.buttonLayout.addWidget(self.cancelButton)
self.patternTable = QtWidgets.QTableWidget(0, 4)
self.table_font = self.patternTable.font()
self.table_font.setPointSize(10)
self.patternTable.setFont(self.table_font)
self.patternTable.setHorizontalHeaderLabels(
["Pattern Name", "Definition", "Hotspot", "Color"])
self.patternTable.resizeColumnsToContents()
self.patternTable.horizontalHeader().setSectionResizeMode(
1, QtWidgets.QHeaderView.Stretch)
self.patternTable.sortItems(1, QtCore.Qt.AscendingOrder)
self.description = QtWidgets.QTextEdit(self)
self.description.setReadOnly(True)
self.description.setHtml(
"<b>Double click a table cell to edit pattern name or definition.</b><br><br> \
<b>Pattern syntax</b><br><br> \
The patterns are defined using extended PROSITE syntax:<ul> \
<li> standard IUPAC one-letter codes are used for all amino acids \
<li> each element in a pattern is separated using '-' symbol \
<li> symbol 'x' is used for position where any amino acid is accepted \
<li> ambiguities are listed using the acceptable amino acids between \
square brackets, e.g. [ACT] means Ala, Cys or Thr \
<li>amino acids not accepted for a given position are indicated \
by listing them between curly brackets, e.g. {GP} means 'not Gly and not Pro' \
<li>repetition is indicated using parentheses, e.g. A(3) means Ala-Ala-Ala, \
x(2,4) means between 2 to 4 any residues \
<li>the following lowercase characters can be used as additional flags \
<ul> \
<li>'x': any amino acid \
<li>'a': acidic residue: [DE] \
<li>'b': basic residue: [KR] \
<li>'o': hydrophobic residue: [ACFILPWVY] \
<li>'p': aromatic residue: [WYF] \
<li>'s': solvent exposed residue \
<li>'h': helical residue \
<li>'e': extended residue \
<li>'f': flexible residue \
</ul></ul><br> \
<b>Pattern examples</b> \
<ul> \
<li> N-{P}-[ST] : Asn-X-Ser or Thr (X != Pro) \
<li> N[sf]-{P}[sf]-[ST][sf] : as above, but all residues flexible OR solvent exposed \
<li> Nsf-{P}sf-[ST]sf : as above, but all residues flexible AND solvent exposed \
<li> Ns{f} : Asn solvent exposed AND not in flexible region \
<li> N[s{f}] : Asn solvent exposed OR not in flexible region \
<li> [ab]{K}{s}f : acidic OR basic, with exception of Lys flexible AND not solvent exposed \
<li> Ahe : Ala helical AND extended - no match possible \
<li> A[he] : Ala helical OR extended \
<li> A{he} : Ala not helical nor extended \
<li> [ST] : Ser OR Thr \
<li> ST : Ser AND Thr - no match possible \
</ul>")
self.layout = QtWidgets.QVBoxLayout(self)
self.layout.addWidget(self.patternTable)
self.layout.addWidget(self.description)
self.layout.addLayout(self.buttonLayout)
self.patternTable.resizeColumnToContents(1)
self.patternTable.horizontalHeader().setStretchLastSection(False)
self.patternTable.setWordWrap(False)
self.patternTable.itemSelectionChanged.connect(self.selectionChanged)
self.patternTable.cellDoubleClicked.connect(self.cellDoubleClicked)
self.cancelButton.clicked.connect(self.cancelClicked)
self.okButton.clicked.connect(self.okClicked)
self.newButton.clicked.connect(self.addNewClicked)
self.deleteButton.clicked.connect(self.deleteClicked)
self.resize(self.patternTable.width() + 15, self.patternTable.height())
self.selecting = False
[docs] def selectionChanged(self):
if self.selecting:
return
self.selecting = True
for item in self.patternTable.selectedItems():
row = item.row()
# Select the entire row.
self.patternTable.setRangeSelected(
QtWidgets.QTableWidgetSelectionRange(
row, 0, row,
self.patternTable.columnCount() - 1), True)
self.selecting = False
[docs] def cellDoubleClicked(self, row, column):
"""
User double clicked a cell.
"""
global MSV_PATTERNS
if column == 3:
name, pattern, hotspot, color = MSV_PATTERNS[row]
r, g, b = color
color = QtWidgets.QColorDialog.getColor(
initial=QtGui.QColor(r, g, b))
if color:
color = (color.red(), color.green(), color.blue())
r, g, b = color
_set_pre_defined_pattern_color(name, color)
new_item = QtWidgets.QTableWidgetItem("")
new_item.setData(QtCore.Qt.DecorationRole,
QtGui.QColor(r, g, b))
new_item.setFlags(QtCore.Qt.ItemIsSelectable |
QtCore.Qt.ItemIsEnabled)
self.patternTable.setItem(row, 3, new_item)
MSV_PATTERNS[row] = (name, pattern, hotspot, color)
writeMSVPatterns(MSV_PATTERNS)
[docs] def okClicked(self):
"""
OK button callback
"""
# Validate patterns
valid = True
for index in range(self.patternTable.rowCount()):
name = str(self.patternTable.item(index, 0).text())
pattern = str(self.patternTable.item(index, 1).text())
hotspot = str(self.patternTable.item(index, 2).text())
self.patternTable.item(index, 1).setForeground(QtCore.Qt.black)
if hotspot != "None":
try:
num = int(hotspot)
except:
self.patternTable.setRangeSelected(
QtWidgets.QTableWidgetSelectionRange(
index, 0, index,
self.patternTable.columnCount() - 1), False)
self.patternTable.item(index,
2).setForeground(QtCore.Qt.red)
valid = False
break
if find_generalized_pattern(None, pattern,
validate_pattern=True) != True:
self.patternTable.setRangeSelected(
QtWidgets.QTableWidgetSelectionRange(
index, 0, index,
self.patternTable.columnCount() - 1), False)
self.patternTable.item(index, 1).setForeground(QtCore.Qt.red)
valid = False
break
if not valid:
QtWidgets.QMessageBox.critical(
self, "Invalid Pattern Syntax",
"Please correct the pattern marked red.")
return
self.hide()
self.ok_pressed = True
[docs] def deleteClicked(self):
"""
Delete button callback
"""
rows = self.patternTable.selectionModel().selectedRows()
while rows:
self.patternTable.removeRow(rows[0].row())
rows = self.patternTable.selectionModel().selectedRows()
pattern_list = self.createPatternList()
updateFindComboBox(pattern_list)
[docs] def cancelClicked(self):
"""
Cancel button callback
"""
self.hide()
self.ok_pressed = False
[docs] def addNewClicked(self):
"""
Add new callback
"""
row = self.patternTable.rowCount()
self.patternTable.insertRow(row)
new_item = QtWidgets.QTableWidgetItem("New Pattern")
new_item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled |
QtCore.Qt.ItemIsEditable)
self.patternTable.setItem(row, 0, new_item)
new_item = QtWidgets.QTableWidgetItem("A-A-A")
new_item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled |
QtCore.Qt.ItemIsEditable)
self.patternTable.setItem(row, 1, new_item)
new_item = QtWidgets.QTableWidgetItem("None")
new_item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled |
QtCore.Qt.ItemIsEditable)
self.patternTable.setItem(row, 2, new_item)
new_item = QtWidgets.QTableWidgetItem("")
new_item.setData(QtCore.Qt.DecorationRole, QtGui.QColor(255, 0, 0))
new_item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
self.patternTable.setItem(row, 3, new_item)
pattern_list = self.createPatternList()
updateFindComboBox(pattern_list)
[docs] def setPatterns(self, pattern_list):
self.patternTable.clear()
self.patternTable.setRowCount(0)
row = 0
self.patternTable.setHorizontalHeaderLabels(
["Pattern Name", "Definition", "Hotspot", "Color"])
for index, item in enumerate(pattern_list):
name, pattern, hotspot, color = item
self.patternTable.insertRow(row)
new_item = QtWidgets.QTableWidgetItem(name)
new_item.setFlags(QtCore.Qt.ItemIsSelectable |
QtCore.Qt.ItemIsEnabled |
QtCore.Qt.ItemIsEditable)
self.patternTable.setItem(row, 0, new_item)
new_item = QtWidgets.QTableWidgetItem(pattern)
new_item.setFlags(QtCore.Qt.ItemIsSelectable |
QtCore.Qt.ItemIsEnabled |
QtCore.Qt.ItemIsEditable)
self.patternTable.setItem(row, 1, new_item)
new_item = QtWidgets.QTableWidgetItem(hotspot)
new_item.setFlags(QtCore.Qt.ItemIsSelectable |
QtCore.Qt.ItemIsEnabled |
QtCore.Qt.ItemIsEditable)
self.patternTable.setItem(row, 2, new_item)
r, g, b = color
new_item = QtWidgets.QTableWidgetItem("")
new_item.setData(QtCore.Qt.DecorationRole, QtGui.QColor(r, g, b))
new_item.setFlags(QtCore.Qt.ItemIsSelectable |
QtCore.Qt.ItemIsEnabled)
self.patternTable.setItem(row, 3, new_item)
row += 1
self.patternTable.resizeColumnToContents(0)
[docs] def display(self):
"""
Brings up the modal dialog. Returns None if cancelled,
or the modified FeaturePattern, if OK.
"""
self.exec()
if self.ok_pressed:
self.ok_pressed = False
pattern_list = self.createPatternList()
updateFindComboBox(pattern_list)
return pattern_list
# Cancel pressed:
return MSV_PATTERNS
[docs] def createPatternList(self):
global MSV_PATTERNS
pattern_list = []
for index in range(self.patternTable.rowCount()):
name = str(self.patternTable.item(index, 0).text())
pattern = str(self.patternTable.item(index, 1).text())
hotspot = str(self.patternTable.item(index, 2).text())
color = QtGui.QColor(
self.patternTable.item(index, 3).data(QtCore.Qt.DecorationRole))
color = (color.red(), color.green(), color.blue())
pattern_list.append((name, pattern, hotspot, color))
MSV_PATTERNS = pattern_list
writeMSVPatterns(pattern_list)
return pattern_list
[docs]def updateFindComboBox(pattern_list):
global FIND_COMBO_BOX
if FIND_COMBO_BOX is None:
return
name_list = [x[0] for x in pattern_list]
FIND_COMBO_BOX.clear()
FIND_COMBO_BOX.addItem("Select Pattern...")
FIND_COMBO_BOX.addItems(name_list)
FIND_COMBO_BOX.addItem("Edit Patterns...")
FIND_COMBO_BOX.insertSeparator(1)
FIND_COMBO_BOX.insertSeparator(len(name_list) + 2)