Source code for schrodinger.ui.ligfilter

"""
Components for creating GUIs that set up Ligfilter jobs.

See the schrodinger.utils.ligfilter module for a complete description of
ligfilter and its functions.

There's not likely to be much general use for this module unless you are
creating a modified interface for LigFilter or want to embed an interface
to Ligfilter in your own application.

The main class in this module is the LigfilterFrame. This provides a complete
interface to the Ligfilter GUI.

Copyright Schrodinger, LLC. All rights reserved.

"""
# Contributors: Jeff A. Saunders, Matvey Adzhigirey

# ToDo:
#
# Delete button needs to be disabled if nothing selected
# Enable "Add" only when settings appropriate?
# Should non-numeric properties be selectable?  For example, a string property
#     could be checked for existence, and a boolean prop could be checked for
#     'True'.  Change the requirements of the operator/value options depending
#     on the property type?
# Should the list of patterns show default vs. custom patterns, simple vs.
#     composite patterns?

from collections import OrderedDict

import schrodinger.structure as structure
import schrodinger.ui.qt.appframework as appframework
import schrodinger.ui.qt.filedialog as filedialog
import schrodinger.ui.qt.propertyselector as propertyselector
import schrodinger.utils.ligfilter as ligfilter
from schrodinger.Qt import QtCore
from schrodinger.Qt import QtGui
from schrodinger.Qt import QtWidgets
from schrodinger.Qt.QtCore import Qt
from schrodinger.structutils import analyze

# Import the module(s) created from the QtDesigner *.ui file(s):
from . import ligfilter_definition_dialog_ui
from . import ligfilter_ui

try:
    from schrodinger import maestro
except ImportError:
    maestro = None

#
# Global constants
#
TEXTBOX_FONT = "Courier New"


[docs]class PatternModel(QtCore.QAbstractTableModel): """ Class for strring patterns (definitions) and their groups """
[docs] def __init__(self): QtCore.QAbstractTableModel.__init__(self) self.items = []
[docs] def rowCount(self, parent=QtCore.QModelIndex()): # noqa: M511 """ Returns number of rows """ return len(self.items)
[docs] def columnCount(self, parent=QtCore.QModelIndex()): # noqa: M511 """ Returns number of columns """ return 1
[docs] def setItems(self, data_list): """ Sets the internal data list to the specified list. NOTE: Will use the actual list passed to it (without making a copy) """ self.beginResetModel() self.items = data_list self.endResetModel()
[docs] def getRowText(self, row): return self.items[row]
[docs] def data(self, index, role=Qt.DisplayRole): """ Given a cell index, returns the data that should be displayed in that cell (text or check button state). Used by the view. """ if role == Qt.DisplayRole: item = self.items[index.row()] return item return None
[docs] def headerData(self, section, orientation, role): """ Returns the string that should be displayed in the specified header cell. Used by the View. """ if orientation == Qt.Horizontal: if role == Qt.DisplayRole: if section == 0: return "Definition"
[docs] def flags(self, index): """ Returns flags for the specified cell. Whether it is enabled or not. """ if not index.isValid(): return 0 item = self.items[index.row()] if str(item).endswith(":"): return Qt.NoItemFlags else: return (Qt.ItemIsSelectable | Qt.ItemIsEnabled)
[docs]class DefinitionViewerDialog(QtWidgets.QDialog): """ Frame containing a text box with a text display of the definition. Shows multi-level definitions (i.e., patterns built from other patterns) with indenting. """
[docs] def __init__(self, parent, definitionname, definitions): """ Pass in a definition name and a dictionary of definitions (names and Definition objects), and this class displays the definition in a text box. """ QtWidgets.QDialog.__init__(self, parent) self.parent = parent self.setWindowTitle("Ligand Filtering - View Definition") self.main_layout = QtWidgets.QVBoxLayout(self) #self.main_layout.setContentsMargins(3, 3, 3, 3) #self.main_layout.setSpacing(1) self.definitionname = definitionname self.definitions = definitions self.label = QtWidgets.QLabel(self) self.label.setText("Definition of functional group '%s'" % self.definitionname) self.main_layout.addWidget(self.label) self.text = QtWidgets.QPlainTextEdit(self) self.text.setReadOnly(True) self.main_layout.addWidget(self.text) self.button_box = QtWidgets.QDialogButtonBox(QtCore.Qt.Horizontal, self) self.ok_button = QtWidgets.QPushButton("OK") self.button_box.addButton(self.ok_button, QtWidgets.QDialogButtonBox.ActionRole) self.main_layout.addStretch() # Add a stretchable section to the end self.main_layout.addWidget(self.button_box) self.ok_button.clicked.connect(self.accept) self.text.setFont(QtGui.QFont(TEXTBOX_FONT)) if definitionname not in self.definitions: self.text.appendPlainText("No definition") else: self.printTree(definitionname, 0)
[docs] def printTree(self, name, level): """ Recursively descend through the included/excluded lists, printing to the text box and indenting the text at deeper levels. """ if name in self.definitions: d = self.definitions[name] dsort = d.includes() dsort.sort(key=str.lower) for i in dsort: self.text.appendPlainText("%*s %s" % (level * 4, "+", i)) self.printTree(i, level + 1) esort = d.excludes() esort.sort(key=str.lower) for e in esort: self.text.appendPlainText("%*s %s" % (level * 4, "-", e)) self.printTree(e, level + 1)
[docs]class AddDefinitionDialog(QtWidgets.QDialog): """ Frame for generating a pattern definition. """
[docs] def warning(self, text): QtWidgets.QMessageBox.warning(self, "Warning", text)
[docs] def __init__(self, parent, definitions): QtWidgets.QDialog.__init__(self, parent) self.parent = parent self.ui = ligfilter_definition_dialog_ui.Ui_Dialog() self.ui.setupUi(self) self.setWindowTitle("Ligand Filtering - Add Definition") # Make a definition dictionary for convenience self.definitions = {} for d in definitions: self.definitions[d.name] = d self.included = [] self.excluded = [] self.ui.include_button.clicked.connect(self.include) self.ui.exclude_button.clicked.connect(self.exclude) self.ui.getsmarts_button.clicked.connect(self.getSMARTSFromSelection) self.definition_model = QtCore.QStringListModel() self.ui.definition_listview.setSelectionMode( QtWidgets.QAbstractItemView.SingleSelection) self.ui.definition_listview.setModel(self.definition_model) self.ui.definition_listview.selectionModel().selectionChanged.connect( self.updateDefinitionState) names = list(self.definitions) names.sort(key=lambda s: s.lower()) self.definition_model.setStringList(names) self.ui.viewdefinition_button.clicked.connect(self.view) self.inclexcl_model = QtCore.QStringListModel() self.ui.inclexcl_listview.setSelectionMode( QtWidgets.QAbstractItemView.SingleSelection) self.ui.inclexcl_listview.setModel(self.inclexcl_model) self.ui.inclexcl_listview.selectionModel().selectionChanged.connect( self.updateInclExclState) self.ui.inclexcl_listview.setFont(QtGui.QFont(TEXTBOX_FONT)) self.ui.delete_button.clicked.connect(self.delete) self.ui.deleteall_button.clicked.connect(self.deleteAll) self.setDefaults() self.ui.buttonBox.accepted.connect(self.okPressed) # NOTE: This needs to be of the "old style" API to work - PANEL-4182 self.ui.buttonBox.accepted.disconnect(self.accept) self.ui.buttonBox.rejected.connect(self.rejected) self.ui.buttonBox.helpRequested.connect(self.helpPressed) # Change the name of the OK button to "Add": self.ui.buttonBox.button(QtWidgets.QDialogButtonBox.Ok).setText("Add") self.ok_pressed = False self.done = False
[docs] def okPressed(self): self.ok_pressed = True self.done = True self.accept()
[docs] def rejected(self): self.ok_pressed = False self.done = True self.reject()
[docs] def helpPressed(self): maestro.command("showpanel help") maestro.command("helptopic GLIDE_ADD_DEFINITION_DB")
[docs] def display(self): """ Brings up the dialog and waits for the user to close it. Returns None if cancelled, or modified receptor if OK. """ # Use show/withdraw instead of activate/deactivate so the dialog # isn't modal. Allows Workspace selection while dialog is visible. # START section temporarily altered for Ev:87076. #self.addpattern_dialog.show() self.show() while not self.done: if maestro: maestro.process_pending_events() else: QtWidgets.QApplication.instance().processEvents() if self.ok_pressed: newpattern = self.getDefinition() #if newpattern: # START section temporarily altered for Ev:87076. # # If the pattern is already defined, replace it with the new # # pattern definition. # for pattern in self.patterns: # if newpattern.name==pattern.name: # self.patterns.remove(pattern) # break # self.patterns.append(newpattern) # self.setPatternOptions() # self.updateText() # self.addpattern_dialog.withdraw() return newpattern return None
[docs] def setDefaults(self): self.deleteAll() self.updateDefinitionState()
[docs] def updateDefinitionState(self, selected=None, deselected=None): selected_rows = self.ui.definition_listview.selectionModel( ).selectedRows() if selected_rows: self.ui.viewdefinition_button.setEnabled(True) row_text = self.definition_model.stringList()[ selected_rows[0].row()] self.ui.smarts_ef.setText(row_text) else: self.ui.viewdefinition_button.setEnabled(False)
[docs] def view(self): selected_rows = self.ui.definition_listview.selectionModel( ).selectedRows() if not selected_rows: self.warning("No definition selected") else: row_text = str( self.definition_model.stringList()[selected_rows[0].row()]) view_dialog = DefinitionViewerDialog(self, row_text, self.definitions) view_dialog.exec()
[docs] def getDefinition(self): """ Return the pattern defined in the AddDefinitionDialog. Return None if the definition is bad, or if the user opts not to overwrite an existing pattern definition. """ name = str(self.ui.newdefinition_ef.text()) if not name: # No name has been given. self.warning('No name specified') elif name in self.included or name in self.excluded: # No pattern definition includes/excludes its own name self.warning('Circular pattern definition') elif not self.included: # Nothing included in the pattern definition self.warning( 'No SMARTS expression or pattern name included in definition') elif name in list(self.definitions): # A pattern definition already exists of that name. Issue warning. msg = '%s\n%s' % ( "This pattern name already exists.", "Are you sure you want to override the pattern definition?") if appframework.question(msg, button1="OK", button2="Cancel", parent=self, title="Question"): return ligfilter.Definition(name, self.included, self.excluded) else: return ligfilter.Definition(name, self.included, self.excluded)
[docs] def updateText(self): string_list = [] for i in self.included: string_list.append(" + %s" % i) for e in self.excluded: string_list.append(" - %s" % e) self.inclexcl_model.setStringList(string_list)
[docs] def include(self): value = str(self.ui.smarts_ef.text()) if value: if value in self.included + self.excluded: self.warning('SMARTS expression or pattern name already used') else: self.included.append(value) self.updateText() else: self.warning('No SMARTS expression or pattern name specified') self.updateInclExclState()
[docs] def exclude(self): value = str(self.ui.smarts_ef.text()) if value: if value in self.included + self.excluded: self.warning('SMARTS expression or pattern name already used') else: if self.definitions[value].excludes(): self.warning( 'Pattern with excludes cannot itself be excluded') else: self.excluded.append(value) self.updateText() else: self.warning('No SMARTS expression or pattern name specified') self.updateInclExclState()
[docs] def getSMARTSFromSelection(self): """ Set the SMARTS entry field according to atoms selected in the Workspace. """ st = maestro.workspace_get() selected_atoms = maestro.selected_atoms_get() if not selected_atoms: self.warning("There are no atoms selected in the Workspace") return elif len(selected_atoms) > 50: self.warning( "There are too many atoms selected in the Workspace (%i)" % len(selected_atoms)) return try: smarts = analyze.generate_smarts_canvas(st, selected_atoms, honor_maestro_prefs=True) except ValueError as err: self.warning(str(err)) return self.ui.smarts_ef.setText(smarts)
[docs] def updateInclExclState(self, selected=None, deselected=None): selected_rows = self.ui.inclexcl_listview.selectionModel().selectedRows( ) if selected_rows: self.ui.delete_button.setEnabled(True) else: self.ui.delete_button.setEnabled(False) if self.inclexcl_model.rowCount() > 0: self.ui.deleteall_button.setEnabled(True) else: self.ui.deleteall_button.setEnabled(False)
[docs] def delete(self): selected_rows = self.ui.inclexcl_listview.selectionModel().selectedRows( ) if selected_rows: string_list = self.inclexcl_model.stringList() index = selected_rows[0].row() s = str(string_list[index]).strip().split(" ") op = s[0] name = " ".join(s[1:]) if op == '+': self.included.remove(name) else: self.excluded.remove(name) string_list.pop(index) self.inclexcl_model.setStringList(string_list) else: self.warning("No pattern/expression specified") self.updateInclExclState()
[docs] def deleteAll(self): self.included = [] self.excluded = [] self.inclexcl_model.setStringList([]) self.updateDefinitionState()
[docs]class CriteriaListModel(QtCore.QAbstractListModel): """ Class for storing the criteria list information. """
[docs] def __init__(self): QtCore.QAbstractListModel.__init__(self) self._criteria = []
[docs] def rowCount(self, parent=QtCore.QModelIndex()): # noqa: M511 """ Returns number of rows """ return len(self._criteria)
[docs] def resetData(self): self.setAllData([])
[docs] def setAllData(self, lines): self.beginResetModel() self._criteria = lines self.endResetModel()
[docs] def data(self, index, role=Qt.DisplayRole): """ Given a cell index, returns the data that should be displayed in that cell (text or check button state). Used by the view. """ if role == Qt.DisplayRole: try: line, linetype = self._criteria[index.row()] except IndexError: return None # VSW-841 return line
[docs] def getRowCriteria(self, rownum): return self._criteria[rownum]
[docs] def removeRow(self, rownum): self.beginRemoveRows(QtCore.QModelIndex(), rownum, rownum) self._criteria.pop(rownum) self.endRemoveRows()
[docs] def flags(self, index): """ Returns flags for the specified cell. Whether selectable or not. """ if not index.isValid(): return Qt.NoItemFlags try: line, linetype = self._criteria[index.row()] except IndexError: # Work around for Ev:135525 return Qt.NoItemFlags if linetype: return Qt.ItemIsSelectable | Qt.ItemIsEnabled else: return Qt.ItemIsEnabled
[docs]class LigfilterFrame(QtWidgets.QWidget): """ Frame for setting up Ligfilter jobs, including property/SMARTS criteria and new definitions. This is the main class in this module. """
[docs] def __init__(self, parent, show_properties_tab=True): QtWidgets.QWidget.__init__(self, parent) self.parent = parent self.ui = ligfilter_ui.Ui_Form() self.ui.setupUi(self) self.show_properties_tab = show_properties_tab if not self.show_properties_tab: self.ui.tabWidget.setTabEnabled(0, False) self.prop_operators = ligfilter.OPERATORS + ["Exists"] # Non-numeric operators self.boolean_prop_operators = ("is True", "is False", "Exists") # # Notebook for adding criteria and patterns # self.addprop_selector = propertyselector.PropertySelector( self.ui.propselector_widget) self.addprop_selector.listbox.itemSelectionChanged.connect( self.selectProperty) self.ui.addprop_ef.textChanged[str].connect(self.checkPropertyType) self.ui.addpropop1_menu.addItems(self.prop_operators) self.ui.addpropop1_menu.currentTextChanged.connect( self.addpropop1Modified) self.ui.addpropmult_menu.addItems(("", "AND", "OR")) self.ui.addpropmult_menu.currentTextChanged.connect( self.setMultiplePropCriterionState) self.ui.addpropop2_menu.addItems(ligfilter.OPERATORS) self.ui.addpropcriterion_button.clicked.connect(self.addPropCriterion) # # The "Add predefined criteria" tab # self.addpredef_model = QtCore.QStringListModel() self.ui.addpredef_listview.setSelectionMode( QtWidgets.QAbstractItemView.SingleSelection) self.ui.addpredef_listview.setModel(self.addpredef_model) self.ui.addpredef_listview.selectionModel().selectionChanged.connect( self.selectPredefined) predef_sorted = ligfilter.PREDEFINED_KEYS[:] predef_sorted.sort(key=str.lower) self.addpredef_model.setStringList(predef_sorted) self.ui.addpredef_ef.setEnabled(False) # FIXME # self.ui.addpredef_ef.configure( # entry_disabledforeground=self.ui.addpredef_ef.cget('entry_fg') # ) self.ui.addpredefop1_menu.addItems(ligfilter.OPERATORS) self.ui.addpredefval1_ef.setValidator(QtGui.QDoubleValidator(self)) self.ui.addpredefmult_menu.addItems(("", "AND", "OR")) self.ui.addpredefmult_menu.currentTextChanged.connect( self.setMultiplePredefinedCriterionState) self.ui.addpredefop2_menu.addItems(ligfilter.OPERATORS) self.ui.addpredefval2_ef.setValidator(QtGui.QDoubleValidator(self)) self.ui.addpredefcriterion_button.clicked.connect( self.addPredefinedCriterion) # # The "Add pattern criteria" tab # self.addpattern_model = PatternModel() self.ui.addpattern_listview.setSelectionMode( QtWidgets.QAbstractItemView.SingleSelection) self.ui.addpattern_listview.setModel(self.addpattern_model) self.ui.addpattern_listview.selectionModel().selectionChanged.connect( self.selectPattern) self.ui.addpattern_button.clicked.connect(self.addPattern) self.ui.viewpattern_button.clicked.connect(self.viewPattern) self.ui.addpattern_ef.setEnabled(False) # FIXME # self.ui.addpattern_ef.configure( # entry_disabledforeground=self.ui.addpattern_ef.cget('entry_fg') # ) self.ui.addpatternop1_menu.addItems(ligfilter.OPERATORS) self.ui.addpatternval1_ef.setValidator(QtGui.QDoubleValidator(self)) self.ui.addpatternmult_menu.addItems(("", "AND", "OR")) self.ui.addpatternmult_menu.currentTextChanged.connect( self.setMultiplePatternCriterionState) self.ui.addpatternop2_menu.addItems(ligfilter.OPERATORS) self.ui.addpatternval2_ef.setValidator(QtGui.QDoubleValidator(self)) self.ui.addpatterncriterion_button.clicked.connect( self.addPatternCriterion) # # Filtering information # # Restrict the selection/highlight mechanism in the text box. self.filter_model = CriteriaListModel() self.ui.filter_listview.setSelectionMode( QtWidgets.QAbstractItemView.SingleSelection) self.ui.filter_listview.setModel(self.filter_model) self.ui.filter_listview.selectionModel().selectionChanged.connect( self.filterSelectionChanged) self.ui.filter_listview.setFont(QtGui.QFont(TEXTBOX_FONT)) # Read self.ui.read_button.clicked.connect(self.importFilterFile) # Delete self.ui.delete_button.clicked.connect(self.deleteEntry) self.filterSelectionChanged() self._properties_resettable = True self.setDefaults()
[docs] def filterSelectionChanged(self, selected=None, deselected=None): rows_selected = False selected_rows = self.ui.filter_listview.selectionModel().selectedRows() for index in selected_rows: rownum = index.row() line, linetype = self.filter_model.getRowCriteria(rownum) if linetype: rows_selected = True break self.ui.delete_button.setEnabled(rows_selected)
[docs] def setPatternOptions(self): """ Set the option menu list of keys to the predefined keys, the default patterns, and any custom patterns. There always are predefined keys, so the "add pattern criterion" widgets stay enabled. The "delete pattern" widgets are disabled if no custom patterns are defined. """ # Group the definitions by their group: defs_by_group = OrderedDict() for d in ligfilter.default_definitions: group = defs_by_group.setdefault(d.group, []) group.append(d.name) # List the user-defined patterns under the "User-defined" group if self.patterns: none_group = defs_by_group.setdefault("User-defined", []) for d in self.patterns: none_group.append(d.name) names = [] for group, defs in defs_by_group.items(): # Ev:87552 We need to support Unicode strings also: defs.sort(key=lambda s: s.lower()) if group is None: group = "Ungrouped" names.append(group + ":") for d in defs: name = " %s" % d names.append(name) self.addpattern_model.setItems(names)
[docs] def getSelectedPatternName(self): """ Return the name of the selected pattern (or None) """ selected_rows = self.ui.addpattern_listview.selectionModel( ).selectedRows() if selected_rows: row = selected_rows[0].row() row_text = str(self.addpattern_model.getRowText(row)) if not row_text.endswith(":"): # If not a group row_text = row_text[4:] # Get rid of the space indenting return row_text return None
[docs] def selectPattern(self): name = self.getSelectedPatternName() if name: self.ui.addpattern_ef.setText(name)
[docs] def selectPredefined(self): selected_rows = self.ui.addpredef_listview.selectionModel( ).selectedRows() if selected_rows: row_text = self.addpredef_model.stringList()[selected_rows[0].row()] self.ui.addpredef_ef.setText(row_text)
[docs] def selectProperty(self): selected_props = self.addprop_selector.getSelected() if selected_props: self.ui.addprop_ef.setText(str(selected_props[0]))
[docs] def setMultiplePropCriterionState(self, result): if str(result) == "": self.ui.addpropop2_menu.setEnabled(False) self.ui.addpropval2_ef.setEnabled(False) else: self.ui.addpropop2_menu.setEnabled(True) self.ui.addpropval2_ef.setEnabled(True)
[docs] def setMultiplePredefinedCriterionState(self, result): if result == "": self.ui.addpredefop2_menu.setEnabled(False) self.ui.addpredefval2_ef.setEnabled(False) else: self.ui.addpredefop2_menu.setEnabled(True) self.ui.addpredefval2_ef.setEnabled(True)
[docs] def setMultiplePatternCriterionState(self, result): if str(result) == "": self.ui.addpatternop2_menu.setEnabled(False) self.ui.addpatternval2_ef.setEnabled(False) else: self.ui.addpatternop2_menu.setEnabled(True) self.ui.addpatternval2_ef.setEnabled(True)
[docs] def updateText(self): """ Rewrite the text in the criteria/patterns text box """ lines = [] # Tag some of the lines so they can be selected by the special # mouse bindings (in selectTextLine). if self.patterns: lines.append(('#\n# Custom patterns\n#', None)) for pattern in self.patterns: # Only tag the first line as selectable s = str(pattern).split('\n') lines.append((s[0], "pattern")) for line in s[1:]: lines.append('%s' % line) lines.append(('\n', None)) if self.criteria: lines.append(('#\n# Filter criteria\n#', None)) for criterion in self.criteria: lines.append((str(criterion), "criterion")) self.filter_model.setAllData(lines)
[docs] def addPropCriterion(self): prop = str(self.ui.addprop_ef.text()) try: structure.PropertyName(dataname=prop) except: self.warning("Not a valid property name") return if not prop: self.warning('No property name specified') return if prop in [x.name for x in self.criteria]: self.warning('A criterion with this name already exists') return operator = str(self.ui.addpropop1_menu.currentText()) value = str(self.ui.addpropval1_ef.text()) if operator == "Exists": criterion = ligfilter.PropertyCriterion(prop) elif operator == "is True": criterion = ligfilter.PropertyCriterion(prop, "== True") elif operator == "is False": criterion = ligfilter.PropertyCriterion(prop, "== False") elif not value: self.warning('Value required for property criterion') return # FIXME # elif not self.ui.addpropval1_ef.valid(): # self.warning('Value not valid') # return else: propbool = str(self.ui.addpropmult_menu.currentText()) if propbool: operator2 = str(self.ui.addpropop2_menu.currentText()) value2 = str(self.ui.addpropval2_ef.text()) if operator2 and value2: compstr = '%s %s %s %s %s' % (operator, value, propbool, operator2, value2) criterion = ligfilter.PropertyCriterion(prop, compstr) else: self.warning('Second criterion requires operator and value') return elif value: compstr = '%s %s' % (operator, value) criterion = ligfilter.PropertyCriterion(prop, compstr) else: criterion = ligfilter.PropertyCriterion(prop) self.criteria.append(criterion) # self.setCriteriaOptions() self.updateText()
[docs] def addpropop1Modified(self, result): """ Callback for the first operator OptionMenu on the Properties tab. Disables the rest of the "Add criterion" widgets when property existence is being checked. """ if result in self.boolean_prop_operators: # Operators without values. This also catches "Exists" for # string, integer, and real properties. self.ui.addpropval1_ef.setEnabled(False) self.ui.addpropmult_menu.setEnabled(False) self.ui.addpropop2_menu.setEnabled(False) self.ui.addpropval2_ef.setEnabled(False) else: self.ui.addpropval1_ef.setEnabled(True) self.ui.addpropmult_menu.setEnabled(True) value = str(self.ui.addpropmult_menu.currentText()) self.setMultiplePropCriterionState(value)
[docs] def checkPropertyType(self, prop): """ Callback for modifications to the property EntryField. Adjust the operator list and value validator according to the property type entered. The EntryField may need to be cleared to prevent non-modifiable values (e.g., when switching from string to integer). """ prop = str(self.ui.addprop_ef.text()) # Don't apply checks if the property field text is cleared if not prop: return # Clear previus values: self.ui.addpropval1_ef.clear() self.ui.addpropval2_ef.clear() ptype = prop[0] if ptype == 's': # Restrict to "Exists", because the backend doesn't support string # comparisons. self.ui.addpropop1_menu.clear() self.ui.addpropop1_menu.addItems(["Exists"]) self.ui.addpropop1_menu.setCurrentIndex(0) # the entry fields are disabled elif ptype in ('i', 'r'): # Unrestricted operators, real EntryField validation self.ui.addpropop1_menu.clear() self.ui.addpropop1_menu.addItems(self.prop_operators) if ptype == 'i': validator = QtGui.QIntValidator(self) else: validator = QtGui.QDoubleValidator(self) self.ui.addpropval1_ef.setValidator(validator) self.ui.addpropval2_ef.setValidator(validator) self.ui.addpropop1_menu.setCurrentIndex(0) elif ptype == 'b': self.ui.addpropop1_menu.clear() self.ui.addpropop1_menu.addItems(self.boolean_prop_operators) self.ui.addpropop1_menu.setCurrentIndex(0)
# the entry fields are disabled
[docs] def viewPattern(self): name = self.getSelectedPatternName() if not name: self.warning("No definition selected") else: pdict = {} for d in ligfilter.default_definitions + self.patterns: pdict[d.name] = d view_dialog = DefinitionViewerDialog(self, name, pdict) view_dialog.exec()
[docs] def addPredefinedCriterion(self): predef = str(self.ui.addpredef_ef.text()) if not predef: self.warning('No predefined selected') return if predef in [x.name for x in self.criteria]: self.warning('A criterion with this name already exists') return operator = str(self.ui.addpredefop1_menu.currentText()) value = str(self.ui.addpredefval1_ef.text()) if not operator or not value: self.warning('Missing operator or value') return # FIXME elif not self.ui.addpredefval1_ef.valid(): # self.warning('Value not valid') # return else: propbool = str(self.ui.addpredefmult_menu.currentText()) if propbool: operator2 = str(self.ui.addpredefop2_menu.currentText()) value2 = str(self.ui.addpredefval2_ef.text()) if operator2 and value2: compstr = '%s %s %s %s %s' % (operator, value, propbool, operator2, value2) criterion = ligfilter.PredefinedCriterion(predef, compstr) else: self.warning('Second criterion requires operator and value') return else: compstr = '%s %s' % (operator, value) criterion = ligfilter.PredefinedCriterion(predef, compstr) self.criteria.append(criterion) self.updateText()
[docs] def addPatternCriterion(self): pattern = str(self.ui.addpattern_ef.text()) if not pattern: self.warning('No pattern selected') return if pattern in [x.name for x in self.criteria]: self.warning('A criterion with this name already exists') return operator = str(self.ui.addpatternop1_menu.currentText()) value = str(self.ui.addpatternval1_ef.text()) if not operator or not value: self.warning('Missing operator or value') return # FIXME elif not self.ui.addpatternval1_ef.valid(): # self.warning('Value not valid') # return else: # A SMARTS Criterion now takes a Definition argument instead # of simply a name. Get the Definition from the default and # custom lists. matchd = None for d in ligfilter.default_definitions + self.patterns: if pattern == d.name: matchd = d break if matchd: propbool = str(self.ui.addpatternmult_menu.currentText()) if propbool: operator2 = str(self.ui.addpatternop2_menu.currentText()) value2 = str(self.ui.addpatternval2_ef.text()) if operator2 and value2: compstr = '%s %s %s %s %s' % (operator, value, propbool, operator2, value2) criterion = ligfilter.SmartsCriterion(matchd, compstr) else: self.warning( 'Second criterion requires operator and value') return else: compstr = '%s %s' % (operator, value) criterion = ligfilter.SmartsCriterion(matchd, compstr) else: self.warning("Nothing defined for '%s'" % pattern) return self.criteria.append(criterion) self.updateText()
[docs] def addPattern(self): """ Callback function for the "New" button. Pops up a dialog for construction the new definition, and incorporates the new (or redefined) pattern if the dialog returns one. """ self.addpattern_dialog = AddDefinitionDialog( self, ligfilter.default_definitions + self.patterns) newpattern = self.addpattern_dialog.display() if newpattern: # If the pattern is already defined, replace it with the new # pattern definition. for pattern in self.patterns: if newpattern.name == pattern.name: self.patterns.remove(pattern) break self.patterns.append(newpattern) self.setPatternOptions() self.updateText()
[docs] def importFilterFile(self): """ Read in an external filter file """ filterfile = filedialog.get_open_file_name( self.parent, "Select a LigFilter definitions/criteria file", "", # initial dir "LigFilter files (*.%s)" % ligfilter.FILTERFILE_EXT, ) if filterfile: # not canceled fh = open(str(filterfile)) self.readFilterCriteria(fh) fh.close()
[docs] def readFilterCriteria(self, fh): """ Reads the filter criteria from the specified open file handle or a list strings (lines in a filter file). """ patterns, criteria = ligfilter.read_keys(fh, validate=True) self.criteria = criteria self.patterns = patterns self.setPatternOptions() self.updateText()
[docs] def deleteEntry(self): """ Delete the selected criterion or pattern """ selected_rows = self.ui.filter_listview.selectionModel().selectedRows() if not selected_rows: self.warning('No pattern/criterion selected') return for index in selected_rows: rownum = index.row() line, linetype = self.filter_model.getRowCriteria(rownum) items = str(line).split() if linetype == "criterion": for x in self.criteria: if items[0] == x.name: self.criteria.remove(x) self.filter_model.removeRow(rownum) break elif linetype == "pattern": for x in self.patterns: if items[1] == x.name: self.patterns.remove(x) self.setPatternOptions() self.filter_model.removeRow(rownum) break
[docs] def setDefaults(self): self.criteria = [] self.properties = [] self.patterns = [] self.selectedprop = None self.ui.addprop_ef.setText("") self.ui.addpropop1_menu.setCurrentIndex(0) self.ui.addpropval1_ef.setText("") self.ui.addpropval2_ef.setText("") # Ev:132763 Do not clear the properties if this frame is instantiated # within the Ligand Filtering panel AND the structure source is the PT: if self._properties_resettable: self.setProperties(None) self.ui.addpredef_ef.setText("") self.ui.addpredefop1_menu.setCurrentIndex(0) self.ui.addpredefval1_ef.setText("") self.ui.addpredefval2_ef.setText("") self.ui.addpattern_ef.setText("") self.ui.addpatternop1_menu.setCurrentIndex(0) self.ui.addpatternval1_ef.setText("") self.ui.addpatternval2_ef.setText("") # Select the properties tab (if it's visible): if self.show_properties_tab: self.ui.tabWidget.setCurrentIndex(0) else: # Otherwise select the General attributes tab (LigPrep): self.ui.tabWidget.setCurrentIndex(1) self.ui.addpropmult_menu.setCurrentIndex(0) self.ui.addpredefmult_menu.setCurrentIndex(0) self.ui.addpatternmult_menu.setCurrentIndex(0) self.setPatternOptions() self.filter_model.resetData()
[docs] def setProperties(self, propertylist, resettable=True): """ Set the available CT-level properties. If this properties list should not be reset when the user presses the Reset button (See Ev:132763), set resettable to False. """ self.properties = propertylist if not self.properties: self.addprop_selector.setProperties([]) else: self.addprop_selector.setProperties(self.properties) self._properties_resettable = resettable
[docs] def getFilterText(self): """ Will return a string representation of the specified criteria, including any user-defined definitions. Returns a Python Unicode string. """ text = "" for line, linetype in self.filter_model._criteria: text = "%s%s\n" % (text, line) return text
[docs] def warning(self, text): QtWidgets.QMessageBox.warning(self, "Warning", text)
[docs] def setup(self, jobparam): """ Function passed to AppFramework that is called as part of the setup cascade when a job is started/written. Place all data in the jobparam object necessary for running the job. """ # The text of the filter file is all that is needed. All other job # parameters are handled by AppFramework itself. Checking of the # filter file contents will already have been done by the sanityCheck # call. jobparam.inputtext = self.getFilterText() return True
#EOF