Skip to content

Commit 60c5a72

Browse files
committed
Merge branch 'master' into shell_cmd_update
2 parents 34c3820 + 5aaf9b9 commit 60c5a72

31 files changed

+531
-201
lines changed

examples/datetime/datetime.ui

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
<rect>
77
<x>0</x>
88
<y>0</y>
9-
<width>208</width>
9+
<width>240</width>
1010
<height>149</height>
1111
</rect>
1212
</property>

pydm/data_plugins/plugin.py

Lines changed: 93 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import numpy as np
33
import weakref
44
import threading
5+
import warnings
56

67
from typing import Optional, Callable
78
from urllib.parse import ParseResult
@@ -105,103 +106,106 @@ def remove_listener(self, channel, destroying: Optional[bool] = False) -> None:
105106
QObject is destroyed, setting this to True ensures we do not try to do the disconnection a second time.
106107
If set to False, any active signals/slots on the channel will be manually disconnected here.
107108
"""
108-
if self._should_disconnect(channel.connection_slot, destroying):
109-
try:
110-
self.connection_state_signal.disconnect(channel.connection_slot)
111-
except TypeError:
112-
pass
109+
with warnings.catch_warnings():
110+
warnings.simplefilter("ignore", category=RuntimeWarning)
113111

114-
if self._should_disconnect(channel.value_slot, destroying):
115-
for signal_type in (int, float, str, bool, object):
112+
if self._should_disconnect(channel.connection_slot, destroying):
116113
try:
117-
self.new_value_signal[signal_type].disconnect(channel.value_slot)
118-
# If the signal exists (always does in this case since we define it for all 'signal_type' earlier)
119-
# but doesn't match slot, TypeError is thrown. We also don't need to catch KeyError/IndexError here,
120-
# since those are only thrown when signal type doesn't exist.
114+
self.connection_state_signal.disconnect(channel.connection_slot)
121115
except TypeError:
122116
pass
123117

124-
if self._should_disconnect(channel.severity_slot, destroying):
125-
try:
126-
self.new_severity_signal.disconnect(channel.severity_slot)
127-
except (KeyError, TypeError):
128-
pass
129-
130-
if self._should_disconnect(channel.write_access_slot, destroying):
131-
try:
132-
self.write_access_signal.disconnect(channel.write_access_slot)
133-
except (KeyError, TypeError):
134-
pass
135-
136-
if self._should_disconnect(channel.enum_strings_slot, destroying):
137-
try:
138-
self.enum_strings_signal.disconnect(channel.enum_strings_slot)
139-
except (KeyError, TypeError):
140-
pass
141-
142-
if self._should_disconnect(channel.unit_slot, destroying):
143-
try:
144-
self.unit_signal.disconnect(channel.unit_slot)
145-
except (KeyError, TypeError):
146-
pass
147-
148-
if self._should_disconnect(channel.upper_ctrl_limit_slot, destroying):
149-
try:
150-
self.upper_ctrl_limit_signal.disconnect(channel.upper_ctrl_limit_slot)
151-
except (KeyError, TypeError):
152-
pass
153-
154-
if self._should_disconnect(channel.lower_ctrl_limit_slot, destroying):
155-
try:
156-
self.lower_ctrl_limit_signal.disconnect(channel.lower_ctrl_limit_slot)
157-
except (KeyError, TypeError):
158-
pass
159-
160-
if self._should_disconnect(channel.upper_alarm_limit_slot, destroying):
161-
try:
162-
self.upper_alarm_limit_signal.disconnect(channel.upper_alarm_limit_slot)
163-
except (KeyError, TypeError):
164-
pass
165-
166-
if self._should_disconnect(channel.lower_alarm_limit_slot, destroying):
167-
try:
168-
self.lower_alarm_limit_signal.disconnect(channel.lower_alarm_limit_slot)
169-
except (KeyError, TypeError):
170-
pass
171-
172-
if self._should_disconnect(channel.upper_warning_limit_slot, destroying):
173-
try:
174-
self.upper_warning_limit_signal.disconnect(channel.upper_warning_limit_slot)
175-
except (KeyError, TypeError):
176-
pass
177-
178-
if self._should_disconnect(channel.lower_warning_limit_slot, destroying):
179-
try:
180-
self.lower_warning_limit_signal.disconnect(channel.lower_warning_limit_slot)
181-
except (KeyError, TypeError):
182-
pass
183-
184-
if self._should_disconnect(channel.prec_slot, destroying):
185-
try:
186-
self.prec_signal.disconnect(channel.prec_slot)
187-
except (KeyError, TypeError):
188-
pass
189-
190-
if self._should_disconnect(channel.timestamp_slot, destroying):
191-
try:
192-
self.timestamp_signal.disconnect(channel.timestamp_slot)
193-
except (KeyError, TypeError):
194-
pass
195-
196-
if not destroying and channel.value_signal is not None and hasattr(self, "put_value"):
197-
for signal_type in (str, int, float, np.ndarray, dict):
118+
if self._should_disconnect(channel.value_slot, destroying):
119+
for signal_type in (int, float, str, bool, object):
120+
try:
121+
self.new_value_signal[signal_type].disconnect(channel.value_slot)
122+
# If the signal exists (always does in this case since we define it for all 'signal_type' earlier)
123+
# but doesn't match slot, TypeError is thrown. We also don't need to catch KeyError/IndexError here,
124+
# since those are only thrown when signal type doesn't exist.
125+
except TypeError:
126+
pass
127+
128+
if self._should_disconnect(channel.severity_slot, destroying):
129+
try:
130+
self.new_severity_signal.disconnect(channel.severity_slot)
131+
except (KeyError, TypeError):
132+
pass
133+
134+
if self._should_disconnect(channel.write_access_slot, destroying):
198135
try:
199-
channel.value_signal[signal_type].disconnect(self.put_value)
200-
# When signal type can't be found, PyQt5 throws KeyError here, but PySide6 index error.
201-
# If signal type exists but doesn't match the slot, TypeError gets thrown.
202-
except (KeyError, IndexError, TypeError):
136+
self.write_access_signal.disconnect(channel.write_access_slot)
137+
except (KeyError, TypeError):
203138
pass
204139

140+
if self._should_disconnect(channel.enum_strings_slot, destroying):
141+
try:
142+
self.enum_strings_signal.disconnect(channel.enum_strings_slot)
143+
except (KeyError, TypeError):
144+
pass
145+
146+
if self._should_disconnect(channel.unit_slot, destroying):
147+
try:
148+
self.unit_signal.disconnect(channel.unit_slot)
149+
except (KeyError, TypeError):
150+
pass
151+
152+
if self._should_disconnect(channel.upper_ctrl_limit_slot, destroying):
153+
try:
154+
self.upper_ctrl_limit_signal.disconnect(channel.upper_ctrl_limit_slot)
155+
except (KeyError, TypeError):
156+
pass
157+
158+
if self._should_disconnect(channel.lower_ctrl_limit_slot, destroying):
159+
try:
160+
self.lower_ctrl_limit_signal.disconnect(channel.lower_ctrl_limit_slot)
161+
except (KeyError, TypeError):
162+
pass
163+
164+
if self._should_disconnect(channel.upper_alarm_limit_slot, destroying):
165+
try:
166+
self.upper_alarm_limit_signal.disconnect(channel.upper_alarm_limit_slot)
167+
except (KeyError, TypeError):
168+
pass
169+
170+
if self._should_disconnect(channel.lower_alarm_limit_slot, destroying):
171+
try:
172+
self.lower_alarm_limit_signal.disconnect(channel.lower_alarm_limit_slot)
173+
except (KeyError, TypeError):
174+
pass
175+
176+
if self._should_disconnect(channel.upper_warning_limit_slot, destroying):
177+
try:
178+
self.upper_warning_limit_signal.disconnect(channel.upper_warning_limit_slot)
179+
except (KeyError, TypeError):
180+
pass
181+
182+
if self._should_disconnect(channel.lower_warning_limit_slot, destroying):
183+
try:
184+
self.lower_warning_limit_signal.disconnect(channel.lower_warning_limit_slot)
185+
except (KeyError, TypeError):
186+
pass
187+
188+
if self._should_disconnect(channel.prec_slot, destroying):
189+
try:
190+
self.prec_signal.disconnect(channel.prec_slot)
191+
except (KeyError, TypeError):
192+
pass
193+
194+
if self._should_disconnect(channel.timestamp_slot, destroying):
195+
try:
196+
self.timestamp_signal.disconnect(channel.timestamp_slot)
197+
except (KeyError, TypeError):
198+
pass
199+
200+
if not destroying and channel.value_signal is not None and hasattr(self, "put_value"):
201+
for signal_type in (str, int, float, np.ndarray, dict):
202+
try:
203+
channel.value_signal[signal_type].disconnect(self.put_value)
204+
# When signal type can't be found, PyQt5 throws KeyError here, but PySide6 index error.
205+
# If signal type exists but doesn't match the slot, TypeError gets thrown.
206+
except (KeyError, IndexError, TypeError):
207+
pass
208+
205209
self.listener_count = self.listener_count - 1
206210
if self.listener_count < 1:
207211
self.close()

pydm/main_window.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -377,10 +377,9 @@ def change_stylesheet_action(self, checked):
377377
self, "Change Stylesheet...", folder, "PyDM Stylesheets (*.qss *.css)"
378378
)
379379

380-
if ss_filename:
381-
ss_filename = str(ss_filename)
382-
self.ss_path = ss_filename.split("'")
383-
style = open(self.ss_path[1], "r")
380+
ss_path = str(ss_filename[0])
381+
if ss_path:
382+
style = open(ss_path, "r")
384383
style = style.read()
385384
self.setStyleSheet(style)
386385

pydm/tests/widgets/test_base.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,12 @@ def mock_exec_(*args):
218218
monkeypatch.setattr(QMenu, "exec_", mock_exec_)
219219

220220
mouse_event = QMouseEvent(
221-
QMouseEvent.MouseButtonRelease, pydm_label.rect().center(), Qt.RightButton, Qt.RightButton, Qt.ShiftModifier
221+
QMouseEvent.MouseButtonRelease,
222+
pydm_label.rect().center(), # localPos
223+
pydm_label.rect().center(), # globalPos
224+
Qt.RightButton, # button
225+
Qt.RightButton, # buttons
226+
Qt.ShiftModifier, # modifiers
222227
)
223228
pydm_label.open_context_menu(mouse_event)
224229
assert "Context Menu displayed." in caplog.text
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
# Unit Tests for the PyDMLineEdit Widget
2+
3+
import os
4+
import platform
5+
import pytest
6+
import time
7+
8+
from pydm.widgets.datetime import PyDMDateTimeEdit, TimeBase
9+
from qtpy.QtCore import QDateTime
10+
11+
12+
# --------------------
13+
# POSITIVE TEST CASES
14+
# --------------------
15+
16+
17+
@pytest.mark.parametrize(
18+
"init_channel",
19+
[
20+
"CA://MTEST",
21+
"",
22+
None,
23+
],
24+
)
25+
def test_construct(qtbot, init_channel):
26+
"""
27+
Test the widget construct.
28+
Expectations:
29+
All parameters have the correct default value.
30+
31+
Parameters
32+
----------
33+
qtbot : fixture
34+
Window for widget testing
35+
init_channel : str
36+
The data channel to be used by the widget
37+
38+
"""
39+
pydm_datetimeedit = PyDMDateTimeEdit(init_channel=init_channel)
40+
qtbot.addWidget(pydm_datetimeedit)
41+
42+
if init_channel:
43+
assert pydm_datetimeedit.channel == str(init_channel)
44+
else:
45+
assert pydm_datetimeedit.channel is None
46+
47+
assert pydm_datetimeedit.blockPastDate == True
48+
assert pydm_datetimeedit.relative == True
49+
assert pydm_datetimeedit.timeBase == TimeBase.Milliseconds
50+
51+
52+
@pytest.mark.parametrize(
53+
"value, expected_value",
54+
[
55+
# Assuming the time is localized to EST
56+
(0, "1969/12/31 19:00:00.000"),
57+
(1000, "1969/12/31 19:00:01.000"),
58+
(60000, "1969/12/31 19:01:00.000"),
59+
(3600000, "1969/12/31 20:00:00.000"),
60+
(18000000, "1970/01/01 00:00:00.000"),
61+
(0.0, "1969/12/31 19:00:00.000"),
62+
(1000.0, "1969/12/31 19:00:01.000"),
63+
(60000.0, "1969/12/31 19:01:00.000"),
64+
(3600000.0, "1969/12/31 20:00:00.000"),
65+
(18000000.0, "1970/01/01 00:00:00.000"),
66+
],
67+
)
68+
def test_value_changed(qtbot, signals, value, expected_value):
69+
"""
70+
Test the widget's handling of the value changed event.
71+
72+
Expectations:
73+
The following settings are in place after the value changed signal is emitted:
74+
1. The value displayed by the widget is the new value
75+
2. The value format maintained by the widget the correct format for the new value
76+
77+
Parameters
78+
----------
79+
qtbot : fixture
80+
pytest-qt window for widget testing
81+
signals : fixture
82+
The signals fixture, which provides access signals to be bound to the appropriate slots
83+
value : int, float
84+
The value to be displayed by the widget
85+
expected_value : str
86+
The expected displayed value of the widget
87+
"""
88+
# These tests will fail on Windows, we can't easily modify time
89+
if platform.system() == "Windows":
90+
return
91+
92+
os.environ["TZ"] = "US/Eastern"
93+
time.tzset()
94+
95+
pydm_datetimeedit = PyDMDateTimeEdit()
96+
pydm_datetimeedit.blockPastDate = False
97+
pydm_datetimeedit.relative = False
98+
pydm_datetimeedit.timeBase = TimeBase.Milliseconds
99+
qtbot.addWidget(pydm_datetimeedit)
100+
101+
signals.new_value_signal[type(value)].connect(pydm_datetimeedit.channelValueChanged)
102+
signals.new_value_signal[type(value)].emit(value)
103+
104+
assert pydm_datetimeedit.text() == expected_value

0 commit comments

Comments
 (0)