source: wxmtxy/trunk/src/moxy/pair.py @ 631

Last change on this file since 631 was 631, checked in by jemian, 12 years ago

begin refactoring into release structure and rebranding with new product name: moxy

  • Property svn:eol-style set to native
  • Property svn:executable set to *
  • Property svn:keywords set to Date Revision Author HeadURL Id
File size: 17.9 KB
Line 
1#!/usr/bin/env python
2#Boa:FramePanel:XYpair
3
4#*************************************************************************
5# Copyright (c) 2009-2010 The University of Chicago, as Operator of Argonne
6#     National Laboratory.
7# Copyright (c) 2009-2010 The Regents of the University of California, as
8#     Operator of Los Alamos National Laboratory.
9# This file is distributed subject to a Software License Agreement found
10# in the file LICENSE that is included with this distribution.
11#*************************************************************************
12
13'''
14Define the GUI elements and interface for one X,Y pair
15
16@version:
17########### SVN repository information ###################
18# $Date: 2011-09-09 17:12:01 +0000 (Fri, 09 Sep 2011) $
19# $Author: jemian $
20# $Revision: 631 $
21# $URL: wxmtxy/trunk/src/moxy/pair.py $
22# $Id: pair.py 631 2011-09-09 17:12:01Z jemian $
23########### SVN repository information ###################
24'''
25
26import wx
27import wx.lib.stattext
28import wxmtxy_tab
29import wxmtxy_row
30import wxmtxy_axis
31import wx.lib.scrolledpanel
32#import pvConnect
33import copy
34import pprint
35
36
37[wxID_XYPAIR, wxID_XYPAIRLBLMOTOR, wxID_XYPAIRLBLREADBACK, 
38 wxID_XYPAIRLBLTARGET, wxID_XYPAIRLBL_X_TITLE, wxID_XYPAIRLBL_Y_TITLE, 
39 wxID_XYPAIRSTOP, wxID_XYPAIRTABLE, wxID_XYPAIRTITLE, wxID_XYPAIRX_RBV, 
40 wxID_XYPAIRX_VAL, wxID_XYPAIRY_RBV, wxID_XYPAIRY_VAL, 
41] = [wx.NewId() for _init_ctrls in range(13)]
42
43
44class XYpair(wx.Panel):
45    '''Table of settings for a specified X,Y pair of EPICS motors'''
46
47    # see:  http://wiki.wxpython.org/BoaFAQ   
48    _custom_classes = {
49        'wx.lib.scrolledpanel.ScrolledPanel': ['Tab'],
50        'wx.Dialog': ['PvDialog']
51    }
52   
53    COLOR_MOVING = wx.Colour(179, 250, 142)     # pale green
54    COLOR_NOT_MOVING = wx.Colour(200, 191, 140) # default below
55    COLOR_NOT_MOVING = wx.Colour(237, 233, 227) # Boa shows this one, which?
56
57    def _init_coll_fgsEpics_Items(self, parent):
58        # generated method, don't edit
59
60        parent.AddWindow(self.lblMotor, 0, border=2,
61              flag=wx.ALL | wx.ALIGN_CENTER_VERTICAL | wx.GROW)
62        parent.AddWindow(self.lbl_x_title, 1, border=2,
63              flag=wx.ALL | wx.ALIGN_CENTER_VERTICAL | wx.GROW)
64        parent.AddWindow(self.lbl_y_title, 1, border=2,
65              flag=wx.ALL | wx.ALIGN_CENTER_VERTICAL | wx.GROW)
66        parent.AddWindow(self.lblReadback, 0, border=2,
67              flag=wx.ALL | wx.ALIGN_CENTER_VERTICAL | wx.GROW)
68        parent.AddWindow(self.x_rbv, 1, border=2, flag=wx.ALL | wx.GROW)
69        parent.AddWindow(self.y_rbv, 1, border=2, flag=wx.ALL | wx.GROW)
70        parent.AddWindow(self.lblTarget, 0, border=2,
71              flag=wx.ALL | wx.ALIGN_CENTER_VERTICAL | wx.GROW)
72        parent.AddWindow(self.x_val, 1, border=2, flag=wx.ALL | wx.GROW)
73        parent.AddWindow(self.y_val, 1, border=2, flag=wx.ALL | wx.GROW)
74
75    def _init_coll_sizer_Items(self, parent):
76        # generated method, don't edit
77
78        parent.AddWindow(self.title, 0, border=0, flag=wx.GROW)
79        parent.AddSizer(self.fgsEpics, 0, border=4, flag=wx.ALL | wx.GROW)
80        parent.AddWindow(self.stop, 0, border=0, flag=wx.ALIGN_CENTER)
81        parent.AddWindow(self.table, 1, border=0, flag=wx.GROW)
82
83    def _init_coll_fgsEpics_Growables(self, parent):
84        # generated method, don't edit
85
86        parent.AddGrowableCol(1)
87        parent.AddGrowableCol(2)
88
89    def _init_sizers(self):
90        # generated method, don't edit
91        self.sizer = wx.BoxSizer(orient=wx.VERTICAL)
92
93        self.fgsEpics = wx.FlexGridSizer(cols=3, hgap=4, rows=3, vgap=4)
94
95        self._init_coll_sizer_Items(self.sizer)
96        self._init_coll_fgsEpics_Items(self.fgsEpics)
97        self._init_coll_fgsEpics_Growables(self.fgsEpics)
98
99        self.SetSizer(self.sizer)
100
101    def _init_ctrls(self, prnt):
102        # generated method, don't edit
103        wx.Panel.__init__(self, id=wxID_XYPAIR, name='XYpair', parent=prnt,
104              pos=wx.Point(532, 345), size=wx.Size(408, 274),
105              style=wx.TRANSPARENT_WINDOW | wx.TAB_TRAVERSAL)
106        self.SetClientSize(wx.Size(400, 240))
107        self.SetMinSize(wx.Size(240, 240))
108
109        self.title = wx.lib.stattext.GenStaticText(ID=wxID_XYPAIRTITLE,
110              label='Window Title', name='title', parent=self, pos=wx.Point(0,
111              0), size=wx.Size(400, 23), style=wx.ALIGN_CENTRE)
112        self.title.SetFont(wx.Font(15, wx.SWISS, wx.NORMAL, wx.NORMAL, False,
113              u'Arial'))
114        self.title.Center(wx.BOTH)
115        self.title.SetToolTipString('description of this X,Y set')
116
117        self.lblMotor = wx.lib.stattext.GenStaticText(ID=wxID_XYPAIRLBLMOTOR,
118              label='motor', name='lblMotor', parent=self, pos=wx.Point(6, 29),
119              size=wx.Size(80, 20), style=wx.ALIGN_CENTRE)
120        self.lblMotor.SetToolTipString(u'values obtained from EPICS')
121        self.lblMotor.Center(wx.BOTH)
122        self.lblMotor.SetBackgroundColour(wx.Colour(200, 191, 140))
123        self.lblMotor.SetMinSize(wx.Size(80, 20))
124
125        self.lbl_x_title = wx.lib.stattext.GenStaticText(ID=wxID_XYPAIRLBL_X_TITLE,
126              label='title_x', name='lbl_x_title', parent=self, pos=wx.Point(94,
127              29), size=wx.Size(146, 20), style=wx.ALIGN_CENTRE)
128        self.lbl_x_title.SetToolTipString('name and units of X axis')
129        self.lbl_x_title.Center(wx.BOTH)
130        self.lbl_x_title.SetBackgroundColour(wx.Colour(200, 191, 140))
131        self.lbl_x_title.SetMinSize(wx.Size(80, 20))
132
133        self.lbl_y_title = wx.lib.stattext.GenStaticText(ID=wxID_XYPAIRLBL_Y_TITLE,
134              label='title_y', name='lbl_y_title', parent=self,
135              pos=wx.Point(248, 29), size=wx.Size(146, 20),
136              style=wx.ALIGN_CENTRE)
137        self.lbl_y_title.SetToolTipString(u'name and units of Y axis')
138        self.lbl_y_title.Center(wx.BOTH)
139        self.lbl_y_title.SetBackgroundColour(wx.Colour(200, 191, 140))
140        self.lbl_y_title.SetMinSize(wx.Size(80, 20))
141
142        self.lblReadback = wx.lib.stattext.GenStaticText(ID=wxID_XYPAIRLBLREADBACK,
143              label='readback', name='lblReadback', parent=self, pos=wx.Point(6,
144              57), size=wx.Size(80, 20), style=wx.ALIGN_CENTRE)
145        self.lblReadback.SetToolTipString(u'indicates current position')
146        self.lblReadback.Center(wx.BOTH)
147        self.lblReadback.SetBackgroundColour(wx.Colour(200, 191, 140))
148        self.lblReadback.SetMinSize(wx.Size(80, 20))
149
150        self.x_rbv = wx.lib.stattext.GenStaticText(ID=wxID_XYPAIRX_RBV,
151              label='x_rbv', name='x_rbv', parent=self, pos=wx.Point(94, 57),
152              size=wx.Size(146, 20), style=wx.ALIGN_CENTRE)
153        self.x_rbv.SetToolTipString(u'X axis readback value')
154        self.x_rbv.Center(wx.BOTH)
155        self.x_rbv.SetMinSize(wx.Size(80, 20))
156
157        self.y_rbv = wx.lib.stattext.GenStaticText(ID=wxID_XYPAIRY_RBV,
158              label='y_rbv', name='y_rbv', parent=self, pos=wx.Point(248, 57),
159              size=wx.Size(146, 20), style=wx.ALIGN_CENTRE)
160        self.y_rbv.SetToolTipString(u'Y axis readback value')
161        self.y_rbv.Center(wx.BOTH)
162        self.y_rbv.SetMinSize(wx.Size(80, 20))
163
164        self.lblTarget = wx.lib.stattext.GenStaticText(ID=wxID_XYPAIRLBLTARGET,
165              label='target', name='lblTarget', parent=self, pos=wx.Point(6,
166              85), size=wx.Size(80, 20), style=wx.ALIGN_CENTRE)
167        self.lblTarget.SetToolTipString(u'also known as "commanded value"')
168        self.lblTarget.Center(wx.BOTH)
169        self.lblTarget.SetBackgroundColour(wx.Colour(200, 191, 140))
170        self.lblTarget.SetMinSize(wx.Size(80, 20))
171
172        self.x_val = wx.lib.stattext.GenStaticText(ID=wxID_XYPAIRX_VAL,
173              label='x_val', name='x_val', parent=self, pos=wx.Point(94, 85),
174              size=wx.Size(146, 20), style=wx.ALIGN_CENTRE)
175        self.x_val.SetToolTipString(u'X axis target value')
176        self.x_val.Center(wx.BOTH)
177        self.x_val.SetMinSize(wx.Size(80, 20))
178
179        self.y_val = wx.lib.stattext.GenStaticText(ID=wxID_XYPAIRY_VAL,
180              label='y_val', name='y_val', parent=self, pos=wx.Point(248, 85),
181              size=wx.Size(146, 20), style=wx.ALIGN_CENTRE)
182        self.y_val.SetToolTipString(u'Y axis target value')
183        self.y_val.Center(wx.BOTH)
184        self.y_val.SetMinSize(wx.Size(80, 20))
185
186        self.stop = wx.Button(id=wxID_XYPAIRSTOP, label='Stop', name='stop',
187              parent=self, pos=wx.Point(140, 111), size=wx.Size(120, 32),
188              style=0)
189        self.stop.SetBackgroundColour(wx.Colour(223, 0, 0))
190        self.stop.SetForegroundColour(wx.Colour(255, 255, 255))
191        self.stop.SetToolTipString(u'command EPICS to stop these two motors')
192        self.stop.SetHelpText(u'command EPICS to stop these two motors')
193        self.stop.SetFont(wx.Font(11, wx.SWISS, wx.NORMAL, wx.BOLD, False,
194              'Arial'))
195        self.stop.Bind(wx.EVT_BUTTON, self.OnStopButton, id=wxID_XYPAIRSTOP)
196
197        self.table = wx.Notebook(id=wxID_XYPAIRTABLE, name='table', parent=self,
198              pos=wx.Point(0, 143), size=wx.Size(400, 97), style=0)
199        self.table.SetMinSize(wx.Size(240, 50))
200        self.table.SetToolTipString(u'table of various settings for X,Y motors"')
201
202        self._init_sizers()
203
204    def __init_names__(self):
205        '''cross-reference the widgets to a dictionary'''
206        self.widget = {"x": {}, "y": {}}
207        self.widget["x"]["VAL"] = self.x_val
208        self.widget["y"]["VAL"] = self.y_val
209        self.widget["x"]["RBV"] = self.x_rbv
210        self.widget["y"]["RBV"] = self.y_rbv
211        self.widget["x"]["title"] = self.lbl_x_title
212        self.widget["y"]["title"] = self.lbl_y_title
213
214    def __init__(self, parent, name, root, rootCallback, newtab=False):
215        '''initialize an instance of this class
216            @param parent: object that owns this class
217            @param name: display test that describes this XYpair
218            @param root: root object
219            @param rootCallback: routine in the parent to handle Button events from the Tab
220            @param newtab: [Boolean] create a default Tab group?'''
221        self.tab_count = 0
222        self.epics = {}
223        self.titles = {}
224        for axis in ['x', 'y']:
225            self.epics[axis] = wxmtxy_axis.Axis()
226            self.titles[axis] = {}
227            for field in ['DESC', 'EGU']:
228                self.titles[axis][field] = ""
229        self._init_ctrls(parent)
230        self.__init_names__()   # build a cross-reference
231        self.parent = parent
232        self.SetName(name)
233        self.root = root
234        self.rootCallback = rootCallback
235        self.SetAxisTitles('X axis, egu', 'Y axis, egu')
236        if newtab == True:
237            self.NewTab()
238        self.Layout()
239        #self.SetMotorColor("x", True)
240        #self.SetMotorColor("y", False)
241        self.SetEpicsConfig({'x': {}, 'y': {}})
242
243# ################################
244# ##       added methods       ###
245# ################################
246
247    def NewTab(self, newrow=True):
248        '''make a new tab
249           @param newrow: [Boolean] option to create a first row'''
250        panel = wxmtxy_tab.Tab(parent=self.table, pair=self,
251                       pairCallback=self.TabHandler, newrow=newrow)
252        self.tab_count += 1
253        self.table.AddPage(imageId=-1, page=panel, select=True,
254               text='tab ' + repr(self.tab_count))
255        return panel
256
257    def DeleteTab(self):
258        '''Delete the given tab'''
259        tabnum = self.GetSelection()
260        if tabnum < 0:
261            return 'No tab to delete.'
262        else:
263            text = self.GetTabText(tabnum)
264            self.table.DeletePage(tabnum)
265            return 'Deleted tab named: ' + text
266
267    def TabHandler(self, theTab, theRow, command):
268        '''Callback function to handle a command from a tab
269           @param theTab: wxmtxy_tab.Tab object
270           @param theRow: wxmtxy_row.Row object
271           @param command: [string] Row button action to pass upward for handling'''
272        self.rootCallback(self, theTab, theRow, command)
273
274    def GetSelection(self):
275        '''@return index number of the selected tab object'''
276        return self.table.GetSelection()
277
278    def GetTabSelection(self):
279        '''@return selected tab object'''
280        tabnum = self.GetSelection()
281        if tabnum < 0:
282            return None
283        return self.table.GetPage(tabnum)
284
285    def GetPageTitle(self):
286        '''@return page title'''
287        return self.title.GetLabel()
288
289    def SetPageTitle(self, title):
290        '''define the page title
291           @param title: [string] new page title'''
292        self.title.SetLabel(title)
293        self.Layout()
294
295    def GetTabText(self, tabnum):
296        '''return the text of the tab numbered tabnum
297           @param tabnum: [int] index of selected tab'''
298        return self.table.GetPageText(tabnum)
299
300    def SetTabText(self, tabnum, text):
301        '''set the text of the tab numbered tabnum
302           @param tabnum: [int] index of selected tab
303           @param text: [string] new text'''
304        self.table.SetPageText(tabnum, text)
305
306    def GetAxisTitleX(self):
307        '''@return X axis title'''
308        return self.lbl_x_title.GetLabel()
309
310    def GetAxisTitleY(self):
311        '''@return Y axis title'''
312        return self.lbl_y_title.GetLabel()
313
314    def SetMotorColor(self, axis, state):
315        '''change the background color of the RBV and VAL widgets
316       
317           @param axis: [string] either "x" or "y"
318           @param state: [Boolean], color is green if state == False, neutral if True
319        '''
320        colormap = {False: self.COLOR_MOVING, True: self.COLOR_NOT_MOVING}
321        rbv = {"x": self.x_rbv, "y": self.y_rbv}
322        val = {"x": self.x_val, "y": self.y_val}
323        rbv[axis].SetBackgroundColour(colormap[state])
324        val[axis].SetBackgroundColour(colormap[state])
325        self.Refresh()
326        self.Layout()
327
328    def GetRbvXY(self):
329        '''@return readback values for X and Y as a tuple'''
330        x = self.x_rbv.GetLabel()
331        y = self.y_rbv.GetLabel()
332        return x, y
333
334    def SetAxisTitles(self, x_title, y_title):
335        '''define the axis titles
336            @param x_title: [string] X axis title
337            @param y_title: [string] Y axis title'''
338        self.lbl_x_title.SetLabel(x_title)
339        self.lbl_y_title.SetLabel(y_title)
340        self.Layout()
341
342    def GetEpicsConfig(self):
343        '''@return deep copy of EPICS PV configuration'''
344        config = {}
345        for axis in ['x', 'y']:
346            config[axis] = copy.deepcopy(self.epics[axis].GetConfigure())
347        return config
348
349    def SetEpicsConfig(self, config):
350        '''define the EPICS PVs from a configuration
351           @param config: Python dictionary with axes configurations'''
352        for axis in ['x', 'y']:
353            self.epics[axis].SetConfigure(copy.deepcopy(config[axis]))
354
355    def CallbackPositions(self, epics_args, user_args):
356        '''receive a callback on the VAL and RBV fields'''
357        axis = user_args[0][0]
358        field = user_args[0][1]
359        value = epics_args['pv_value']
360        if epics_args.has_key('pv_precision'):
361            # what about display precision?
362            fmt = '%.' + str(epics_args['pv_precision'])
363            abs_value = abs(value)
364            if abs_value >= 1e5 or (abs_value < 1e-5 and abs_value > 0):
365                fmt += 'E'
366            else:
367                fmt += 'f'
368            str_value = fmt % value
369        else:
370            str_value = str(value)
371        self.widget[axis][field] .SetLabel(str_value)
372        self.Layout()
373
374    def CallbackTitle(self, epics_args, user_args):
375        '''receive a callback on the DESC and EGU fields'''
376        axis = user_args[0][0]
377        field = user_args[0][1]
378        value = epics_args['pv_value']
379        self.titles[axis][field] = value
380        title = "%s, %s" % (self.titles[axis]['DESC'], self.titles[axis]['EGU'])
381        self.widget[axis]['title'] .SetLabel(title)
382
383    def CallbackDMOV(self, epics_args, user_args):
384        '''receive a callback on the DMOV field'''
385        axis = user_args[0][0]
386        value = epics_args['pv_value']
387        self.SetMotorColor(axis, value == 1)
388
389    def ConnectEpics(self):
390        '''try to connect the XY_pair PV names with EPICS'''
391        #+++++++++++++++++++++++++++++++
392        # need to replace this starting from the example in wxmtxy_axis.main()
393        #+++++++++++++++++++++++++++++++
394        for axis in ['x', 'y']:
395            cfg = self.epics[axis].GetConfigure()
396            for field in ['VAL', 'RBV']:
397                item = self.epics[axis].db[field]
398                item.connection.SetUserCallback(self.CallbackPositions)
399                item.connection.SetUserArgs((axis, field))
400                item.SetWidget(self.widget[axis][field] .SetLabel)
401
402            # advanced handling
403            desc = self.epics[axis].db['DESC']
404            desc.connection.SetUserCallback(self.CallbackTitle)
405            desc.connection.SetUserArgs((axis, 'DESC'))
406
407            egu = self.epics[axis].db['EGU']
408            egu.connection.SetUserCallback(self.CallbackTitle)
409            egu.connection.SetUserArgs((axis, 'EGU'))
410
411            dmov = self.epics[axis].db['DMOV']
412            dmov.connection.SetUserCallback(self.CallbackDMOV)
413            dmov.connection.SetUserArgs((axis, 'DMOV'))
414
415            # do not need to setup the STOP button
416
417            cfg['AXIS'] = axis
418            #pprint.pprint(cfg)
419            self.epics[axis].Connect()
420
421    def ReleaseEpics(self):
422        '''release connections with the XY_pair EPICS PVs
423            @note: When will this be called?'''
424        for axis in ['x', 'y']:
425            self.epics[axis].Disconnect()
426
427    def StopAxes(self):
428        '''Send a stop to both axes'''
429        for axis in ['x', 'y']:
430            #print __name__, 'Stop axis', axis
431            self.epics[axis].Stop()
432
433    def MoveAxes(self, x, y):
434        '''Command both axes to move to new position
435            @param x: [float] new X position
436            @param y: [float] new Y position'''
437        #print __name__, 'MoveAxes:', x, y
438        self.epics['x'].Move(x)
439        self.epics['y'].Move(y)
440               
441# ################################
442# ##  event handling routines  ###
443# ################################
444
445    def OnStopButton(self, event):
446        '''user requested to stop the X and Y motors
447           @param event: wxPython event object'''
448        self.TabHandler(self, None, 'stop')
Note: See TracBrowser for help on using the repository browser.