"""
This contains a QDialog class that allows the user to define criteria to filter
properties by.
It also contains classes that hold the criteria and determine if properties
match the criteria.
Copyright Schrodinger, LLC. All rights reserved.
"""
# Contributors: Jeff A. Saunders, Matvey Adzhigirey, David Giesen
# ToDo:
#
# Enable "Add" only when settings appropriate?
#
import schrodinger.ui.qt.propertyselector as propertyselector
from schrodinger import structure
from schrodinger.Qt import QtCore
from schrodinger.Qt import QtGui
from schrodinger.Qt import QtWidgets
from schrodinger.Qt.QtCore import Qt
from schrodinger.ui.qt import swidgets
# Import the module(s) created from the QtDesigner *.ui file(s):
from . import propfilter_ui
#
# Global constants
#
TEXTBOX_FONT = "Courier New"
EQUALS = '='
NOT_EQUALS = 'Not ='
CONTAINS = 'Contains'
STARTSWITH = 'Starts with'
ENDSWITH = 'Ends with'
NOT_CONTAINS = 'Does not contain'
EXISTS = 'Exists'
NOT_EXISTS = 'Does not exist'
SELECT = 'Select'
GREATEREQUAL = '>='
LESSEREQUAL = '<='
GREATER = '>'
LESSER = '<'
IS_TRUE = 'is True'
IS_FALSE = 'is False'
AND = 'AND'
OR = 'OR'
STRING_OPS = [
EQUALS, NOT_EQUALS, CONTAINS, NOT_CONTAINS, STARTSWITH, ENDSWITH, EXISTS,
NOT_EXISTS, SELECT
]
NUMBER_OPS = [
EQUALS, NOT_EQUALS, GREATEREQUAL, LESSEREQUAL, GREATER, LESSER, EXISTS,
NOT_EXISTS, SELECT
]
BOOL_OPS = [IS_TRUE, IS_FALSE, EXISTS, NOT_EXISTS]
MUST_HAVE_VALUE = set([
EQUALS, NOT_EQUALS, CONTAINS, NOT_CONTAINS, STARTSWITH, ENDSWITH,
GREATEREQUAL, LESSEREQUAL, GREATER, LESSER
])
[docs]class StringDatabaseCriterion(object):
"""
Holds a criterion for a string property and determines whether a database
object or structure meets that criterion
"""
[docs] def __init__(self, dataname, username, criteria, joiner=""):
"""
Create a StringDatabaseCriterion object
:type dataname: str
:param dataname: The internal name of the property this criterion
checks. The dataname is of the form x_y_z.
:type username: str
:param username: The name the user sees for this property
:type criteria: list of tuple
:param criteria: List of one or two criterion. Each criterion is a
tuple consisting of (operator, value). operator is some
relationship defined by the constants in this module, and value is
the target value. For instance, in property > 5, '>' is the
operator and 5 is the value.
Operator constants are:
- EQUALS
- NOT_EQUALS
- CONTAINS
- STARTSWITH
- ENDSWITH
- NOT_CONTAINS
- EXISTS
- NOT_EXISTS
- SELECT
- GREATEREQUAL
- LESSEREQUAL
- GREATER
- LESSER
- IS_TRUE
- IS_FALSE
For the SELECT operator, value can be a list of values. A property that
matches any one of those values exactly matches the criterion.
:type joiner: str
:param joiner: If two criteria are provided, joiner is the boolean that
connects them - either AND or OR. Default is the empty string,
which means that only the first criterion in the criteria list will
be examined.
"""
self.property = dataname
self.criteria = criteria
self.joiner = joiner
# Create the string that represents this criterion
self.string = username.replace(' ', '_')
self.string = self.string + ' ' + criteria[0][0].lower()
if criteria[0][1] is not None:
self.string = self.string + ' ' + str(criteria[0][1])
if joiner:
self.string = self.string + ' ' + joiner
self.string = self.string + ' ' + criteria[1][0].lower()
if criteria[1][1] is not None:
self.string = self.string + ' ' + str(criteria[1][1])
def __str__(self):
return self.string
[docs] def matches(self,
dbobj=None,
struct=None,
stem='bioluminate',
isCaseSensitive=False):
"""
Checks to see if a
`schrodinger.application.prime.packages.PrimeStructureDatabase.PrimeStructureDBEntry` database
object or a `schrodinger.structure.Structure` object matches this
Criterion.
:type dbobj:
`schrodinger.application.prime.package.PrimeStructureDatabase.PrimeStructureDBEntry`
:param dbobj: The database object to check for a match. Only dbobj or
struct should be provided, but not both. If both are provided, the
struct object is checked and the dbobj is ignored.
:type struct: `schrodinger.structure.Structure`
:param struct: The Structure object to check for a match.
Only dbobj or struct should be provided, but not both. If both are
provided, the struct object is checked and the dbobj is ignored.
:type stem: str
:param stem: If given, this value will be inserted at the beginning of
the second part of the Criterion property name when checking a struct
object. Property x_y_z will become x_stemy_z. Default is
'bioluminate'. This is done because the database removes stem from
property names when storing data.
:type isCaseSensitive: bool
:rtype: bool
:return: True if the provided object passes this Criterion, False if
not.
"""
if not self.criteria:
# An empty Criterion matches anything
return True
if struct:
propname = self.property[:2] + stem + self.property[2:]
try:
obj_val = struct.property[propname]
except KeyError:
obj_val = None
else:
try:
obj_val = dbobj.data[self.property]
except KeyError:
obj_val = None
results = []
for operator, goal in self.criteria:
results.append(
self.checkForMatch(obj_val, operator, goal, isCaseSensitive))
if self.joiner:
if self.joiner == AND:
return all(results)
else:
return any(results)
else:
return results[0]
[docs]class NumericalDatabaseCriterion(StringDatabaseCriterion):
[docs]class BooleanDatabaseCriterion(StringDatabaseCriterion):
[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 clear(self):
self.beginResetModel()
self.criteria = []
self.endResetModel()
[docs] def addLine(self, line, linetype=None):
row_idx = self.rowCount()
self.beginInsertRows(QtCore.QModelIndex(), row_idx, row_idx)
self.criteria.append([line, linetype])
self.endInsertRows()
[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
return line
[docs] def flags(self, index):
"""
Returns flags for the specified cell. Whether selectable or not.
"""
if not index.isValid():
return 0
try:
line, linetype = self.criteria[index.row()]
except IndexError:
return Qt.ItemIsEnabled
if linetype:
return Qt.ItemIsSelectable | Qt.ItemIsEnabled
else:
return Qt.ItemIsEnabled
[docs]class PropertySelectorWithRanges(propertyselector.PropertySelector):
"""
A PropertySelector class that appends ranges to the names of numeric
properties.
"""
[docs] def __init__(self, master, *args, **kwargs):
"""
Create a PropertySelectorWithRanges instance.
:type master: object with getPropertyMinMax method
:param master: The master panel that supplies the min and max values of
numerical properties for the range display
"""
propertyselector.PropertySelector.__init__(self, *args, **kwargs)
self.master = master
[docs] def checkUserNames(self):
"""
Assigns PropertyName.user_name attributes, for all members
in self.proplist. The user_names are unique within the
PropertySelector instance.
The user_name strings are typically just the
PropertyName.userName(), but may be modified with a numerical
suffix to make them unique, or decorated with the family name
(which does not garuntee uniqeness, but may make the items easier
for the end user to visually group).
"""
# Assign the user_name attributes.
assigned_user_names = []
for property_name in self.proplist:
orig_name = property_name.userName()
if self.show_family_name:
fam = property_name.family
orig_name = "%s (%s)" % (orig_name,
structure.PROP_LONG_NAME.get(fam, fam))
user_name = orig_name
if user_name in assigned_user_names:
attempt = 1
while user_name in assigned_user_names:
user_name = "%s-%d" % (orig_name, attempt)
attempt += 1
property_name.user_name = user_name
assigned_user_names.append(property_name.user_name)
[docs]class ValueSelectionDialog(QtWidgets.QDialog):
"""
A SFilteredListWidget that allows the user to select certain values of a
property for the criterion.
"""
[docs] def __init__(self, parent, values, propmenu, value_edit):
"""
Create a ValueSelectionDialog instance
:type parent: QWidget with setSelectedValues method
:param parent: The parent widget of this dialog. The accept method of
this dialog calls the setSelectedValues method of the parent with the
list of selected text in the QListWidget
:type values: list
:param values: List of values to display in the QListWidget
:type propmenu: QComboBox or object with setCurrentIndex method
:param propmenu: The cancel method of this dialog sets
propmenu.setCurrentIndex(0)
:param value_edit: value_edit is passed to parent.setSelectedValues in
the accept method of the dialog
"""
QtWidgets.QDialog.__init__(self, parent)
layout = swidgets.SVBoxLayout(self)
items = [str(x) for x in values]
self.listwidget = SelectingFilteredListWidget(
layout=layout,
mode='extended',
items=items,
label='Select values to accept')
self.propmenu = propmenu
self.value_edit = value_edit
# Bottom buttons
dbb = QtWidgets.QDialogButtonBox
dialog_buttons = dbb(dbb.Ok | dbb.Cancel)
dialog_buttons.accepted.connect(self.accept)
dialog_buttons.rejected.connect(self.reject)
layout.addWidget(dialog_buttons)
[docs] def accept(self):
"""
Actions when the user presses the accept button. Calls
parent.setSelectedValues(values, value_edit) where values is a list of
selected values and value_edit is passed to the __init__ method
"""
self.parent().setSelectedValues(self.listwidget.selectedText(),
self.value_edit)
return QtWidgets.QDialog.accept(self)
[docs] def reject(self):
"""
Actions when the user presses the cancel button. Calls
propmenu.setCurrentIndex(0) on the propmenu passed to the __init__
function.
"""
self.propmenu.setCurrentIndex(0)
return QtWidgets.QDialog.reject(self)
[docs]class PropFilterDialog(QtWidgets.QDialog):
"""
Panel to allow the user to define criteria for filtering property values
"""
[docs] def __init__(self, parent, criteria=None, databases=None):
"""
Create a PropFilterDialog instance
:type parent: QWidget or object with setCriteria method
:param parent: The accept method of this dialog calls the setCriteria
method of the parent with a list of `StringDatabaseCriterion`,
`NumericalDatabaseCriterion` and `BooleanDatabaseCriterion` objects
defined by the user
:type criteria: list
:param criteria: A list of existing `StringDatabaseCriterion`,
`NumericalDatabaseCriterion` and `BooleanDatabaseCriterion` objects
that will display and can be deleted in this panel
:type database:
`schrodinger.application.prime.packages.PrimeStructureDatabase.PrimeStructureDB`
:param database: The database to open during panel creation. If no
database is provided, either a database will have to be provided with
the `loadDatabase` method or properties passed in via the
`setProperties` method before displaying the panel.
"""
QtWidgets.QDialog.__init__(self, parent)
# Set up GUI widgets and hook up signals
self.ui = propfilter_ui.Ui_Form()
self.ui.setupUi(self)
self.prop_selector = PropertySelectorWithRanges(
self, self.ui.propselector_widget)
self.prop_selector.listbox.itemSelectionChanged.connect(
self.selectProperty)
self.ui.prop_edit.textChanged[str].connect(self.checkPropertyType)
self.ui.operator1_combo.currentTextChanged.connect(
self.operator1Changed)
self.ui.operator2_combo.currentTextChanged.connect(
self.operater2Changed)
self.ui.joiner_combo.addItems(("", AND, OR))
self.ui.joiner_combo.currentTextChanged.connect(self.joinerChanged)
self.ui.addcriterion_btn.clicked.connect(self.addCriterion)
self.ui.addcriterion_btn.setAutoDefault(True)
self.ui.addcriterion_btn.setDefault(True)
#
# Filtering information for criteria display
#
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))
# Delete
self.ui.delete_button.clicked.connect(self.deleteEntry)
self.filterSelectionChanged()
# Bottom buttons
dbb = QtWidgets.QDialogButtonBox
dialog_buttons = dbb(dbb.Ok | dbb.Cancel)
dialog_buttons.accepted.connect(self.accept)
dialog_buttons.rejected.connect(self.reject)
self.ui.main_vlayout.addWidget(dialog_buttons)
# Give the panel a decent size
size = self.size()
size.setWidth(800)
self.resize(size)
self.databases = []
self.criteria = []
self.setDefaults(criteria=criteria)
if databases:
self.loadDatabases(databases)
self.updateText()
[docs] def getCumulativeMatches(self):
if not self.databases:
return [0 * len(self.criteria)]
cumulative_matches = []
for database in self.databases:
entries = database.SelectEntries()
for crit in self.criteria:
match = []
for entry in entries:
if crit.matches(
dbobj=entry,
isCaseSensitive=self.ui.checkBox.isChecked()):
match.append(entry)
cumulative_matches.append(len(match))
entries = match[:]
return cumulative_matches
[docs] def accept(self):
"""
Add the selected criteria to the parent - calls parent.setCriteria()
with a list of `StringDatabaseCriterion`,
`NumericalDatabaseCriterion` and `BooleanDatabaseCriterion` objects
"""
self.parent().setCriteria(self.criteria)
return QtWidgets.QDialog.accept(self)
[docs] def filterSelectionChanged(self, selected=None, deselected=None):
"""
Enables or disables the Delete button as appropriate depending on the
selection in the Criteria display area
:param selected: Unused
:param deselected: Unused
"""
# Determine if any of the selected lines are Criterion lines
rows_selected = False
selected_rows = self.ui.filter_listview.selectionModel().selectedRows()
for index in selected_rows:
rownum = index.row()
line, linetype = self.filter_model.criteria[rownum]
if linetype:
rows_selected = True
break
self.ui.delete_button.setEnabled(rows_selected)
[docs] def selectProperty(self):
"""
Populate the property name edit with the property name of the property
the user just selected.
"""
selected_props = self.prop_selector.getSelected()
if selected_props:
self.ui.prop_edit.setText(selected_props[0].userName())
[docs] def joinerChanged(self, result):
"""
Enable/disable the second criteria widgets as appropriate for the joiner
("", AND, OR) that the user just selected.
:type result: str
:param result: The current value selected in the joiner combobox
"""
is_joiner = bool(result)
self.ui.operator2_combo.setEnabled(is_joiner)
self.ui.value2_edit.setEnabled(is_joiner)
[docs] def updateText(self):
"""
Rewrite the text in the criteria text box
"""
self.filter_model.clear()
# Tag some of the lines so they can be selected by the special
# mouse bindings (in selectTextLine).
if self.criteria:
self.filter_model.addLine('#\n# Filter criteria\n#')
cumulative = self.getCumulativeMatches()
for criterion in self.criteria:
self.filter_model.addLine(str(criterion), "criterion")
self.filter_model.addLine(' Cumulative matches = %d' %
cumulative.pop(0))
[docs] def loadDatabases(self, databases):
"""
Load in the data from the database - populates the upper text box with
property names. Adds Misc, Light, or Heavy to property names so the
user can filter them by class.
:type database:
`schrodinger.application.prime.packages.PrimeStructureDatabase.PrimeStructureDB`
:param database: The database to load data from
"""
self.databases = databases
# Fixes PANEL-1965: We only consider 'common' properties, which are
# found in all databases.
props = self.findCommonDatanames(databases)
propertynames = []
# Add the Light, Heavy or Misc class to the property name by making the
# second block of the property name one of those strings. This fakes
# the property display into showing and filtering on these classes.
for prop in props:
tokens = prop.split('_')
if tokens[0] not in ('i', 'r', 's', 'b'):
continue
t2_lower = tokens[2].lower()
tokens[1] = self.convertToClassToken(t2_lower)
classed_name = '_'.join(tokens)
propname = structure.PropertyName(dataname=classed_name)
# Add a property to the PropertyName object that is the actual
# database property name
propname.actual_dataname = prop
propertynames.append(propname)
def propsort(property):
return property.userName()
propertynames.sort(key=propsort)
self.setProperties(propertynames)
[docs] def convertToClassToken(self, s):
"""
This function converts given string to one of class tokens
(either 'Light', 'Heavy' or 'Misc'). Valid 'light' and 'heavy'
strings are defined in light and heavy variables respectively.
:param s: property class string
:type s: str
:return: class token
:rtype: str
"""
light = ['light', 'l1', 'l2', 'l3']
heavy = ['heavy', 'h1', 'h2', 'h3']
token = None
for val in light:
if s.startswith(val):
token = 'Light'
break
if not token:
for val in heavy:
if s.startswith(val):
token = 'Heavy'
break
if not token:
token = 'Misc'
return token
[docs] def findCommonDatanames(self, databases):
"""
This function find column names that are present in all databases.
:param databases: List of the databases to load data from.
:type databases: list
:return: list of common column names
:rtype: list
"""
propnames = [db.GetColumns() for db in databases]
if propnames:
common_props = list(set(propnames[0]).intersection(*propnames[1:]))
else:
common_props = []
return common_props
[docs] def addCriterion(self):
"""
Adds a criterion when the user hits the Add button
"""
# Validity and type checks
prop = str(self.ui.prop_edit.text())
if not prop:
self.warning('No property name specified')
return
try:
propname = self.propertyname_objs[prop.lower()]
except KeyError:
self.warning('Unrecognized property name specified')
return
is_string = propname.type == structure.PROP_STRING
is_int = propname.type == structure.PROP_INTEGER
is_float = propname.type == structure.PROP_FLOAT
is_bool = propname.type == structure.PROP_BOOLEAN
# Extract the data from the widgets
def convert_val(value):
if value == "":
return None
if is_bool:
return bool(value)
elif is_int:
return int(value)
elif is_float:
return float(value)
else:
return value
# First criterion
operator = str(self.ui.operator1_combo.currentText())
if operator != SELECT:
value = convert_val(self.ui.value1_edit.text())
else:
value = self.ui.value1_edit.selected_values
if value is None and operator in MUST_HAVE_VALUE:
self.warning('A value is required for this criterion')
return
joiner = str(self.ui.joiner_combo.currentText())
crits = [(operator, value)]
# User selected AND/OR, so grab the second criterion
if joiner:
value2 = convert_val(self.ui.value2_edit.text())
operator2 = str(self.ui.operator2_combo.currentText())
if not operator2:
self.warning('AND/OR specified but no second criterion')
return
if value2 is None and operator2 in MUST_HAVE_VALUE:
self.warning('A value is required for the second criterion')
return
crits.append((operator2, value2))
# Create the Criterion object
try:
property_name = propname.actual_dataname
except AttributeError:
property_name = propname.dataName()
if is_int or is_float:
# Using userName() rather than prop makes sure the capitalization is
# correct
criterion = NumericalDatabaseCriterion(property_name,
propname.userName(),
crits,
joiner=joiner)
elif is_bool:
criterion = BooleanDatabaseCriterion(property_name,
propname.userName(),
crits,
joiner=joiner)
else:
criterion = StringDatabaseCriterion(property_name,
propname.userName(),
crits,
joiner=joiner)
self.criteria.append(criterion)
self.updateText()
self.ui.prop_edit.setFocus()
self.ui.prop_edit.selectAll()
[docs] def operator1Changed(self, result):
"""
Disable/enable the value edit as appropriate for the selected operator.
Opens a `ValueSelectionDialog` if the operator is SELECT.
:type result: str
:param result: The current value in the 1st operator combobox
"""
result = str(result)
if result not in MUST_HAVE_VALUE:
# Operators without values.
self.ui.value1_edit.clear()
self.ui.value1_edit.setReadOnly(True)
else:
self.ui.value1_edit.setReadOnly(False)
if result == SELECT:
# Open up a filtered listwidget to let the user choose values
self.letUserSelectValues(self.ui.operator1_combo,
self.ui.value1_edit)
[docs] def operater2Changed(self, result):
"""
Disable/enable the value edit as appropriate for the selected operator.
Opens a `ValueSelectionDialog` if the operator is SELECT.
:type result: str
:param result: The current value in the 2nd operator combobox
"""
result = str(result)
if result not in MUST_HAVE_VALUE:
# Operators without values.
self.ui.value2_edit.clear()
self.ui.value2_edit.setReadOnly(True)
else:
self.ui.value2_edit.setReadOnly(False)
if result == SELECT:
# Open up a filtered listwidget to let the user choose values
self.letUserSelectValues(self.ui.operator2_combo,
self.ui.value2_edit)
[docs] def letUserSelectValues(self, propmenu, value_edit):
"""
Open a `ValueSelectionDialog` to allow the user to select values of the
property that will match the criterion.
:type propmenu: QComboBox or object with setCurrentIndex method
:param propmenu: The cancel method of the dialog sets
propmenu.setCurrentIndex(0)
:param value_edit: value_edit is passed to `setSelectedValues` in
the accept method of the dialog
"""
prop = str(self.ui.prop_edit.text())
# Check for valid property name
if not prop:
self.warning('No property name specified, cannot select values')
propmenu.setCurrentIndex(0)
return
try:
propname = self.propertyname_objs[prop.lower()]
except KeyError:
self.warning('Unrecognized property name specified,'
'cannot select values')
propmenu.setCurrentIndex(0)
return
values = []
for database in self.databases:
try:
db_values = database.GetValues(propname.actual_dataname)
except AttributeError:
# actual_dataname may not be set if properties were passed in by a
# method other than loadDatabase - just use the regular dataName()
db_values = database.GetValues(propname.dataName())
values.extend(db_values)
value_edit.selected_values = []
select_dialog = ValueSelectionDialog(self, values, propmenu, value_edit)
select_dialog.exec()
[docs] def setSelectedValues(self, values, value_edit):
"""
Called by `ValueSelectionDialog` to set the text in the proper
value_edit to the values the user selected.
:type values: list
:param values: list of values selected by the user
:type value_edit: QLineEdit
:param value_edit: The edit that values should be placed in
"""
valstring = ','.join(values)
value_edit.setText(valstring)
value_edit.selected_values = values
[docs] def checkPropertyType(self, prop):
"""
Callback for modifications to the property QLineEdit. Adjust the
operator list and value validator according to the property type
entered.
:type prop: str
:param prop: The current value of the property QLineEdit
"""
prop = str(prop)
try:
propname = self.propertyname_objs[prop.lower()]
except KeyError:
# Probably in mid edit
return
if propname.type == structure.PROP_STRING:
# Restrict to "Exists", because the backend doesn't support string
# comparisons.
operators = STRING_OPS
self.ui.value1_edit.setValidator(None)
self.ui.value2_edit.setValidator(None)
elif propname.type == structure.PROP_INTEGER:
operators = NUMBER_OPS
self.ui.value1_edit.setValidator(QtGui.QIntValidator(self))
self.ui.value2_edit.setValidator(QtGui.QIntValidator(self))
elif propname.type == structure.PROP_FLOAT:
operators = NUMBER_OPS
self.ui.value1_edit.setValidator(QtGui.QDoubleValidator(self))
self.ui.value2_edit.setValidator(QtGui.QDoubleValidator(self))
else:
operators = BOOL_OPS
# No need to set validators, no value entry allowed for booleans
self.ui.operator1_combo.clear()
self.ui.operator2_combo.clear()
self.ui.operator1_combo.addItems(operators)
self.ui.operator2_combo.addItems(operators)
[docs] def deleteEntry(self):
"""
Delete the selected criterion or pattern
"""
selected_rows = self.ui.filter_listview.selectionModel().selectedRows()
if not selected_rows:
# Shouldn't happen, but just in case...
self.warning('No criterion selected')
return
for index in selected_rows:
rownum = index.row()
line, linetype = self.filter_model.criteria[rownum]
line = str(line)
if linetype == "criterion":
for crit in self.criteria:
if line == crit.string:
self.criteria.remove(crit)
self.updateText()
break
[docs] def setDefaults(self, criteria=None):
"""
Set the panel defaults
:type criteria: list
:param criteria: Pre-existing `StringDatabaseCriterion`,
`NumericalDatabaseCriterion` and `BooleanDatabaseCriterion` objects
that should be added the criterion area
"""
if criteria:
self.criteria = criteria
else:
self.criteria = []
self.propertyname_objs = {}
self.selectedprop = None
self.ui.prop_edit.setText("")
self.ui.operator1_combo.clear()
self.ui.operator1_combo.addItems(STRING_OPS)
self.ui.value1_edit.setText("")
self.ui.value2_edit.setText("")
self.setProperties(None)
self.ui.joiner_combo.setCurrentIndex(0)
self.ui.operator2_combo.clear()
self.ui.operator2_combo.addItems(STRING_OPS)
[docs] def setProperties(self, property_names):
"""
Set the list of properties the user can choose from.
:type property_names: list of `schrodinger.structure.PropertyName`
:param property_names: Each item of the list is a
`schrodinger.structure.PropertyName` object for a property that the
user can select from
"""
self.propertyname_objs = {}
if not property_names:
self.prop_selector.setProperties([])
else:
self.prop_selector.setProperties(property_names)
usernames = []
# Create a completer that will complete property names as the user
# types them into the property name field
for pname in property_names:
uname = pname.userName()
usernames.append(uname)
self.propertyname_objs[uname.lower()] = pname
completer = QtWidgets.QCompleter(usernames)
completer.setCaseSensitivity(Qt.CaseInsensitive)
self.ui.prop_edit.setCompleter(completer)
[docs] def warning(self, text):
"""
Pop up a warning dialog
:type text: str
:param text: The text to display in the dialog
"""
QtWidgets.QMessageBox.warning(self, "Warning", text)
[docs] def getPropertyMinMax(self, property):
"""
Get the min and max values of property from the database.
:type property: str
:param property: The name of a database property to get the min and max
of
:rtype: list
:return: [min, max] of property. Returns [None, None] if the database is
not defined.
"""
# NOTE: This method is currently not used; see BIOLUM-2661
min_value = None
max_value = None
for database in self.databases:
new_min, new_max = database.GetRange(property)
if min_value is None:
min_value = new_min
if max_value is None:
max_value = new_max
if new_min < min_value:
min_value = new_min
if new_max > max_value:
max_value = new_max
return [min_value, max_value]
if __name__ == '__main__':
from schrodinger.application.prime.packages import \
PrimeStructureDatabase as psd
app = QtWidgets.QApplication([])
panel = PropFilterDialog(None)
database = psd.PrimeStructureDB('testdb.db')
panel.loadDatabase(database)
panel.exec()