source: moxy/trunk/src/moxy/moxy_wx/axis.py @ 837

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

refactor common code

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