Skip to content

Commit

Permalink
Merge pull request #721 from hhslepicka/rules_initial_value
Browse files Browse the repository at this point in the history
ENH: Add initial value to rules.
  • Loading branch information
hhslepicka authored Mar 24, 2021
2 parents 439c115 + dfc75dc commit 47a2f15
Show file tree
Hide file tree
Showing 7 changed files with 109 additions and 8 deletions.
Binary file modified docs/source/_static/widgets/widget_rules/rules_editor.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 10 additions & 1 deletion docs/source/widgets/widget_rules/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ Here is a step-by-step video on how to open the ``Rules Editor``.
Here is a screenshot of the ``Rules Editor`` screen in detail.

.. figure:: /_static/widgets/widget_rules/rules_editor.png
:scale: 100 %
:align: center
:alt: Rules Editor

Expand Down Expand Up @@ -70,6 +69,16 @@ clicking on the **Remove Rule** sign.

With the channel(s) added it is time to create the expression.

- **Initial Value**
The value set here will be sent to the widget upon instantiation of the Rule.
The value will be casted to the type expected the property.
Users can also use macros here and the macro value will be cast as other values.

.. Note::
Initial Value does not accept expressions. It is just a simple value to be
configured into the selected property before the channels connect and the
rule start being evaluated.

.. _Expression:

- **Expression**
Expand Down
40 changes: 40 additions & 0 deletions pydm/tests/widgets/test_rules.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,3 +174,43 @@ def test_rules_invalid_expr(qtbot, caplog):

dispatcher.unregister(widget)
assert weakref.ref(widget) not in re.widget_map


def test_rules_initial_value(qtbot, caplog):
"""
Test the rules initial value mechanism.
Parameters
----------
qtbot : fixture
Parent of all the widgets
caplog : fixture
To capture the log messages
"""
widget = PyDMLabel()
widget.setText("Defaut Label")
qtbot.addWidget(widget)
widget.show()

rules = [{'name': 'Rule #1', 'property': 'Text',
'expression': 'str(ch[0])',
'initial_value': 'Initial Value Test',
'channels': [{'channel': 'ca://MTEST:Float', 'trigger': True}]}]

dispatcher = RulesDispatcher()
dispatcher.register(widget, rules)

re = dispatcher.rules_engine
assert weakref.ref(widget) in re.widget_map
assert len(re.widget_map[weakref.ref(widget)]) == 1
assert re.widget_map[weakref.ref(widget)][0]['rule'] == rules[0]
assert widget.text() == 'Initial Value Test'
blocker = qtbot.waitSignal(re.rule_signal, timeout=1000)

re.callback_conn(weakref.ref(widget), 0, 0, value=True)
re.callback_value(weakref.ref(widget), 0, 0, trigger=True, value=5)
assert re.widget_map[weakref.ref(widget)][0]['calculate'] is True

blocker.wait()
assert re.widget_map[weakref.ref(widget)][0]['calculate'] is False
assert widget.text() == str(5)
8 changes: 8 additions & 0 deletions pydm/tests/widgets/test_rules_editor.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ def test_rules_editor(qtbot, monkeypatch):

# Create the rules data for the widget
rules_list = [{'name': 'Rule #1', 'property': 'Enable',
'initial_value': 'False',
'expression': 'ch[0] > 1',
'channels': [
{'channel': 'ca://MTEST:Float', 'trigger': True}]}]
Expand Down Expand Up @@ -89,6 +90,7 @@ def test_rules_editor(qtbot, monkeypatch):
assert re.tbl_channels.item(0, 1).checkState() == QtCore.Qt.Checked
assert re.lbl_expected_type.text() == 'bool'
assert re.txt_expression.text() == 'ch[0] > 1'
assert re.txt_initial_value.text() == 'False'

qtbot.keyClicks(re.txt_name, '-Test')
qtbot.keyClick(re.txt_name, QtCore.Qt.Key_Return)
Expand All @@ -106,6 +108,12 @@ def test_rules_editor(qtbot, monkeypatch):
assert re.txt_expression.text() == 'ch[0] < 1'
assert re.rules[0]['expression'] == 'ch[0] < 1'

re.txt_initial_value.clear()
qtbot.keyClicks(re.txt_initial_value, 'True')
qtbot.keyClick(re.txt_initial_value, QtCore.Qt.Key_Return)
assert re.txt_initial_value.text() == 'True'
assert re.rules[0]['initial_value'] == 'True'

# Test Delete with Confirm - NO
assert re.tbl_channels.rowCount() == 2
re.tbl_channels.setRangeSelected(QTableWidgetSelectionRange(1, 0, 1, 1), True)
Expand Down
16 changes: 13 additions & 3 deletions pydm/widgets/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,6 @@ def set_opacity(self, val):
self.setGraphicsEffect(op)
self.setAutoFillBackground(True)


@Slot(dict)
def rule_evaluated(self, payload):
"""
Expand All @@ -261,8 +260,19 @@ def rule_evaluated(self, payload):
return

method_name, data_type = self.RULE_PROPERTIES[prop]
method = getattr(self, method_name)
method(value)
try:
method = getattr(self, method_name)
if data_type == bool and isinstance(value, str_types):
# We do this as we already import json and for Python:
# bool("False") -> True
val = json.loads(value.lower())
else:
val = data_type(value)
method(val)
except:
logger.error('Error at Rule: %s. Could not execute method %s with '
'value %s and type as %s.',
name, method_name, value, data_type.__name__)

@Property(str, designable=False)
def rules(self):
Expand Down
30 changes: 26 additions & 4 deletions pydm/widgets/rules.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,10 @@ def register(self, widget, rules):

item = dict()
item['rule'] = rule
initial_val = rule.get('initial_value', "").strip()
name = rule.get('name')
prop = rule.get('property')
item['initial_value'] = initial_val
item['calculate'] = False
item['values'] = [None] * len(channels_list)
item['conn'] = [False] * len(channels_list)
Expand All @@ -183,6 +187,8 @@ def register(self, widget, rules):
value_slot=value_cb)
item['channels'].append(c)
rules_db.append(item)
if initial_val:
self.emit_value(widget_ref, name, prop, initial_val)

if rules_db:
self.widget_map[widget_ref] = rules_db
Expand Down Expand Up @@ -305,10 +311,26 @@ def calculate_expression(self, widget_ref, idx, rule):
expression = rule['rule']['expression']
name = rule['rule']['name']
prop = rule['rule']['property']

val = eval(expression, eval_env)
payload = {'widget': widget_ref, 'name': name, 'property': prop,
'value': val}
self.rule_signal.emit(payload)
self.emit_value(widget_ref, name, prop, val)
except Exception as e:
logger.exception("Error while evaluating Rule.")

def emit_value(self, widget_ref, name, prop, val):
"""
Emit the payload with the new value for the property.
Parameters
----------
widget_ref : weakref
A weakref to the widget owner of the rule.
name : str
The Rule name
prop : str
The Rule property
val : object
The value to emit
"""
payload = {'widget': widget_ref, 'name': name, 'property': prop,
'value': val}
self.rule_signal.emit(payload)
12 changes: 12 additions & 0 deletions pydm/widgets/rules_editor.py
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,11 @@ def setup_ui(self):
"color: rgb(0, 128, 255); font-weight: bold;")
expression_layout.addRow(lbl_expected, self.lbl_expected_type)

lbl_initial = QtWidgets.QLabel("Initial Value:")
self.txt_initial_value = QtWidgets.QLineEdit()
self.txt_initial_value.editingFinished.connect(self.initial_value_changed)
expression_layout.addRow(lbl_initial, self.txt_initial_value)

lbl_expression = QtWidgets.QLabel("Expression:")
expr_help_layout = QtWidgets.QHBoxLayout()
self.txt_expression = QtWidgets.QLineEdit()
Expand All @@ -209,6 +214,7 @@ def clear_form(self):
self.txt_name.setText("")
self.cmb_property.setCurrentIndex(-1)
self.tbl_channels.clearContents()
self.txt_initial_value.setText("")
self.txt_expression.setText("")
self.frm_edit.setEnabled(False)
self.loading_data = False
Expand All @@ -233,6 +239,7 @@ def load_from_list(self):
self.txt_name.setText(data.get('name', ''))
self.cmb_property.setCurrentText(data.get('property', ''))
self.property_changed(0)
self.txt_initial_value.setText(data.get('initial_value', ''))
self.txt_expression.setText(data.get('expression', ''))

channels = data.get('channels', [])
Expand All @@ -259,6 +266,7 @@ def add_rule(self):
default_name = "New Rule"
data = {"name": default_name,
"property": self.default_property,
"initial_value": "",
"expression": "",
"channels": []
}
Expand Down Expand Up @@ -423,6 +431,10 @@ def expression_changed(self):
"""Callback executed when the expression is modified."""
self.change_entry("expression", self.txt_expression.text())

def initial_value_changed(self):
"""Callback executed when the initial value is modified"""
self.change_entry("initial_value", self.txt_initial_value.text())

@staticmethod
def is_data_valid(rules):
"""
Expand Down

0 comments on commit 47a2f15

Please sign in to comment.