1 | #!/usr/bin/env python |
---|
2 | |
---|
3 | ########### SVN repository information ################### |
---|
4 | # $Date: 2013-08-07 21:42:32 +0000 (Wed, 07 Aug 2013) $ |
---|
5 | # $Author: jemian $ |
---|
6 | # $Revision: 1425 $ |
---|
7 | # $URL: bcdaqwidgets/trunk/src/bcdaqwidgets/bcdaqwidgets.py $ |
---|
8 | # $Id: bcdaqwidgets.py 1425 2013-08-07 21:42:32Z jemian $ |
---|
9 | ########### SVN repository information ################### |
---|
10 | |
---|
11 | ''' |
---|
12 | PySide-based EPICS-aware widgets for Python |
---|
13 | |
---|
14 | Copyright (c) 2009 - 2013, UChicago Argonne, LLC. |
---|
15 | See LICENSE file for details. |
---|
16 | |
---|
17 | The bcdaqwidgets [#]_ module provides a set of PySide (also Qt4) |
---|
18 | widgets that are EPICS-aware. These include: |
---|
19 | |
---|
20 | ============================= ================================================================ |
---|
21 | widget description |
---|
22 | ============================= ================================================================ |
---|
23 | :func:`BcdaQLabel` EPICS-aware QLabel widget |
---|
24 | :func:`BcdaQLineEdit` EPICS-aware QLineEdit widget |
---|
25 | :func:`BcdaQPushButton` EPICS-aware QPushButton widget |
---|
26 | :func:`BcdaQMomentaryButton` sends a value when pressed or released, label does not change |
---|
27 | :func:`BcdaQToggleButton` toggles boolean PV when pressed |
---|
28 | ============================= ================================================================ |
---|
29 | |
---|
30 | .. [#] BCDA: Beam line Controls and Data Acquisition group |
---|
31 | of the Advanced Photon Source, Argonne National Laboratory, |
---|
32 | http://www.aps.anl.gov/bcda |
---|
33 | |
---|
34 | ''' |
---|
35 | |
---|
36 | |
---|
37 | import epics |
---|
38 | try: |
---|
39 | from PyQt4 import QtCore, QtGui |
---|
40 | pyqtSignal = QtCore.pyqtSignal |
---|
41 | except: |
---|
42 | from PySide import QtCore, QtGui |
---|
43 | pyqtSignal = QtCore.Signal |
---|
44 | |
---|
45 | |
---|
46 | def typesafe_enum(*sequential, **named): |
---|
47 | ''' |
---|
48 | typesafe enum |
---|
49 | |
---|
50 | EXAMPLE:: |
---|
51 | |
---|
52 | >>> Numbers = typesafe_enum('ZERO', 'ONE', 'TWO', four='IV') |
---|
53 | >>> Numbers.ZERO |
---|
54 | 0 |
---|
55 | >>> Numbers.ONE |
---|
56 | 1 |
---|
57 | >>> Numbers.four |
---|
58 | IV |
---|
59 | |
---|
60 | :see: http://stackoverflow.com/questions/36932/whats-the-best-way-to-implement-an-typesafe_enum-in-python |
---|
61 | ''' |
---|
62 | enums = dict(zip(sequential, range(len(sequential))), **named) |
---|
63 | return type('TypesafeEnum', (), enums) |
---|
64 | |
---|
65 | AllowedStates = typesafe_enum('DISCONNECTED', 'CONNECTED',) |
---|
66 | CLUT = { # clut: Color LookUp Table |
---|
67 | AllowedStates.DISCONNECTED: "#ffffff", # white |
---|
68 | AllowedStates.CONNECTED: "#e0e0e0", # a bit darker than default #f0f0f0 |
---|
69 | |
---|
70 | } |
---|
71 | |
---|
72 | SeverityColor = typesafe_enum('NO_ALARM', 'MINOR', 'MAJOR',) |
---|
73 | SeverityColor.NO_ALARM = "green" # green |
---|
74 | SeverityColor.MINOR = "#ff0000" # dark orange since yellow looks bad against gray |
---|
75 | SeverityColor.MAJOR = "red" # red |
---|
76 | |
---|
77 | |
---|
78 | class StyleSheet(object): |
---|
79 | ''' |
---|
80 | manage style sheet settings for a Qt widget |
---|
81 | |
---|
82 | Example:: |
---|
83 | |
---|
84 | widget = QtGui.QLabel('example label') |
---|
85 | sty = bcdaqwidgets.StyleSheet(widget) |
---|
86 | sty.updateStyleSheet({ |
---|
87 | 'font': 'bold', |
---|
88 | 'color': 'white', |
---|
89 | 'background-color': 'dodgerblue', |
---|
90 | 'qproperty-alignment': 'AlignCenter', |
---|
91 | }) |
---|
92 | |
---|
93 | ''' |
---|
94 | |
---|
95 | def __init__(self, widget, sty={}): |
---|
96 | ''' |
---|
97 | :param obj widget: the Qt widget on which to apply the style sheet |
---|
98 | :param dict sty: starting dictionary of style sheet settings |
---|
99 | ''' |
---|
100 | self.widget = widget |
---|
101 | widgetclass = str(type(widget)).strip('>').split('.')[-1].strip("'") |
---|
102 | self.widgetclass = widgetclass |
---|
103 | self.style_cache = dict(sty) |
---|
104 | |
---|
105 | def clearCache(self): |
---|
106 | '''clear the internal cache''' |
---|
107 | self.style_cache = {} |
---|
108 | |
---|
109 | def updateStyleSheet(self, sty={}): |
---|
110 | '''change specified styles and apply all to widget''' |
---|
111 | self._updateCache(sty) |
---|
112 | self.widget.setStyleSheet(str(self)) |
---|
113 | |
---|
114 | def _updateCache(self, sty={}): |
---|
115 | '''update internal cache with specified styles''' |
---|
116 | for key, value in sty.items(): |
---|
117 | self.style_cache[key] = value |
---|
118 | |
---|
119 | def __str__(self): |
---|
120 | '''returns a CSS text with the cache settings''' |
---|
121 | s = self.widgetclass + ' {\n' |
---|
122 | for key, value in sorted(self.style_cache.items()): |
---|
123 | s += ' %s: %s;\n' % (key, value) |
---|
124 | s += '}' |
---|
125 | return s |
---|
126 | |
---|
127 | |
---|
128 | class BcdaQSignalDef(QtCore.QObject): |
---|
129 | ''' |
---|
130 | Define the signals used to communicate between the PyEpics |
---|
131 | thread and the PySide (main Qt4 GUI) thread. |
---|
132 | ''' |
---|
133 | # see: http://www.pyside.org/docs/pyside/PySide/QtCore/Signal.html |
---|
134 | # see: http://zetcode.com/gui/pysidetutorial/eventsandsignals/ |
---|
135 | |
---|
136 | newFgColor = pyqtSignal() |
---|
137 | newBgColor = pyqtSignal() |
---|
138 | newText = pyqtSignal() |
---|
139 | |
---|
140 | |
---|
141 | class BcdaQWidgetSuper(object): |
---|
142 | '''superclass for EPICS-aware widgets''' |
---|
143 | |
---|
144 | def __init__(self, pvname=None, useAlarmState=False): |
---|
145 | self.style_dict = {} |
---|
146 | self.text_cache = ' ' * 4 |
---|
147 | self.pv = None # PyEpics PV object |
---|
148 | self.ca_callback = None |
---|
149 | self.ca_connect_callback = None |
---|
150 | self.state = AllowedStates.DISCONNECTED |
---|
151 | self.labelSignal = BcdaQSignalDef() |
---|
152 | self.clut = dict(CLUT) |
---|
153 | |
---|
154 | self.useAlarmState = useAlarmState |
---|
155 | self.severity_color_list = [SeverityColor.NO_ALARM, SeverityColor.MINOR, SeverityColor.MAJOR] |
---|
156 | |
---|
157 | # for internal use persisting the various styleSheet settings |
---|
158 | self._style_sheet = StyleSheet(self) |
---|
159 | |
---|
160 | def ca_connect(self, pvname, ca_callback=None, ca_connect_callback=None): |
---|
161 | ''' |
---|
162 | Connect this widget with the EPICS pvname |
---|
163 | |
---|
164 | :param str pvname: EPICS Process Variable name |
---|
165 | :param obj ca_callback: EPICS CA callback handler method |
---|
166 | :param obj ca_connect_callback: EPICS CA connection state callback handler method |
---|
167 | ''' |
---|
168 | if self.pv is not None: |
---|
169 | self.ca_disconnect() |
---|
170 | if len(pvname) > 0: |
---|
171 | self.ca_callback = ca_callback |
---|
172 | self.ca_connect_callback = ca_connect_callback |
---|
173 | self.pv = epics.PV(pvname, |
---|
174 | callback=self.onPVChange, |
---|
175 | connection_callback=self.onPVConnect) |
---|
176 | self.state = AllowedStates.CONNECTED |
---|
177 | self.setToolTip(pvname) |
---|
178 | |
---|
179 | def ca_disconnect(self): |
---|
180 | '''disconnect from this EPICS PV, if connected''' |
---|
181 | if self.pv is not None: |
---|
182 | self.pv.remove_callback() |
---|
183 | pvname = self.pv.pvname |
---|
184 | self.pv.disconnect() |
---|
185 | self.pv = None |
---|
186 | self.ca_callback = None |
---|
187 | self.ca_connect_callback = None |
---|
188 | self.state = AllowedStates.DISCONNECTED |
---|
189 | self.text_cache = ' ' * 4 |
---|
190 | self.SetText() |
---|
191 | self.SetBackgroundColor() |
---|
192 | self.setToolTip(pvname + ' not connected') |
---|
193 | |
---|
194 | def onPVConnect(self, pvname='', **kw): |
---|
195 | '''respond to a PyEpics CA connection event''' |
---|
196 | conn = kw['conn'] |
---|
197 | self.text_cache = { # adjust the text |
---|
198 | False: '', #'disconnected', |
---|
199 | True: 'connected', |
---|
200 | }[conn] |
---|
201 | self.labelSignal.newText.emit() # threadsafe update of the widget |
---|
202 | self.state = { # adjust the state |
---|
203 | False: AllowedStates.DISCONNECTED, |
---|
204 | True: AllowedStates.CONNECTED, |
---|
205 | }[conn] |
---|
206 | self.labelSignal.newBgColor.emit() # threadsafe update of the widget |
---|
207 | if self.ca_connect_callback is not None: |
---|
208 | # caller wants to be notified of this camonitor event |
---|
209 | self.ca_connect_callback(**kw) |
---|
210 | |
---|
211 | def onPVChange(self, pvname=None, char_value=None, **kw): |
---|
212 | '''respond to a PyEpics camonitor() event''' |
---|
213 | self.text_cache = char_value # cache the new text locally |
---|
214 | self.labelSignal.newText.emit() # threadsafe update of the widget |
---|
215 | if self.ca_callback is not None: |
---|
216 | # caller wants to be notified of this camonitor event |
---|
217 | self.ca_callback(pvname=pvname, char_value=char_value, **kw) |
---|
218 | |
---|
219 | def SetText(self, *args, **kw): |
---|
220 | '''set the text of the widget (threadsafe update)''' |
---|
221 | # pull the new text from the cache (set by onPVChange() method) |
---|
222 | self.setText(self.text_cache) |
---|
223 | |
---|
224 | # if desired, color the text based on the alarm severity |
---|
225 | if self.useAlarmState and self.pv is not None: |
---|
226 | self.pv.get_ctrlvars() |
---|
227 | if self.pv.severity is not None: |
---|
228 | color = self.severity_color_list[self.pv.severity] |
---|
229 | self.updateStyleSheet({'color': color}) |
---|
230 | |
---|
231 | def updateStyleSheet(self, changes_dict): |
---|
232 | '''update the widget's stylesheet''' |
---|
233 | self._style_sheet.updateStyleSheet(changes_dict) |
---|
234 | |
---|
235 | |
---|
236 | class BcdaQLabel(QtGui.QLabel, BcdaQWidgetSuper): |
---|
237 | ''' |
---|
238 | Provide the value of an EPICS PV on a PySide.QtGui.QLabel |
---|
239 | |
---|
240 | USAGE:: |
---|
241 | |
---|
242 | from moxy.qtlib import bcdaqwidgets |
---|
243 | |
---|
244 | ... |
---|
245 | |
---|
246 | widget = bcdaqwidgets.BcdaQLabel() |
---|
247 | widget.ca_connect("example:m1.RBV") |
---|
248 | |
---|
249 | :param str pvname: epics process variable name for this widget |
---|
250 | :param bool useAlarmState: change the text color based on pv severity |
---|
251 | :param str bgColorPv: update widget's background color based on this pv's value |
---|
252 | |
---|
253 | ''' |
---|
254 | |
---|
255 | def __init__(self, pvname=None, useAlarmState=False, bgColorPv=None): |
---|
256 | ''':param str text: initial Label text (really, we can ignore this)''' |
---|
257 | BcdaQWidgetSuper.__init__(self, useAlarmState=useAlarmState) |
---|
258 | QtGui.QLabel.__init__(self, self.text_cache) |
---|
259 | |
---|
260 | # define the signals we'll use in the camonitor handler to update the GUI |
---|
261 | self.labelSignal = BcdaQSignalDef() |
---|
262 | self.labelSignal.newBgColor.connect(self.SetBackgroundColor) |
---|
263 | self.labelSignal.newText.connect(self.SetText) |
---|
264 | |
---|
265 | self.updateStyleSheet({ |
---|
266 | 'background-color': 'bisque', |
---|
267 | 'border': '1px solid gray', |
---|
268 | 'font': 'bold', |
---|
269 | }) |
---|
270 | |
---|
271 | self.clut = dict(CLUT) |
---|
272 | self.pv = None |
---|
273 | self.ca_callback = None |
---|
274 | self.ca_connect_callback = None |
---|
275 | self.state = AllowedStates.DISCONNECTED |
---|
276 | self.SetBackgroundColor() |
---|
277 | self.setAlignment(QtCore.Qt.AlignHCenter) |
---|
278 | |
---|
279 | if pvname is not None and isinstance(pvname, str): |
---|
280 | self.ca_connect(pvname) |
---|
281 | |
---|
282 | if bgColorPv is not None: |
---|
283 | self.bgColorObj = epics.PV(pvname=bgColorPv, callback=self.onBgColorObjChanged) |
---|
284 | |
---|
285 | self.bgColor_clut = {'not connected': 'white', '0': '#88ff88', '1': 'transparent'} |
---|
286 | self.bgColor = None |
---|
287 | |
---|
288 | self.bgColorSignal = BcdaQSignalDef() |
---|
289 | self.bgColorSignal.newBgColor.connect(self.SetBackgroundColorExtra) |
---|
290 | |
---|
291 | def onBgColorObjChanged(self, *args, **kw): |
---|
292 | '''epics pv callback when bgColor PV changes''' |
---|
293 | if not self.bgColorObj.connected: # white and displayed text is ' ' |
---|
294 | self.bgColor = self.bgColor_clut['not connected'] |
---|
295 | else: |
---|
296 | value = str(self.bgColorObj.get()) |
---|
297 | if value in self.bgColor_clut: |
---|
298 | self.bgColor = self.bgColor_clut[value] |
---|
299 | # trigger the background color to change |
---|
300 | self.bgColorSignal.newBgColor.emit() |
---|
301 | |
---|
302 | def SetBackgroundColorExtra(self, *args, **kw): |
---|
303 | '''changes the background color of the widget''' |
---|
304 | if self.bgColor is not None: |
---|
305 | self.updateStyleSheet({'background-color': self.bgColor}) |
---|
306 | self.bgColor = None |
---|
307 | |
---|
308 | def SetBackgroundColor(self, *args, **kw): |
---|
309 | '''set the background color of the widget via its stylesheet''' |
---|
310 | color = self.clut[self.state] |
---|
311 | self.updateStyleSheet({'background-color': color}) |
---|
312 | |
---|
313 | |
---|
314 | class BcdaQLineEdit(QtGui.QLineEdit, BcdaQWidgetSuper): |
---|
315 | ''' |
---|
316 | Provide the value of an EPICS PV on a PySide.QtGui.QLineEdit |
---|
317 | |
---|
318 | USAGE:: |
---|
319 | |
---|
320 | from moxy.qtlib import bcdaqwidgets |
---|
321 | |
---|
322 | ... |
---|
323 | |
---|
324 | widget = bcdaqwidgets.BcdaQLineEdit() |
---|
325 | widget.ca_connect("example:m1.VAL") |
---|
326 | |
---|
327 | ''' |
---|
328 | |
---|
329 | def __init__(self, pvname=None, useAlarmState=False): |
---|
330 | ''':param str text: initial Label text (really, we can ignore this)''' |
---|
331 | BcdaQWidgetSuper.__init__(self) |
---|
332 | QtGui.QLineEdit.__init__(self, self.text_cache) |
---|
333 | |
---|
334 | # define the signals we'll use in the camonitor handler to update the GUI |
---|
335 | self.labelSignal.newBgColor.connect(self.SetBackgroundColor) |
---|
336 | self.labelSignal.newText.connect(self.SetText) |
---|
337 | |
---|
338 | self.clut = dict(CLUT) |
---|
339 | self.clut[AllowedStates.CONNECTED] = "bisque" |
---|
340 | self.updateStyleSheet({ |
---|
341 | 'background-color': 'bisque', |
---|
342 | 'border': '3px inset gray', |
---|
343 | }) |
---|
344 | |
---|
345 | self.SetBackgroundColor() |
---|
346 | self.setAlignment(QtCore.Qt.AlignHCenter) |
---|
347 | self.returnPressed.connect(self.onReturnPressed) |
---|
348 | |
---|
349 | if pvname is not None and isinstance(pvname, str): |
---|
350 | self.ca_connect(pvname) |
---|
351 | |
---|
352 | def onReturnPressed(self): |
---|
353 | '''send the widget's text to the EPICS PV''' |
---|
354 | if self.pv is not None and len(self.text()) > 0: |
---|
355 | self.pv.put(self.text()) |
---|
356 | |
---|
357 | def SetBackgroundColor(self, *args, **kw): |
---|
358 | '''set the background color of the QLineEdit() via its QPalette''' |
---|
359 | color = self.clut[self.state] |
---|
360 | self.updateStyleSheet({'background-color': color}) |
---|
361 | |
---|
362 | |
---|
363 | class BcdaQPushButton(QtGui.QPushButton, BcdaQWidgetSuper): |
---|
364 | ''' |
---|
365 | Provide a QtGui.QPushButton connected to an EPICS PV |
---|
366 | |
---|
367 | It is necessary to also call the SetPressedValue() and/or |
---|
368 | SetReleasedValue() method to define the value to be sent |
---|
369 | to the EPICS PV with the corresponding push button event. |
---|
370 | If left unconfigured, no action will be taken. |
---|
371 | |
---|
372 | USAGE:: |
---|
373 | |
---|
374 | from moxy.qtlib import bcdaqwidgets |
---|
375 | |
---|
376 | ... |
---|
377 | |
---|
378 | widget = bcdaqwidgets.BcdaQPushButton() |
---|
379 | widget.ca_connect("example:bo0") |
---|
380 | widget.SetReleasedValue(1) |
---|
381 | |
---|
382 | ''' |
---|
383 | |
---|
384 | def __init__(self, label='', pvname=None): |
---|
385 | ''':param str text: initial Label text (really, we can ignore this)''' |
---|
386 | BcdaQWidgetSuper.__init__(self) |
---|
387 | QtGui.QPushButton.__init__(self, self.text_cache) |
---|
388 | self.setText(label) |
---|
389 | |
---|
390 | self.labelSignal = BcdaQSignalDef() |
---|
391 | self.labelSignal.newBgColor.connect(self.SetBackgroundColor) |
---|
392 | self.labelSignal.newText.connect(self.SetText) |
---|
393 | |
---|
394 | self.clut = dict(CLUT) |
---|
395 | self.updateStyleSheet({'font': 'bold',}) |
---|
396 | |
---|
397 | self.pv = None |
---|
398 | self.ca_callback = None |
---|
399 | self.ca_connect_callback = None |
---|
400 | self.state = AllowedStates.DISCONNECTED |
---|
401 | self.setCheckable(True) |
---|
402 | self.SetBackgroundColor() |
---|
403 | self.clicked[bool].connect(self.onPressed) |
---|
404 | self.released.connect(self.onReleased) |
---|
405 | |
---|
406 | self.pressed_value = None |
---|
407 | self.released_value = None |
---|
408 | |
---|
409 | if pvname is not None and isinstance(pvname, str): |
---|
410 | self.ca_connect(pvname) |
---|
411 | |
---|
412 | def onPressed(self, **kw): |
---|
413 | '''button was pressed, send preset value to EPICS''' |
---|
414 | if self.pv is not None and self.pressed_value is not None: |
---|
415 | self.pv.put(self.pressed_value, wait=True) |
---|
416 | |
---|
417 | def onReleased(self, **kw): |
---|
418 | '''button was released, send preset value to EPICS''' |
---|
419 | if self.pv is not None and self.released_value is not None: |
---|
420 | self.pv.put(self.released_value, wait=True) |
---|
421 | |
---|
422 | def SetPressedValue(self, value): |
---|
423 | '''specify the value to be sent to the EPICS PV when the button is pressed''' |
---|
424 | self.pressed_value = value |
---|
425 | |
---|
426 | def SetReleasedValue(self, value): |
---|
427 | '''specify the value to be sent to the EPICS PV when the button is released''' |
---|
428 | self.released_value = value |
---|
429 | |
---|
430 | def SetBackgroundColor(self, *args, **kw): |
---|
431 | '''set the background color of the QPushButton() via its stylesheet''' |
---|
432 | color = self.clut[self.state] |
---|
433 | self.updateStyleSheet({'background-color': color}) |
---|
434 | |
---|
435 | |
---|
436 | class BcdaQMomentaryButton(BcdaQPushButton): |
---|
437 | ''' |
---|
438 | Send a value when pressed or released, label does not change if PV changes. |
---|
439 | |
---|
440 | This is a special case of a BcdaQPushButton where the text on the button |
---|
441 | does not respond to changes of the value of the attached EPICS PV. |
---|
442 | |
---|
443 | It is a good choice to use, for example, for a motor STOP button. |
---|
444 | |
---|
445 | USAGE:: |
---|
446 | |
---|
447 | from moxy.qtlib import bcdaqwidgets |
---|
448 | |
---|
449 | ... |
---|
450 | |
---|
451 | widget = bcdaqwidgets.BcdaQMomentaryButton('Stop') |
---|
452 | widget.ca_connect("example:m1.STOP") |
---|
453 | widget.SetReleasedValue(1) |
---|
454 | |
---|
455 | ''' |
---|
456 | |
---|
457 | def SetText(self, *args, **kw): |
---|
458 | '''do not change the label from the EPICS PV''' |
---|
459 | pass |
---|
460 | |
---|
461 | |
---|
462 | class BcdaQToggleButton(BcdaQPushButton): |
---|
463 | ''' |
---|
464 | Toggles boolean PV when pressed |
---|
465 | |
---|
466 | This is a special case of a BcdaQPushButton where the text on the button |
---|
467 | changes with the value of the attached EPICS PV. In this case, the |
---|
468 | displayed value is the name of the next state of the EPICS PV when |
---|
469 | the button is pressed. |
---|
470 | |
---|
471 | It is a good choice to use, for example, for an ON/OFF button. |
---|
472 | |
---|
473 | USAGE:: |
---|
474 | |
---|
475 | from moxy.qtlib import bcdaqwidgets |
---|
476 | |
---|
477 | ... |
---|
478 | |
---|
479 | widget = bcdaqwidgets.BcdaQToggleButton() |
---|
480 | widget.ca_connect("example:room_light") |
---|
481 | widget.SetReleasedValue(1) |
---|
482 | |
---|
483 | ''' |
---|
484 | |
---|
485 | def __init__(self, pvname=None): |
---|
486 | BcdaQPushButton.__init__(self) |
---|
487 | self.value_names = {1: 'change to 0', 0: 'change to 1'} |
---|
488 | self.setToolTip('tell EPICS PV to do this') |
---|
489 | |
---|
490 | if pvname is not None and isinstance(pvname, str): |
---|
491 | self.ca_connect(pvname) |
---|
492 | |
---|
493 | def ca_connect(self, pvname, ca_callback=None, ca_connect_callback=None): |
---|
494 | BcdaQPushButton.ca_connect(self, pvname, ca_callback=None, ca_connect_callback=None) |
---|
495 | labels = self.pv.enum_strs |
---|
496 | if labels is not None and len(labels)>1: |
---|
497 | # describe what happens when the button is pressed |
---|
498 | self.value_names[0] = labels[1] |
---|
499 | self.value_names[1] = labels[0] |
---|
500 | |
---|
501 | def onPressed(self): |
---|
502 | '''button was pressed, toggle the EPICS value as a boolean''' |
---|
503 | if self.pv is not None: |
---|
504 | self.pv.put(not self.pv.get(), wait=True) |
---|
505 | |
---|
506 | # disable these methods |
---|
507 | def onReleased(self, **kw): pass |
---|
508 | def SetPressedValue(self, value): pass |
---|
509 | def SetReleasedValue(self, value): pass |
---|
510 | |
---|
511 | def SetText(self, *args, **kw): |
---|
512 | '''set the text of the widget (threadsafe update) from the EPICS PV''' |
---|
513 | # pull the new text from the cache (set by onPVChange() method) |
---|
514 | self.setText(self.value_names[self.pv.get()]) |
---|
515 | |
---|
516 | |
---|
517 | #------------------------------------------------------------------ |
---|
518 | |
---|
519 | |
---|
520 | class DemoView(QtGui.QWidget): |
---|
521 | ''' |
---|
522 | Show the BcdaQWidgets using an EPICS PV connection. |
---|
523 | |
---|
524 | Allow it to connect and ca_disconnect. |
---|
525 | This is a variation of EPICS PV Probe. |
---|
526 | ''' |
---|
527 | |
---|
528 | def __init__(self, parent=None, pvname=None, bgColorPvname=None): |
---|
529 | QtGui.QWidget.__init__(self, parent) |
---|
530 | |
---|
531 | layout = QtGui.QGridLayout() |
---|
532 | layout.addWidget(QtGui.QLabel('BcdaQLabel'), 0, 0) |
---|
533 | self.value = BcdaQLabel() |
---|
534 | layout.addWidget(self.value, 0, 1) |
---|
535 | self.setLayout(layout) |
---|
536 | |
---|
537 | self.sig = BcdaQSignalDef() |
---|
538 | self.sig.newBgColor.connect(self.SetBackgroundColor) |
---|
539 | self.toggle = False |
---|
540 | |
---|
541 | self.setWindowTitle("Demo bcdaqwidgets module") |
---|
542 | if pvname is not None: |
---|
543 | self.ca_connect(pvname) |
---|
544 | |
---|
545 | layout.addWidget(QtGui.QLabel('BcdaQLabel with alarm colors'), 1, 0) |
---|
546 | layout.addWidget(BcdaQLabel(pvname=pvname, useAlarmState=True), 1, 1) |
---|
547 | |
---|
548 | pvnameBg = pvname.split('.')[0] + '.DMOV' |
---|
549 | lblWidget = BcdaQLabel(pvname=pvname, bgColorPvname=pvnameBg) |
---|
550 | layout.addWidget(QtGui.QLabel('BcdaQLabel with BG color change due to moving motor'), 2, 0) |
---|
551 | layout.addWidget(lblWidget, 2, 1) |
---|
552 | |
---|
553 | layout.addWidget(QtGui.QLabel('BcdaQLineEdit'), 3, 0) |
---|
554 | layout.addWidget(BcdaQLineEdit(pvname=pvname), 3, 1) |
---|
555 | |
---|
556 | pvname = pvname.split('.')[0] + '.PROC' |
---|
557 | widget = BcdaQMomentaryButton(label=pvname, pvname=pvname) |
---|
558 | layout.addWidget(QtGui.QLabel('BcdaQMomentaryButton'), 4, 0) |
---|
559 | layout.addWidget(widget, 4, 1) |
---|
560 | |
---|
561 | def ca_connect(self, pvname): |
---|
562 | self.value.ca_connect(pvname, ca_callback=self.callback) |
---|
563 | |
---|
564 | def callback(self, *args, **kw): |
---|
565 | self.sig.newBgColor.emit() # threadsafe update of the widget |
---|
566 | |
---|
567 | def SetBackgroundColor(self, *args, **kw): |
---|
568 | '''toggle the background color of self.value via its stylesheet''' |
---|
569 | self.toggle = not self.toggle |
---|
570 | color = {False: "#ccc333", True: "#cccccc",}[self.toggle] |
---|
571 | self.value.updateStyleSheet({'background-color': color}) |
---|
572 | |
---|
573 | |
---|
574 | #------------------------------------------------------------------ |
---|
575 | |
---|
576 | |
---|
577 | def main(): |
---|
578 | '''command-line interface to test this GUI widget''' |
---|
579 | import argparse |
---|
580 | import sys |
---|
581 | parser = argparse.ArgumentParser(description='Test the bcdaqwidgets module') |
---|
582 | |
---|
583 | # positional arguments |
---|
584 | # not required if GUI option is selected |
---|
585 | parser.add_argument('test_PV', |
---|
586 | action='store', |
---|
587 | nargs='?', |
---|
588 | help="EPICS PV name", |
---|
589 | default="prj:m1.RBV", |
---|
590 | ) |
---|
591 | results = parser.parse_args() |
---|
592 | |
---|
593 | app = QtGui.QApplication(sys.argv) |
---|
594 | view = DemoView(pvname=results.test_PV) |
---|
595 | view.show() |
---|
596 | sys.exit(app.exec_()) |
---|
597 | |
---|
598 | |
---|
599 | if __name__ == '__main__': |
---|
600 | main() |
---|