source: moxy/trunk/src/moxy/vc_axis.py @ 781

Last change on this file since 781 was 781, checked in by jemian, 11 years ago

less printout, strip off .VAL for revert button

  • Property svn:eol-style set to native
  • Property svn:executable set to *
  • Property svn:keywords set to Author Date Id Rev Url
File size: 29.1 KB
Line 
1#!/usr/bin/env python
2
3########### SVN repository information ###################
4# $Date: 2012-02-04 05:27:01 +0000 (Sat, 04 Feb 2012) $
5# $Author: jemian $
6# $Revision: 781 $
7# $URL$
8# $Id: vc_axis.py 781 2012-02-04 05:27:01Z jemian $
9########### SVN repository information ###################
10
11'''
12demo view for EPICS PV connection class: m_axis.SingleAxis()
13
14Copyright (c) 2009 - 2011, UChicago Argonne, LLC.
15See LICENSE file for details.
16'''
17
18
19# - - - - - - - - - - - - - - - - - - Imports
20
21
22import wx
23import wx.lib.buttons
24import wx.lib.stattext
25import m_pv
26import m_axis
27import epics.wx
28
29
30# - - - - - - - - - - - - - - - - - - Global
31
32
33__svnid__ = "$Id: vc_axis.py 781 2012-02-04 05:27:01Z jemian $"
34
35CONNECT_TEST_TIMEOUT = 0.1                      # seconds
36
37COLOR_MOVING = wx.Colour(179, 250, 142)         # pale green
38COLOR_NOT_MOVING = wx.Colour(200, 191, 140)     # not exactly background color
39COLOR_BACKGROUND = wx.Colour(237, 233, 227)     # boa-constructor uses this
40COLOR_PV_AUTOFILL = wx.Colour(200, 200, 200)    # pale grey
41COLOR_PV_NOT_OK = wx.Colour(254, 232, 255)      # pale pink
42COLOR_PV_OK = wx.Colour(235, 254, 231)          # pale green (another)
43
44COLOR_PV_NOT_DEFINED = COLOR_BACKGROUND
45
46
47# - - - - - - - - - - - - - - - - - - classes
48
49
50class SingleAxisPanel(wx.Panel):
51    '''
52    Show a single motion axis in a panel.
53    The EPICS CA connections must be made here to get the callbacks.
54   
55    Some buttons (Connect, Disconnect, ...) are optional
56    and can be suppressed by adding  ``show_buttons = False``
57   
58    :param obj parent: panel or frame that contains this panel
59    :param axis: set of PVs that describe the operation of this axis
60    :type axis: m_axis.SingleAxis object
61    '''
62   
63    def __init__(self, parent, axis, show_buttons = True):
64        self.axis = axis
65        self.connected = False
66        self.show_buttons = show_buttons
67        self.parent = parent
68
69        wx.Panel.__init__(self, id=wx.ID_ANY, parent=parent)
70        self.SetToolTipString( u'SingleAxisPanel' )
71
72        # tabbed notebook: Operate | Setup
73        self.notebook = wx.Notebook( self )
74       
75        self.operate_panel = SingleAxisOperatePanel(self.notebook, self, show_buttons)
76        self.setup_panel = SingleAxisSetupPanel(self.notebook, 
77                                self,
78                                show_buttons, 
79                                operate_panel = self.operate_panel)
80
81        self.notebook.AddPage( self.operate_panel, "operate", select=True )
82        self.notebook.AddPage( self.setup_panel, "setup" )
83       
84        sizer = wx.BoxSizer( orient=wx.VERTICAL )
85        sizer.AddSizer( self.notebook, 1, flag=wx.EXPAND )
86        self.SetSizer( sizer )
87
88        self.setup_panel._Set_All_PvWidgetBackgroundColours()
89        self.Fit()
90   
91    def connect(self):
92        '''connect the widgets with the EPICS PVs'''
93        self.operate_panel.connect()
94        self.setup_panel.connect()
95   
96    def disconnect(self):
97        '''disconnect the widgets from the EPICS PVs'''
98        self.operate_panel.disconnect()
99        self.setup_panel.disconnect()
100
101
102class SingleAxisOperatePanel(wx.Panel):
103    '''
104    Show the widgets to operate a single motion axis in a panel.
105   
106    Some buttons (Connect, Disconnect, ...) are optional
107    and can be suppressed by adding  ``show_buttons = False``
108   
109    :param obj parent: container of this panel
110    :param obj axis_parent: container of the axis object
111    :param bool show_buttons: suppress display of certain buttons
112    '''
113   
114    def __init__(self, parent, axis_parent, show_buttons = True):
115        self.parent = parent
116        self.axis_parent = axis_parent
117        self.show_buttons = show_buttons
118        self.connected = False
119
120        wx.Panel.__init__(self, id=wx.ID_ANY, parent=parent)
121        self.SetToolTipString( self.axis_parent.axis.axis.get_pv('VAL').pvname )
122        self._init_panel(self)
123        self.Fit()
124   
125    def _init_panel(self, panel):
126        description_sizer = self._Make_Description_Sizer( panel )
127        position_sizer = self._Make_Positions_Sizer( panel )
128       
129        controlsButton = Make_My_Button(panel, 
130                                         label=u'more', 
131                                         name='controlsButton', 
132                                         tip=u'Access more controls for EPICS motors', 
133                                         binding=self.doMotorControlsButton)
134       
135        stopButton = Make_My_Button(panel, 
136                                         label=u'Stop', 
137                                         name='stopButton', 
138                                         tip=u'Stop this axis from moving', 
139                                         binding=self.doStopButton)
140        stopButton.SetBackgroundColour('red')
141        stopButton.SetForegroundColour('yellow')
142
143        self.SetStatus( 'initialized' )
144       
145        sizer = wx.BoxSizer( orient=wx.VERTICAL )
146        panel.SetSizer( sizer )
147        sizer.AddSizer(description_sizer, 1, flag=wx.EXPAND)
148        sizer.AddSizer(position_sizer, 1, flag=wx.EXPAND)
149        sizer.AddSizer(controlsButton, 0, flag=wx.EXPAND)
150        sizer.AddSizer(stopButton, 0, flag=wx.EXPAND)
151
152    def _Make_Description_Sizer(self, parent):
153        '''
154        describe this axis, return a sizer
155        '''
156        self.gst_name = wx.lib.stattext.GenStaticText(parent, 
157                                                      wx.ID_ANY, 'description')
158        self.gst_name.SetToolTipString(u'local name for this axis')
159        self.gst_name.SetLabel( self.axis_parent.axis.name )
160       
161        self.w_desc = wx.lib.stattext.GenStaticText(parent, 
162                                                    wx.ID_ANY, 'description')
163        self.w_desc.SetToolTipString(u'EPICS description for this axis')
164       
165        self.w_egu = wx.lib.stattext.GenStaticText(parent, wx.ID_ANY, 'units')
166        self.w_egu.SetToolTipString(u'engineering units')
167
168        sbox = wx.StaticBox(parent, id=wx.ID_ANY,
169              label='PV description', style=0)
170        sbs = wx.StaticBoxSizer(sbox, wx.VERTICAL)
171        fgs = wx.FlexGridSizer(rows=3, cols=1, hgap=4, vgap=4)
172        fgs.AddGrowableCol(0)
173        sbs.Add(fgs, 0, wx.EXPAND|wx.ALIGN_CENTRE|wx.ALL, 5)
174
175        fgs.Add(self.gst_name, flag=wx.EXPAND|wx.GROW)
176        fgs.Add(self.w_desc, flag=wx.EXPAND|wx.GROW)
177        fgs.Add(self.w_egu, flag=wx.EXPAND|wx.GROW)
178
179        return sbs
180
181    def _Make_Positions_Sizer(self, parent):
182        '''
183        show readback and target values and connection state, return the sizer
184        '''
185        self.w_readback = wx.lib.stattext.GenStaticText(parent, 
186                                                        wx.ID_ANY, 'readback')
187        self.w_readback.SetToolTipString(u'readback value')
188   
189        self.w_target = Make_My_TextEntry(parent, '', 
190                False, u'target value', self.doTargetEntry)
191       
192        self.w_status = wx.lib.stattext.GenStaticText(parent, wx.ID_ANY, 'status')
193        self.w_status.SetToolTipString(u'EPICS connection status')
194       
195        sbox = wx.StaticBox(parent, id=wx.ID_ANY,
196              label='position', style=0)
197        sbs = wx.StaticBoxSizer(sbox, wx.VERTICAL)
198        fgs = wx.FlexGridSizer(rows=3, cols=1, hgap=4, vgap=4)
199        fgs.AddGrowableCol(0)
200        sbs.Add(fgs, 0, wx.EXPAND|wx.ALIGN_CENTRE|wx.ALL, 5)
201       
202        # style = wx.ALL | wx.ALIGN_CENTER_VERTICAL | wx.GROW
203        style = wx.GROW | wx.EXPAND | wx.ALL
204        fgs.Add(self.w_readback, flag=style)
205        fgs.Add(self.w_target, flag=wx.EXPAND|wx.ALL)
206        fgs.Add(self.w_status, flag=wx.EXPAND|wx.ALL)
207
208        return sbs
209   
210    def doTargetEntry(self, event):
211        ''' [Enter] was pressed in target field, need to tell axis to move '''
212        obj = event.EventObject
213        if obj == self.w_target:
214            if self.axis_parent.axis._connected:
215                s = obj.GetValue()
216                try:
217                    value = float( s )
218                    self.axis_parent.axis.move( value )
219                    self.SetStatus( 'new target' )
220                except ValueError:
221                    msg = '''"%s" is not a floating point number!''' % s
222                    AcknowledgeDialog(self.parent, msg )
223
224    def doStopButton(self, event):
225        '''stop this axis from moving'''
226        self.axis_parent.axis.stopMotion()
227        self.SetStatus( 'stopped' )
228
229    def doMotorControlsButton(self, event):
230        '''stop this axis from moving'''
231        self.motorControlsDialog()
232   
233    def motorControlsDialog(self):
234        if self.axis_parent.axis.isMotorRecord:
235            pv = self.axis_parent.axis.axis.get_pv('VAL').pvname
236            parent = wx.GetTopLevelParent(self.axis_parent)
237            MotorControlsFrame(parent, m_pv.GetBasePv(pv))
238        else:
239            msg = "This axis is not an EPICS motor record."
240            msg += "  No motor control screen available."
241            AcknowledgeDialog(self.axis_parent, msg)
242
243    def SetStatus(self, value):
244        '''describe what's what'''
245        wx.CallAfter(self.w_status.SetLabel, value)
246
247    def connect(self):
248        '''connect the PVs of this axis with EPICS'''
249        if not self.connected:
250            the_axis = self.axis_parent.axis
251            the_axis.connect(self.callback)
252            self.SetStatus( 'connected' )
253            self.w_target.SetEditable(True)
254            self.connected = True
255            self.gst_name.SetLabel( the_axis.name )
256            self.SetToolTipString( the_axis.axis.get_pv('VAL').pvname )
257            for field in ('VAL', 'RBV', 'EGU', 'DESC', 'DMOV', 'STOP'):
258                value = the_axis.axis.get(field)
259                self.callback(**{'field': field, 'value': value})
260
261    def disconnect(self):
262        '''connect the PVs of this axis from EPICS'''
263        if self.connected:
264            self.axis_parent.axis.disconnect()
265            self.SetStatus( 'disconnected' )
266            self.w_target.SetEditable(False)
267            self.connected = False
268
269    def SetVAL(self, value):
270        '''put the value into the widget text'''
271        wx.CallAfter(self.w_target.SetValue, str(value) )
272
273    def SetRBV(self, value):
274        '''put the value into the widget text'''
275        wx.CallAfter(self.w_readback.SetLabel, str(value) )
276
277    def SetEGU(self, value):
278        '''put the value into the widget text'''
279        wx.CallAfter(self.w_egu.SetLabel, value)
280
281    def SetDESC(self, value):
282        '''put the value into the widget text'''
283        wx.CallAfter(self.w_desc.SetLabel, value)
284
285    def SetDMOV(self, value):
286        '''set the widget background color based on moving state'''
287        moving = not value
288        color = {False: COLOR_NOT_MOVING, True: COLOR_MOVING}[moving]
289        wx.CallAfter(self.w_target.SetBackgroundColour, color )
290        wx.CallAfter(self.w_readback.SetBackgroundColour, color )
291        self.SetStatus( {False: 'not moving', True: 'moving'}[moving] )
292
293    def SetSTOP(self, value):
294        '''a no-op'''
295        pass
296   
297    def callback(self, **kw):
298        "PyEPICS CA monitor callback"
299        #print kw
300        if 'conn' in kw.keys():
301            print "connection event", kw
302            choices = {True: 'connected event received',
303                       False: 'disconnect received, IOC is unavailable?'}
304            self.SetStatus( choices[ kw['conn'] ] )
305        else:
306            #print "PV update event"
307            if 'field' in kw:
308                #print kw
309                {'VAL': self.SetVAL,
310                 'RBV': self.SetRBV,
311                 'EGU': self.SetEGU,
312                 'DESC': self.SetDESC,
313                 'DMOV': self.SetDMOV,
314                 'STOP': self.SetSTOP,
315                }[ kw['field'] ]( kw['value'] )
316
317
318class SingleAxisSetupPanel(wx.Panel):
319    '''
320    Show the widgets to setup and configure a single motion axis in a panel.
321   
322    Some buttons (Connect, Disconnect, ...) are optional
323    and can be suppressed by adding  ``show_buttons = False``
324   
325    :param obj parent: panel or frame that contains this panel
326    :param obj axis_parent: container of the axis object
327    :param bool show_buttons: suppress display of certain buttons
328    :param obj operate_panel: SingleAxisOperatePanel object to use with this class
329    '''
330   
331    def __init__(self, parent, axis_parent, show_buttons = True, operate_panel = None):
332        self.parent = parent
333        self.axis_parent = axis_parent
334        self.show_buttons = show_buttons
335        self.operate_panel = operate_panel
336
337        wx.Panel.__init__(self, id=wx.ID_ANY, parent=parent)
338        self.SetToolTipString(self.axis_parent.axis.axis.get_pv('VAL').pvname)
339        self._init_panel(self)
340        self.Fit()
341   
342    def _init_panel(self, panel):
343        if self.show_buttons:
344            connect_buttons = self._Make_Connect_Buttons_Sizer( panel )
345            accept_buttons = self._Make_Accept_Buttons_Sizer( panel )
346       
347        config_sizer = self._Make_Configure_Sizer( panel )
348       
349        sizer = wx.BoxSizer( orient=wx.VERTICAL )
350        panel.SetSizer( sizer )
351        sizer.AddSizer(config_sizer, 1, flag=wx.EXPAND)
352        if self.show_buttons:
353            sizer.AddSizer(accept_buttons, 0, flag=wx.EXPAND)
354            sizer.AddSizer(connect_buttons, 0, flag=wx.EXPAND)
355        self.Fit()
356
357    def _Make_Configure_Sizer(self, parent):
358        '''
359        make an editor to change the settings, return a sizer
360        '''
361        label_name = wx.lib.stattext.GenStaticText(parent, wx.ID_ANY, 'name')
362        label_VAL = wx.lib.stattext.GenStaticText(parent, wx.ID_ANY, 'VAL')
363        label_isMotor = wx.lib.stattext.GenStaticText(parent, wx.ID_ANY, '"motor?"')
364        label_RBV = wx.lib.stattext.GenStaticText(parent, wx.ID_ANY, 'RBV')
365        label_EGU = wx.lib.stattext.GenStaticText(parent, wx.ID_ANY, 'EGU')
366        label_DESC = wx.lib.stattext.GenStaticText(parent, wx.ID_ANY, 'DESC')
367        label_DMOV = wx.lib.stattext.GenStaticText(parent, wx.ID_ANY, 'DMOV')
368        label_STOP = wx.lib.stattext.GenStaticText(parent, wx.ID_ANY, 'STOP')
369       
370        this_axis = self.axis_parent.axis
371        self.w_name = Make_My_TextEntry(parent, this_axis.name,
372                        True, u'local name for this axis', 
373                        self.doConfigureHandler)
374        self.w_VAL_pv = Make_My_TextEntry(parent, 
375                            this_axis.axis.get_pv('VAL').pvname,
376                            True, u'EPICS PV name for target position', 
377                            self.doConfigureHandler)
378        self.w_isMotor = wx.CheckBox( parent )
379        self.w_isMotor.SetValue( this_axis.isMotorRecord )
380        self.w_isMotor.SetLabel( 'Is VAL a motor record?' )
381        self.w_isMotor.SetToolTipString( 'Is VAL an EPICS "motor" record?' )
382        self.w_isMotor.Bind(wx.EVT_CHECKBOX, self.doCheckboxClick)
383       
384        self.w_RBV_pv = Make_My_TextEntry(parent, 
385                            this_axis.axis.get_pv('RBV').pvname,
386                            True, u'EPICS PV name for readback position', 
387                            self.doConfigureHandler)
388        self.w_EGU_pv = Make_My_TextEntry(parent, 
389                            this_axis.axis.get_pv('EGU').pvname,
390                            True, u'EPICS PV name for engineering units', 
391                            self.doConfigureHandler)
392        self.w_DESC_pv = Make_My_TextEntry(parent, 
393                            this_axis.axis.get_pv('DESC').pvname,
394                            True, u'EPICS PV name for description', 
395                            self.doConfigureHandler)
396        self.w_DMOV_pv = Make_My_TextEntry(parent, 
397                            this_axis.axis.get_pv('DMOV').pvname,
398                            True, u'EPICS PV name for motion is done', 
399                            self.doConfigureHandler)
400        self.w_STOP_pv = Make_My_TextEntry(parent, 
401                            this_axis.axis.get_pv('STOP').pvname,
402                            True, u'EPICS PV name for STOP moving command', 
403                            self.doConfigureHandler)
404        if this_axis.isMotorRecord:
405            val_pv = this_axis.axis.get_pv('VAL').pvname
406            self.w_VAL_pv.SetValue( m_pv.GetBasePv(val_pv) )
407            self.w_RBV_pv.SetValue( '' )
408            self.w_EGU_pv.SetValue( '' )
409            self.w_DESC_pv.SetValue( '' )
410            self.w_DMOV_pv.SetValue( '' )
411            self.w_STOP_pv.SetValue( '' )
412       
413        sbox = wx.StaticBox(parent, id=wx.ID_ANY,
414              label='PV description', style=0)
415        sbs = wx.StaticBoxSizer(sbox, wx.VERTICAL)
416        fgs = wx.FlexGridSizer(rows=8, cols=2, hgap=4, vgap=4)
417        fgs.AddGrowableCol(1)
418        sbs.Add(fgs, 0, wx.EXPAND|wx.ALIGN_CENTRE|wx.ALL, 5)
419
420        for obj in (label_name,     self.w_name,
421                    label_VAL,      self.w_VAL_pv,
422                    label_isMotor,  self.w_isMotor,
423                    label_RBV,      self.w_RBV_pv,
424                    label_EGU,      self.w_EGU_pv,
425                    label_DESC,     self.w_DESC_pv,
426                    label_DMOV,     self.w_DMOV_pv,
427                    label_STOP,     self.w_STOP_pv
428                    ):
429            fgs.Add(obj, flag=wx.EXPAND|wx.GROW)
430
431        return sbs
432   
433    def _Make_Connect_Buttons_Sizer(self, parent):
434        '''
435        build a buttons row and place it in a sizer, return the sizer
436        '''
437        self.connectButton = Make_My_Button(parent, 
438              label=u'Connect', name='connectButton', 
439              tip=u'Connect this axis with EPICS', 
440              binding=self.doConnnectButton)
441        self.disconnectButton = Make_My_Button(parent, 
442              label=u'Disconnect', name='disconnectButton', 
443              tip=u'Disconnect this axis from EPICS', 
444              binding=self.doDisconnnectButton)
445
446        sizer = wx.BoxSizer(orient=wx.HORIZONTAL)
447        sizer.AddWindow(self.connectButton, proportion=1, flag=wx.EXPAND)
448        sizer.AddWindow(self.disconnectButton, proportion=1, flag=wx.EXPAND)
449        return sizer
450   
451    def _Make_Accept_Buttons_Sizer(self, parent):
452        '''
453        build a buttons row and place it in a sizer, return the sizer
454        '''
455        self.acceptButton = Make_My_Button(parent, 
456              label=u'Accept', name='acceptButton', 
457              tip=u'Accept the new PV names', 
458              binding=self.doAcceptButton)
459        self.revertButton = Make_My_Button(parent, 
460              label=u'Revert', name='revertButton', 
461              tip=u'Revert to previous PV names', 
462              binding=self.doRevertButton)
463
464        sizer = wx.BoxSizer(orient=wx.HORIZONTAL)
465        sizer.AddWindow(self.acceptButton, proportion=1, flag=wx.EXPAND)
466        sizer.AddWindow(self.revertButton, proportion=1, flag=wx.EXPAND)
467        return sizer
468
469    def doConnnectButton(self, event):
470        '''user clicked button to connect'''
471        self.operate_panel.connect()
472
473    def doDisconnnectButton(self, event):
474        '''user clicked button to disconnect'''
475        self.operate_panel.disconnect()
476   
477    def doAcceptButton(self, event):
478        '''user clicked button to accept PV names'''
479        self.accept()
480        # switch back to operate page in notebook
481        self.parent.SetSelection( 0 )
482
483    def doRevertButton(self, event):
484        '''user clicked button to revert to existing PV name'''
485        self.revert()
486   
487    def doCheckboxClick(self, event):
488        ''' isMotorRecord checkbox was clicked '''
489        #print "isMotorRecord checkbox was clicked"
490        pass
491
492    def doConfigureHandler(self, event):
493        '''
494        User pressed [Enter] key in a configure panel TextCtrl.
495        Indicate with background color if the given PV name can be connected.
496       
497        Consider the best binding/trigger to call ``doConfigureHandler()``.
498        For now, we use choice #1.
499        Choices include:
500       
501        # [Enter] key (as used in ``wxmtxy`` tool)
502        # wx.EVT_TEXT events after a time delay
503        '''
504        obj = event.EventObject
505        if obj in (self.w_VAL_pv, 
506                                 self.w_RBV_pv, 
507                                 self.w_EGU_pv, 
508                                 self.w_DESC_pv, 
509                                 self.w_DMOV_pv, 
510                                 self.w_STOP_pv):
511            self._SetPvWidgetBackgroundColour( obj )
512   
513    def accept(self):
514        '''accept PV names'''
515        # filter out any cases why we should not accept
516        result = self._Verify_Fields_Before_Accepting()
517        if result is not None:
518            AcknowledgeDialog(self.parent, result )
519            return
520
521        # update the axis object with values from the widget fields
522        name = self.w_name.GetValue()
523        isMotorRecord = self.w_isMotor.GetValue()
524        val = self.w_VAL_pv.GetValue()
525        desc = self.w_DESC_pv.GetValue()
526        dmov = self.w_DMOV_pv.GetValue()
527        egu = self.w_EGU_pv.GetValue()
528        rbv = self.w_RBV_pv.GetValue()
529        stop = self.w_STOP_pv.GetValue()
530       
531        self.axis_parent.disconnect()
532        self.axis_parent.axis = m_axis.SingleAxis(name, val, isMotorRecord, desc, dmov, egu, rbv, stop)
533        self.axis_parent.connect()
534        self.operate_panel.SetVAL( str(self.axis_parent.axis.axis.VAL) )
535   
536    def revert(self):
537        '''revert to existing PV name'''
538        # restore widget fields from axis object
539        this_axis = self.axis_parent.axis
540        self.w_name.SetValue( this_axis.name )
541        val = this_axis.axis.get_pv('VAL').pvname
542        self.w_isMotor.SetValue( this_axis.isMotorRecord )
543        if this_axis.isMotorRecord:
544            self.w_VAL_pv.SetValue( m_pv.GetBasePv(val) )
545            self.w_RBV_pv.SetValue( '' )
546            self.w_EGU_pv.SetValue( '' )
547            self.w_DESC_pv.SetValue( '' )
548            self.w_DMOV_pv.SetValue( '' )
549            self.w_STOP_pv.SetValue( '' )
550        else:
551            self.w_VAL_pv.SetValue( val )
552            self.w_RBV_pv.SetValue( this_axis.axis.get_pv('RBV').pvname )
553            self.w_EGU_pv.SetValue( this_axis.axis.get_pv('EGU').pvname )
554            self.w_DESC_pv.SetValue( this_axis.axis.get_pv('DESC').pvname )
555            self.w_DMOV_pv.SetValue( this_axis.axis.get_pv('DMOV').pvname )
556            self.w_STOP_pv.SetValue( this_axis.axis.get_pv('STOP').pvname )
557        self._Set_All_PvWidgetBackgroundColours()
558   
559    def connect(self):
560        '''connect the widgets with the axis configuration'''
561        pass
562   
563    def disconnect(self):
564        '''disconnect the widgets from the axis configuration'''
565        pass
566
567    def _SetPvWidgetBackgroundColour(self, obj):
568        '''
569        Decide the background color of a PV widget
570        based on whether or not a PV connection can be
571        established from this client
572        and set the background color of that widget.
573        IF a connection attempt with a given PV name string
574        does not succeed within ``CONNECT_TEST_TIMEOUT`` seconds,
575        the PV will be considered as unavailable.
576       
577        =======================  =========================================
578        color                    meaning
579        =======================  =========================================
580        COLOR_PV_NOT_DEFINED     no string given for PV name
581        COLOR_PV_OK              string given can be connected as a PV
582        COLOR_PV_NOT_OK          PV connection timed out for string given
583        =======================  =========================================
584        '''
585        pv = obj.GetValue().strip()
586        if len(pv) == 0:
587            color = COLOR_PV_NOT_DEFINED
588        else:
589            valid = m_pv.pvIsAvailable( pv, CONNECT_TEST_TIMEOUT )
590            color = {True: COLOR_PV_OK, False: COLOR_PV_NOT_OK}[valid]
591        wx.CallAfter( obj.SetBackgroundColour, color )
592
593    def _Set_All_PvWidgetBackgroundColours(self):
594        '''
595        Walk through all the PV name widgets and check if they
596        can be connected.  Set the background color of each widget
597        accordingly.
598        '''
599        for obj in (self.w_VAL_pv,
600                   self.w_RBV_pv,
601                   self.w_EGU_pv,
602                   self.w_DESC_pv,
603                   self.w_DMOV_pv,
604                   self.w_STOP_pv):
605            self._SetPvWidgetBackgroundColour( obj )
606
607    def _Verify_Fields_Before_Accepting(self):
608        '''
609        filter out any cases why we should not accept::
610       
611            self.w_VAL_pv MUST be a valid PV
612            if self.w_isMotor, then
613                self.w_VAL_pv MUST be a motor record
614                AND
615                any other specified PV names MUST be valid
616            else,
617                all other PV names must be specified AND valid
618       
619        :return: None or string indicating reason to not accept
620        '''
621        val_pv = self.w_VAL_pv.GetValue()
622        if len( val_pv ) == 0:
623            return "Must define a PV name for the VAL field"
624        if not m_pv.pvIsAvailable(val_pv, CONNECT_TEST_TIMEOUT):
625            return '''cannot connect to PV "%s"''' % val_pv
626        if self.w_isMotor.GetValue():
627            if not m_pv.pvIsMotorRec(val_pv):
628                rtyp = m_pv.GetRTYP(val_pv)
629                return "%s is an EPICS %s record, must be 'motor'" % (val_pv, rtyp)
630            for obj in (self.w_RBV_pv,
631                   self.w_EGU_pv,
632                   self.w_DESC_pv,
633                   self.w_DMOV_pv,
634                   self.w_STOP_pv):
635                pv = obj.GetValue()
636                if len(pv) > 0:
637                    if not m_pv.pvIsAvailable(pv, CONNECT_TEST_TIMEOUT):
638                        return '''cannot connect to PV "%s"''' % pv
639        else:
640            for obj in (self.w_RBV_pv,
641                       self.w_EGU_pv,
642                       self.w_DESC_pv,
643                       self.w_DMOV_pv,
644                       self.w_STOP_pv):
645                pv = obj.GetValue()
646                if len(pv) == 0:
647                    return "Must define a PV name for all fields"
648                if not m_pv.pvIsAvailable(pv, CONNECT_TEST_TIMEOUT):
649                    return '''cannot connect to PV "%s"''' % val_pv
650        return None
651
652
653class MotorControlsFrame(wx.Frame):
654    '''comment'''
655   
656    def __init__(self, parent, pv):
657        '''
658        modeless dialog that provides the motor controls panel from PyEpics
659       
660        :param obj parent: WX parent
661        :param str pv: EPICS PV name for the motor record
662        '''
663        wx.Frame.__init__(self, parent, wx.ID_ANY, 
664                          title="motor: %s" % pv,
665                          style=wx.DEFAULT_FRAME_STYLE|wx.TAB_TRAVERSAL)
666        style = "full"     # small | medium | full (default)
667        mp = epics.wx.MotorPanel(self, pv, psize=style)
668       
669        sizer = wx.BoxSizer( orient=wx.VERTICAL )
670        sizer.AddSizer( mp, 1, flag=wx.EXPAND )
671        self.SetSizer( sizer )
672        sizer.Fit(self)
673       
674        self.Show()
675        self.Raise()
676
677
678class DemoFrame(wx.Frame):
679    '''
680    Put the standard panel in a frame
681    '''
682    def __init__(self, parent, axis):
683        # frame cannot be maximized
684        style = wx.MINIMIZE_BOX | \
685                wx.SYSTEM_MENU | \
686                wx.CAPTION | \
687                wx.CLOSE_BOX | \
688                wx.CLIP_CHILDREN | \
689                wx.RESIZE_BORDER
690        wx.Frame.__init__(self, parent, wx.ID_ANY, 'DemoView', style=style)
691        self.SetSize( (300,400) )
692        self.SetToolTipString(u'Single Axis DemoFrame')
693        self.panel = SingleAxisPanel(parent=self, axis=axis)
694        self.Bind(wx.EVT_CLOSE, self.OnClose)
695        self.panel.connect()
696
697    def OnClose(self, event):
698        dlg = wx.MessageDialog(self, 
699            "Do you really want to close this application?",
700            "Confirm Exit", wx.OK|wx.CANCEL|wx.ICON_QUESTION)
701        #result = wx.ID_OK               # development
702        result = dlg.ShowModal()        # production
703        dlg.Destroy()
704        if result == wx.ID_OK:
705            self.panel.axis.disconnect()
706            self.Destroy()
707
708
709# - - - - - - - - - - - - - - - - - - methods
710
711
712   
713def AcknowledgeDialog(parent, msg):
714    ''' present a modal dialog box with a message to be acknowledged '''
715    dlg = wx.MessageDialog(parent, msg,
716                "Acknowledge", wx.OK|wx.ICON_EXCLAMATION)
717    dlg.ShowModal()
718    dlg.Destroy()
719
720
721def Make_My_Button(parent, label, name, tip, binding):
722    '''
723    Create a button and bind it to a method.
724    Return the button object
725    '''
726    button = wx.lib.buttons.GenButton(id=wx.ID_ANY,
727          label=label, name=name, parent=parent,)
728    button.SetToolTipString(tip)
729    button.Bind(wx.EVT_BUTTON, binding)
730    return button
731
732
733def Make_My_TextEntry(parent, value='', 
734                       editable=True, tooltip='', handler=None):
735    '''
736    Create and return a TextCtrl object.
737    [Enter] key event is bound to widget to call handler.
738   
739    :param obj parent: widget panel that will contain this TextCtrl
740    :param str|float value: initial value to set in the entry field
741    :param bool editable: should the entry field be editable?
742    :param str tooltip: short description of this field
743    :param obj handler: None or method to be called when [Enter] is pressed in this field
744    :return: wx.TextCtrl object
745    '''
746    style = wx.TE_PROCESS_ENTER
747    entry = wx.TextCtrl(parent=parent, id=wx.ID_ANY, style=style)
748    entry.SetEditable( editable )
749    if value is not None:
750        entry.SetValue( str(value) )
751    entry.SetToolTipString( tooltip )
752    if handler is not None:
753        entry.Bind(wx.EVT_TEXT_ENTER, handler)
754    return entry
755
756
757# - - - - - - - - - - - - - - - - - - main
758
759
760if __name__ == '__main__':
761    axis = m_axis.SingleAxis( name='test axis', val='prj:m1' )
762    app = wx.App()
763    DemoFrame( None, axis ).Show()
764    app.MainLoop()
Note: See TracBrowser for help on using the repository browser.