source: trunk/GSASIIrestrGUI.py @ 813

Last change on this file since 813 was 813, checked in by vondreele, 9 years ago

a bit more restraint fix

File size: 45.1 KB
Line 
1# -*- coding: utf-8 -*-
2#GSASIIrestr - restraint GUI routines
3########### SVN repository information ###################
4# $Date: 2012-12-05 15:38:26 -0600 (Wed, 05 Dec 2012) $
5# $Author: vondreele $
6# $Revision: 810 $
7# $URL: https://subversion.xor.aps.anl.gov/pyGSAS/trunk/GSASIIrestrGUI.py $
8# $Id: GSASIIrestrGUI.py 810 2012-12-05 21:38:26Z vondreele $
9########### SVN repository information ###################
10import wx
11import wx.grid as wg
12import time
13import numpy as np
14import numpy.ma as ma
15import os.path
16import GSASIIpath
17GSASIIpath.SetVersionNumber("$Revision: 810 $")
18import GSASIImath as G2mth
19import GSASIIlattice as G2lat
20import GSASIIphsGUI as G2phG
21import GSASIIspc as G2spc
22import GSASIIgrid as G2gd
23
24VERY_LIGHT_GREY = wx.Colour(235,235,235)
25
26################################################################################
27#####  Restraints
28################################################################################           
29       
30def UpdateRestraints(G2frame,data,Phases,phaseName):
31    if not len(Phases):
32        print 'There are no phases to form restraints'
33        return
34    phasedata = Phases[phaseName]
35    if phaseName not in data:
36        data[phaseName] = {}
37    restrData = data[phaseName]
38    if 'Bond' not in restrData:
39        restrData['Bond'] = {'wtFactor':1.0,'Bonds':[],'Use':True}
40    if 'Angle' not in restrData:
41        restrData['Angle'] = {'wtFactor':1.0,'Angles':[],'Use':True}
42    if 'Plane' not in restrData:
43        restrData['Plane'] = {'wtFactor':1.0,'Planes':[],'Use':True}
44    if 'Chiral' not in restrData:
45        restrData['Chiral'] = {'wtFactor':1.0,'Volumes':[],'Use':True}
46    if 'Torsion' not in restrData:
47        restrData['Torsion'] = {'wtFactor':1.0,'Coeff':{},'Torsions':[],'Use':True}
48    if 'Rama' not in restrData:
49        restrData['Rama'] = {'wtFactor':1.0,'Coeff':{},'Ramas':[],'Use':True}
50    General = phasedata['General']
51    Cell = General['Cell'][1:7]          #skip flag & volume   
52    Amat,Bmat = G2lat.cell2AB(Cell)
53    SGData = General['SGData']
54    cx,ct = General['AtomPtrs'][:2]
55    Atoms = phasedata['Atoms']
56    AtLookUp = G2mth.FillAtomLookUp(Atoms)
57    if 'macro' in General['Type']:
58        Names = [atom[0]+atom[1]+atom[2]+' '+atom[3] for atom in Atoms]
59        Ids = []
60        Coords = []
61        Types = []
62    else:   
63        Names = ['all '+ name for name in General['AtomTypes']]
64        iBeg = len(Names)
65        Types = [name for name in General['AtomTypes']]
66        Coords = [ [] for type in Types]
67        Ids = [ 0 for type in Types]
68        Names += [atom[ct-1] for atom in Atoms]
69    Types += [atom[ct] for atom in Atoms]
70    Coords += [atom[cx:cx+3] for atom in Atoms]
71    Ids += [atom[-1] for atom in Atoms]
72   
73    def OnSelectPhase(event):
74        dlg = wx.SingleChoiceDialog(G2frame,'Select','Phase',Phases.keys())
75        try:
76            if dlg.ShowModal() == wx.ID_OK:
77                phaseName = Phases.keys()[dlg.GetSelection()]
78                UpdateRestraints(G2frame,data,Phases,phaseName)
79        finally:
80            dlg.Destroy()
81   
82    def getMacroFile(macName):
83        defDir = os.path.join(os.path.split(__file__)[0],'GSASIImacros')
84        dlg = wx.FileDialog(G2frame,message='Choose '+macName+' restraint macro file',
85            defaultDir=defDir,defaultFile="",wildcard="GSAS-II macro file (*.mac)|*.mac",
86            style=wx.OPEN | wx.CHANGE_DIR)
87        try:
88            if dlg.ShowModal() == wx.ID_OK:
89                macfile = dlg.GetPath()
90                macro = open(macfile,'Ur')
91                head = macro.readline()
92                if macName not in head:
93                    print head
94                    print '**** ERROR - wrong restraint macro file selected, try again ****'
95                    macro = []
96            else: # cancel was pressed
97                macxro = []
98        finally:
99            dlg.Destroy()
100        return macro        #advanced past 1st line
101       
102    def OnAddRestraint(event):
103        page = G2frame.dataDisplay.GetSelection()
104        if 'Bond' in G2frame.dataDisplay.GetPageText(page):
105            bondRestData = restrData['Bond']
106            AddBondRestraint(bondRestData)
107        elif 'Angle' in G2frame.dataDisplay.GetPageText(page):
108            angleRestData = restrData['Angle']
109            AddAngleRestraint(angleRestData)
110        elif 'Plane' in G2frame.dataDisplay.GetPageText(page):
111            AddPlaneRestraint()
112        elif 'Chiral' in G2frame.dataDisplay.GetPageText(page):
113            AddChiralRestraint()
114        elif 'Torsion' in G2frame.dataDisplay.GetPageText(page):
115            AddTorsionRestraint()
116        elif 'Rama' in G2frame.dataDisplay.GetPageText(page):
117            AddRamaRestraint()
118           
119    def OnAddAARestraint(event):
120        page = G2frame.dataDisplay.GetSelection()
121        if 'Bond' in G2frame.dataDisplay.GetPageText(page):
122            bondRestData = restrData['Bond']
123            AddAABondRestraint(bondRestData)
124        elif 'Angle' in G2frame.dataDisplay.GetPageText(page):
125            angleRestData = restrData['Angle']
126            AddAAAngleRestraint(angleRestData)
127        elif 'Plane' in G2frame.dataDisplay.GetPageText(page):
128            AddAAPlaneRestraint()
129        elif 'Chiral' in G2frame.dataDisplay.GetPageText(page):
130            AddAAChiralRestraint()
131        elif 'Torsion' in G2frame.dataDisplay.GetPageText(page):
132            AddAATorsionRestraint()
133        elif 'Rama' in G2frame.dataDisplay.GetPageText(page):
134            AddAARamaRestraint()
135           
136    def AddBondRestraint(bondRestData):
137        Radii = dict(zip(General['AtomTypes'],General['BondRadii']))
138        Lists = {'origin':[],'target':[]}
139        for listName in ['origin','target']:
140            dlg = wx.MultiChoiceDialog(G2frame,'Bond restraint '+listName+' for '+General['Name'],
141                    'Select bond restraint '+listName+' atoms',Names)
142            if dlg.ShowModal() == wx.ID_OK:
143                sel = dlg.GetSelections()
144                for x in sel:
145                    if 'all' in Names[x]:
146                        allType = Types[x]
147                        for name,Type,coords,id in zip(Names,Types,Coords,Ids):
148                            if Type == allType and 'all' not in name:
149                                Lists[listName].append([id,Type,coords])
150                    else:
151                        Lists[listName].append([Ids[x],Types[x],Coords[x],])
152        Factor = .85
153        indices = (-1,0,1)
154        Units = np.array([[h,k,l] for h in indices for k in indices for l in indices])
155        origAtoms = Lists['origin']
156        targAtoms = Lists['target']
157        dlg = wx.ProgressDialog("Generating bond restraints","Processed origin atoms",len(origAtoms), 
158            style = wx.PD_ELAPSED_TIME|wx.PD_AUTO_HIDE|wx.PD_REMAINING_TIME)
159        try:
160            Norig = 0
161            for Oid,Otype,Ocoord in origAtoms:
162                Norig += 1
163                dlg.Update(Norig)
164                for Tid,Ttype,Tcoord in targAtoms:
165                    if 'macro' in General['Type']:
166                        result = [[Tcoord,1,[0,0,0]],]
167                    else:
168                        result = G2spc.GenAtom(Tcoord,SGData,False,Move=False)
169                    BsumR = (Radii[Otype]+Radii[Ttype])*Factor
170                    for Txyz,Top,Tunit in result:
171                        Dx = (Txyz-np.array(Ocoord))+Units
172                        dx = np.inner(Amat,Dx)
173                        dist = ma.masked_less(np.sqrt(np.sum(dx**2,axis=0)),0.5)
174                        IndB = ma.nonzero(ma.masked_greater(dist-BsumR,0.))
175                        if np.any(IndB):
176                            for indb in IndB:
177                                for i in range(len(indb)):
178                                    unit = Units[indb][i]+Tunit
179                                    if np.any(unit):
180                                        Topstr = '%d+%d,%d,%d'%(Top,unit[0],unit[1],unit[2])
181                                    else:
182                                        Topstr = str(Top)
183                                    bondRestData['Bonds'].append([[Oid,Tid],['1',Topstr], \
184                                        ma.getdata(dist[indb])[i],1.54,0.01])
185        finally:
186            dlg.Destroy()
187        UpdateBondRestr(bondRestData)               
188
189    def AddAABondRestraint(bondRestData):
190        Radii = dict(zip(General['AtomTypes'],General['BondRadii']))
191        macro = getMacroFile('bond')
192        if not macro:
193            return
194        macStr = macro.readline()
195        atoms = zip(Names,Coords,Ids)
196       
197        Factor = .90
198        while macStr:
199            items = macStr.split()
200            if 'F' in items[0]:
201                restrData['Bond']['wtFactor'] = float(items[1])
202            elif 'S' in items[0]:
203                oIds = []
204                oCoords = []
205                tIds = []
206                tCoords = []
207                res = items[1]
208                dist = float(items[2])
209                esd = float(items[3])
210                oAtm,tAtm = items[4:6]
211                for Name,coords,Id in atoms:
212                    names = Name.split()
213                    if res == '*' or res in names[0]:
214                        if oAtm == names[2]:
215                            oIds.append(Id)
216                            oCoords.append(np.array(coords))
217                        if tAtm == names[2]:
218                            tIds.append(Id)
219                            tCoords.append(np.array(coords))
220                for i,[oId,oCoord] in enumerate(zip(oIds,oCoords)):
221                    for tId,tCoord in zip(tIds,tCoords)[i:]:
222                        obsd = np.sqrt(np.sum(np.inner(Amat,tCoord-oCoord)**2))
223                        if dist*Factor < obsd < dist/Factor:
224                            bondRestData['Bonds'].append([[oId,tId],['1','1'],obsd,dist,esd])                         
225            macStr = macro.readline()
226        macro.close()
227        UpdateBondRestr(bondRestData)               
228           
229    def AddAngleRestraint(angleRestData):
230        Radii = dict(zip(General['AtomTypes'],zip(General['BondRadii'],General['AngleRadii'])))
231        origAtoms = []
232        dlg = wx.MultiChoiceDialog(G2frame,'Select atom B for angle A-B-C for '+General['Name'],
233                'Select angle restraint origin atoms',Names)
234        if dlg.ShowModal() == wx.ID_OK:
235            sel = dlg.GetSelections()
236            for x in sel:
237                if 'all' in Names[x]:
238                    allType = Types[x]
239                    for name,Type,coords,id in zip(Names,Types,Coords,Ids):
240                        if Type == allType and 'all' not in name:
241                            origAtoms.append([id,Type,coords])
242                else:
243                    origAtoms.append([Ids[x],Types[x],Coords[x]])
244        targAtoms = [[Ids[x+iBeg],Types[x+iBeg],Coords[x+iBeg]] for x in range(len(Names[iBeg:]))]
245
246        Factor = 1.0
247        indices = (-1,0,1)
248        Units = np.array([[h,k,l] for h in indices for k in indices for l in indices])
249        VectA = []
250        for Oid,Otype,Ocoord in origAtoms:
251            IndBlist = []
252            angles = []
253            VectB = []
254            for Tid,Ttype,Tcoord in targAtoms:
255                result = G2spc.GenAtom(Tcoord,SGData,False,Move=False)
256                BsumR = (Radii[Otype][0]+Radii[Ttype][0])*Factor
257                AsumR = (Radii[Otype][1]+Radii[Ttype][1])*Factor
258                for Txyz,Top,Tunit in result:
259                    Dx = (Txyz-Ocoord)+Units
260                    dx = np.inner(Amat,Dx)
261                    dist = ma.masked_less(np.sqrt(np.sum(dx**2,axis=0)),0.5)
262                    IndB = ma.nonzero(ma.masked_greater(dist-BsumR,0.))
263                    if np.any(IndB):
264                        for indb in IndB:
265                            for i in range(len(indb)):
266                                if str(dx.T[indb][i]) not in IndBlist:
267                                    IndBlist.append(str(dx.T[indb][i]))
268                                    unit = Units[indb][i]+Tunit
269                                if np.any(unit):
270                                    Topstr = '%d+%d,%d,%d'%(Top,unit[0],unit[1],unit[2])
271                                else:
272                                    Topstr = str(Top)
273                                    tunit = '[%2d%2d%2d]'%(unit[0]+Tunit[0],unit[1]+Tunit[1],unit[2]+Tunit[2])
274                                    Dist = ma.getdata(dist[indb])[i]
275                                    if (Dist-AsumR) <= 0.:
276                                        VectB.append([Oid,'1',Ocoord,Tid,Topstr,Tcoord,Dist])
277            VectA.append(VectB)
278            for Vects in VectA:
279                for i,vecta in enumerate(Vects):                   
280                    for vectb in Vects[:i]:
281                        ids = [vecta[3],vecta[0],vectb[3]]
282                        ops = [vecta[4],vecta[1],vectb[4]]
283                        XYZ = np.array([vecta[5],vecta[2],vectb[5]])
284                        angle = G2mth.getRestAngle(XYZ,Amat)
285                        angles.append([ids,ops,angle,109.5,1.0])
286            angleRestData['Angles'] += angles
287        UpdateAngleRestr(angleRestData)               
288
289    def AddAAAngleRestraint(angleRestData):
290        macro = getMacroFile('angle')
291        if not macro:
292            return
293        macStr = macro.readline()
294        while macStr:
295            items = macStr.split()
296            print items
297            if 'F' in items[0]:
298                restrData['Angle']['wtFactor'] = float(items[1])
299            elif 'S' in items[0]:
300                List = []
301                res = items[1]
302                dist = items[2]
303                esd = items[3]
304                oAtm = items[4]
305                tAtm = items[5]
306                for name,Type,coords,id in zip(Names,Types,Coords,Ids):
307                    if res == '*' or res in name:
308                        if oAtm in name:
309                            oCoord = coords
310                            oId = id
311                            oName = name
312                        elif tAtm in name:
313                            tCoord = coords
314                            tId = id
315                            tName = name
316               
317            macStr = macro.readline()
318        macro.close()
319        UpdateAngleRestr(angleRestData)               
320       
321    def AddPlaneRestraint():
322        origAtoms = []
323        dlg = wx.MultiChoiceDialog(G2frame,'Select atom B for angle A-B-C for '+General['Name'],
324                'Select angle restraint origin atoms',Names)
325        if dlg.ShowModal() == wx.ID_OK:
326            sel = dlg.GetSelections()
327            for x in sel:
328                if 'all' in Names[x]:
329                    allType = Types[x]
330                    for name,Type,coords,id in zip(Names,Types,Coords,Ids):
331                        if Type == allType and 'all' not in name:
332                            origAtoms.append([id,Type,coords])
333                else:
334                    origAtoms.append([Ids[x],Types[x],Coords[x]])
335
336    def AddAAPlaneRestraint():
337        macro = getMacroFile('plane')
338        if not macro:
339            return
340        macStr = macro.readline()
341        while macStr:
342            items = macStr.split()
343            print items
344            if 'F' in items[0]:
345                restrData['Plane']['wtFactor'] = float(items[1])
346            elif 'S' in items[0]:
347                List = []
348                res = items[1]
349                dist = items[2]
350                esd = items[3]
351                oAtm = items[4]
352                tAtm = items[5]
353                for name,Type,coords,id in zip(Names,Types,Coords,Ids):
354                    if res == '*' or res in name:
355                        if oAtm in name:
356                            oCoord = coords
357                            oId = id
358                            oName = name
359                        elif tAtm in name:
360                            tCoord = coords
361                            tId = id
362                            tName = name
363               
364            macStr = macro.readline()
365        macro.close()
366
367    def AddChiralRestraint():
368        print 'Chiral restraint'
369       
370    def AddAAChiralRestraint():
371        macro = getMacroFile('chiral')
372        if not macro:
373            return
374        macStr = macro.readline()
375        while macStr:
376            items = macStr.split()
377            print items
378            if 'F' in items[0]:
379                restrData['Chiral']['wtFactor'] = float(items[1])
380            elif 'S' in items[0]:
381                List = []
382                res = items[1]
383                dist = items[2]
384                esd = items[3]
385                oAtm = items[4]
386                tAtm = items[5]
387                for name,Type,coords,id in zip(Names,Types,Coords,Ids):
388                    if res == '*' or res in name:
389                        if oAtm in name:
390                            oCoord = coords
391                            oId = id
392                            oName = name
393                        elif tAtm in name:
394                            tCoord = coords
395                            tId = id
396                            tName = name
397               
398            macStr = macro.readline()
399        macro.close()
400       
401    def AddTorsionRestraint():
402        print 'Torsion restraint'
403       
404    def AddAATorsionRestraint():
405        print 'Add AA Torsion'
406       
407    def AddRamaRestraint():
408        print 'Ramachandran restraint'
409               
410    def AddAARamaRestraint():
411        print 'Add AA Ramachandran'
412       
413    def WtBox(wind,restData):
414       
415        def OnWtFactor(event):
416            try:
417                value = float(wtfactor.GetValue())
418            except ValueError:
419                value = 1.0
420            restData['wtFactor'] = value
421            wtfactor.SetValue('%.2f'%(value))
422           
423        def OnUseData(event):
424            restData['Use'] = Obj.GetValue()
425
426        wtBox = wx.BoxSizer(wx.HORIZONTAL)
427        wtBox.Add(wx.StaticText(wind,-1,'Restraint weight factor:'),0,wx.ALIGN_CENTER_VERTICAL)
428        wtfactor = wx.TextCtrl(wind,-1,value='%.2f'%(restData['wtFactor']),style=wx.TE_PROCESS_ENTER)
429        wtfactor.Bind(wx.EVT_TEXT_ENTER,OnWtFactor)
430        wtfactor.Bind(wx.EVT_KILL_FOCUS,OnWtFactor)
431        wtBox.Add(wtfactor,0,wx.ALIGN_CENTER_VERTICAL)
432        useData = wx.CheckBox(wind,-1,label=' Use?')
433        useData.Bind(wx.EVT_CHECKBOX, OnUseData)
434        useData.SetValue(restData['Use'])       
435        wtBox.Add(useData,0,wx.ALIGN_CENTER_VERTICAL)
436        return wtBox
437       
438    def UpdateBondRestr(bondRestData):
439       
440        def OnColSort(event):
441            r,c = event.GetRow(),event.GetCol()
442            if r < 0 and c == 0:
443                names = G2mth.sortArray(table,0)
444                bonds = []
445                for name in names:
446                    idx = table.index(name)
447                    bonds.append(bondList[idx])
448                bondRestData['Bonds'] = bonds
449                UpdateBondRestr(bondRestData)               
450       
451        def OnChangeValue(event):
452            rows = Bonds.GetSelectedRows()
453            if not rows:
454                return
455            Bonds.ClearSelection()
456            val = bondList[rows[0]][3]
457            dlg = G2phG.SingleFloatDialog(G2frame,'New value','Enter new value for bond',val,[0.,5.],'%.4f')
458            if dlg.ShowModal() == wx.ID_OK:
459                parm = dlg.GetValue()
460                for r in rows:
461                    bondList[r][3] = parm
462            dlg.Destroy()
463            UpdateBondRestr(bondRestData)               
464
465        def OnChangeEsd(event):
466            rows = Bonds.GetSelectedRows()
467            if not rows:
468                return
469            Bonds.ClearSelection()
470            val = bondList[rows[0]][4]
471            dlg = G2phG.SingleFloatDialog(G2frame,'New value','Enter new esd for bond',val,[0.,1.],'%.4f')
472            if dlg.ShowModal() == wx.ID_OK:
473                parm = dlg.GetValue()
474                for r in rows:
475                    bondList[r][4] = parm
476            dlg.Destroy()
477            UpdateBondRestr(bondRestData)               
478                               
479        def OnDeleteRestraint(event):
480            rows = Bonds.GetSelectedRows()
481            if not rows:
482                return
483            Bonds.ClearSelection()
484            rows.sort()
485            rows.reverse()
486            for row in rows:
487                bondList.remove(bondList[row])
488            UpdateBondRestr(bondRestData)               
489           
490        BondRestr.DestroyChildren()
491        dataDisplay = wx.Panel(BondRestr)
492        mainSizer = wx.BoxSizer(wx.VERTICAL)
493        mainSizer.Add((5,5),0)
494        mainSizer.Add(WtBox(BondRestr,bondRestData),0,wx.ALIGN_CENTER_VERTICAL)
495
496        bondList = bondRestData['Bonds']
497        if len(bondList) and len(bondList[0]) == 6:   #patch
498            bondList = bondRestData['Bonds'] = []
499        if len(bondList):
500            table = []
501            rowLabels = []
502            Types = [wg.GRID_VALUE_STRING,]+3*[wg.GRID_VALUE_FLOAT+':10,3',]
503            if 'macro' in General['Type']:
504                colLabels = ['(res) A - (res) B','calc','obs','esd']
505                for i,[indx,ops,dcalc,dobs,esd] in enumerate(bondList):
506                    atoms = G2mth.GetAtomItemsById(Atoms,AtLookUp,indx,0,4)
507                    name = ''
508                    for atom in atoms:
509                        name += '('+atom[1]+atom[0].strip()+atom[2]+') '+atom[3]+' - '
510                    table.append([name[:-3],dcalc,dobs,esd])
511                    rowLabels.append(str(i))               
512            else:
513                colLabels = ['A+SymOp - B+SymOp','calc','obs','esd']
514                for i,[indx,ops,dcalc,dobs,esd] in enumerate(bondList):
515                    atoms = G2mth.GetAtomItemsById(Atoms,AtLookUp,indx,ct-1)
516                    table.append([atoms[0]+'+('+ops[0]+') - '+atoms[1]+'+('+ops[1]+')',dcalc,dobs,esd])
517                    rowLabels.append(str(i))
518            bondTable = G2gd.Table(table,rowLabels=rowLabels,colLabels=colLabels,types=Types)
519            Bonds = G2gd.GSGrid(BondRestr)
520            Bonds.SetTable(bondTable, True)
521            Bonds.AutoSizeColumns(False)
522            for r in range(len(bondList)):
523                for c in range(2):
524                    Bonds.SetReadOnly(r,c,True)
525                    Bonds.SetCellStyle(r,c,VERY_LIGHT_GREY,True)
526            Bonds.Bind(wg.EVT_GRID_LABEL_LEFT_DCLICK,OnColSort)
527            G2frame.dataFrame.Bind(wx.EVT_MENU, OnDeleteRestraint, id=G2gd.wxID_RESTDELETE)
528            G2frame.dataFrame.Bind(wx.EVT_MENU, OnChangeValue, id=G2gd.wxID_RESRCHANGEVAL)
529            G2frame.dataFrame.Bind(wx.EVT_MENU, OnChangeEsd, id=G2gd.wxID_RESTCHANGEESD)
530            mainSizer.Add(Bonds,0,)
531        else:
532            mainSizer.Add(wx.StaticText(BondRestr,-1,'No bond distance restraints for this phase'),0,)
533
534        BondRestr.SetSizer(mainSizer)
535        Size = mainSizer.Fit(G2frame.dataFrame)
536        Size[0] += 5
537        Size[1] += 50       #make room for tab
538        BondRestr.SetSize(Size)
539        BondRestr.SetScrollbars(10,10,Size[0]/10-4,Size[1]/10-1)
540        G2frame.dataFrame.setSizePosLeft(Size)
541       
542    def UpdateAngleRestr(angleRestData):
543       
544        def OnColSort(event):
545            r,c = event.GetRow(),event.GetCol()
546            if r < 0 and c == 0:
547                names = G2mth.sortArray(table,0)
548                angles = []
549                for name in names:
550                    idx = table.index(name)
551                    angles.append(angleList[idx])
552                angleRestData['Angles'] = angles
553                UpdateAngleRestr(angleRestData)               
554       
555        def OnChangeValue(event):
556            rows = Angles.GetSelectedRows()
557            if not rows:
558                return
559            Angles.ClearSelection()
560            val = angleList[rows[0]][3]
561            dlg = G2phG.SingleFloatDialog(G2frame,'New value','Enter new value for angle',val,[0.,360.],'%.2f')
562            if dlg.ShowModal() == wx.ID_OK:
563                parm = dlg.GetValue()
564                for r in rows:
565                    angleList[r][3] = parm
566            dlg.Destroy()
567            UpdateAngleRestr(angleRestData)               
568
569        def OnChangeEsd(event):
570            rows = Angles.GetSelectedRows()
571            if not rows:
572                return
573            Angles.ClearSelection()
574            val = angleList[rows[0]][4]
575            dlg = G2phG.SingleFloatDialog(G2frame,'New value','Enter new esd for angle',val,[0.,5.],'%.2f')
576            if dlg.ShowModal() == wx.ID_OK:
577                parm = dlg.GetValue()
578                for r in rows:
579                    angleList[r][4] = parm
580            dlg.Destroy()
581            UpdateAngleRestr(angleRestData)               
582                                           
583        def OnDeleteRestraint(event):
584            rows = Angles.GetSelectedRows()
585            if not rows:
586                return
587            rows.sort()
588            rows.reverse()
589            for row in rows:
590                angleList.remove(angleList[row])
591            UpdateAngleRestr(angleRestData)               
592           
593        AngleRestr.DestroyChildren()
594        dataDisplay = wx.Panel(AngleRestr)
595        mainSizer = wx.BoxSizer(wx.VERTICAL)
596        mainSizer.Add((5,5),0)
597        mainSizer.Add(WtBox(AngleRestr,angleRestData),0,wx.ALIGN_CENTER_VERTICAL)
598
599        angleList = angleRestData['Angles']
600        if len(angleList):
601            table = []
602            rowLabels = []
603            Types = [wg.GRID_VALUE_STRING,]+3*[wg.GRID_VALUE_FLOAT+':10,2',]
604            if 'macro' in General['Type']:
605                colLabels = ['(res) A - (res) B - (res) C','calc','obs','esd']
606                for i,[indx,ops,dcalc,dobs,esd] in enumerate(angleList):
607                    atoms = G2mth.GetAtomItemsById(Atoms,AtLookUp,indx,0,4)
608                    name = ''
609                    for atom in atoms:
610                        name += '('+atom[1]+atom[0].strip()+atom[2]+') '+atom[3]+' - '
611                    table.append([name[:-3],dcalc,dobs,esd])
612                    rowLabels.append(str(i))                               
613            else:
614                colLabels = ['A+SymOp - B+SymOp - C+SymOp','calc','obs','esd']
615                for i,[indx,ops,dcalc,dobs,esd] in enumerate(angleList):
616                    atoms = G2mth.GetAtomItemsById(Atoms,AtLookUp,indx,ct-1)
617                    table.append([atoms[0]+'+('+ops[0]+') - '+atoms[1]+'+('+ops[1]+') - '+atoms[2]+ \
618                    '+('+ops[2]+')',dcalc,dobs,esd])
619                    rowLabels.append(str(i))
620            angleTable = G2gd.Table(table,rowLabels=rowLabels,colLabels=colLabels,types=Types)
621            Angles = G2gd.GSGrid(AngleRestr)
622            Angles.SetTable(angleTable, True)
623            Angles.AutoSizeColumns(False)
624            for r in range(len(angleList)):
625                for c in range(2):
626                    Angles.SetReadOnly(r,c,True)
627                    Angles.SetCellStyle(r,c,VERY_LIGHT_GREY,True)
628            Angles.Bind(wg.EVT_GRID_LABEL_LEFT_DCLICK,OnColSort)
629            G2frame.dataFrame.Bind(wx.EVT_MENU, OnDeleteRestraint, id=G2gd.wxID_RESTDELETE)
630            G2frame.dataFrame.Bind(wx.EVT_MENU, OnChangeValue, id=G2gd.wxID_RESRCHANGEVAL)
631            G2frame.dataFrame.Bind(wx.EVT_MENU, OnChangeEsd, id=G2gd.wxID_RESTCHANGEESD)
632            mainSizer.Add(Angles,0,)
633        else:
634            mainSizer.Add(wx.StaticText(AngleRestr,-1,'No bond angle restraints for this phase'),0,)
635
636        AngleRestr.SetSizer(mainSizer)
637        Size = mainSizer.Fit(G2frame.dataFrame)
638        Size[0] += 5
639        Size[1] += 50      #make room for tab
640        AngleRestr.SetSize(Size)
641        AngleRestr.SetScrollbars(10,10,Size[0]/10-4,Size[1]/10-1)
642        G2frame.dataFrame.setSizePosLeft(Size)
643   
644    def UpdatePlaneRestr(planeRestData):
645       
646        items = G2frame.dataFrame.RestraintEdit.GetMenuItems()
647        for item in items:
648            if item.GetLabel() in ['Change value']:
649                item.Enable(False)
650
651        def OnChangeEsd(event):
652            rows = Planes.GetSelectedRows()
653            if not rows:
654                return
655            Planes.ClearSelection()
656            val = planeList[rows[0]][4]
657            dlg = G2phG.SingleFloatDialog(G2frame,'New value','Enter new esd for plane',val,[0.,5.],'%.2f')
658            if dlg.ShowModal() == wx.ID_OK:
659                parm = dlg.GetValue()
660                for r in rows:
661                    planeList[r][4] = parm
662            dlg.Destroy()
663            UpdatePlaneRestr(planeRestData)               
664                                           
665        def OnDeleteRestraint(event):
666            rows = Planes.GetSelectedRows()
667            if not rows:
668                return
669            rows.sort()
670            rows.reverse()
671            for row in rows:
672                planeList.remove(planeList[row])
673            UpdatePlaneRestr(planeRestData)               
674           
675        PlaneRestr.DestroyChildren()
676        dataDisplay = wx.Panel(PlaneRestr)
677        mainSizer = wx.BoxSizer(wx.VERTICAL)
678        mainSizer.Add((5,5),0)
679        mainSizer.Add(WtBox(PlaneRestr,planeRestData),0,wx.ALIGN_CENTER_VERTICAL)
680
681        planeList = planeRestData['Planes']
682        if len(planeList):
683            table = []
684            rowLabels = []
685            Types = [wg.GRID_VALUE_STRING,]+3*[wg.GRID_VALUE_FLOAT+':10,2',]
686            if 'macro' in General['Type']:
687                colLabels = ['(res) atom','calc','obs','esd']
688                for i,[indx,ops,dcalc,dobs,esd] in enumerate(planeList):
689                    atoms = G2mth.GetAtomItemsById(Atoms,AtLookUp,indx,0,4)
690                    name = ''
691                    for a,atom in enumerate(atoms):
692                        name += '('+atom[1]+atom[0].strip()+atom[2]+') '+atom[3]+' - '
693                        if (a+1)%3 == 0:
694                            name += '\n'
695                    table.append([name[:-3],dcalc,dobs,esd])
696                    rowLabels.append(str(i))
697            else:                               
698                colLabels = ['atom+SymOp','calc','obs','esd']
699                for i,[indx,ops,dcalc,dobs,esd] in enumerate(planeList):
700                    atoms = G2mth.GetAtomItemsById(Atoms,AtLookUp,indx,ct-1)
701                    atString = ''
702                    for a,atom in enumerate(atoms):
703                        atString += atom+'+ ('+ops[a]+'),'
704                        if (a+1)%3 == 0:
705                            atString += '\n'
706                    table.append([atString[:-1],dcalc,dobs,esd])
707                    rowLabels.append(str(i))
708            planeTable = G2gd.Table(table,rowLabels=rowLabels,colLabels=colLabels,types=Types)
709            Planes = G2gd.GSGrid(PlaneRestr)
710            Planes.SetTable(planeTable, True)
711            Planes.AutoSizeColumns(False)
712            Planes.AutoSizeRows(False)
713            for r in range(len(planeList)):
714                for c in range(3):
715                    Planes.SetReadOnly(r,c,True)
716                    Planes.SetCellStyle(r,c,VERY_LIGHT_GREY,True)
717            G2frame.dataFrame.Bind(wx.EVT_MENU, OnDeleteRestraint, id=G2gd.wxID_RESTDELETE)
718            G2frame.dataFrame.Bind(wx.EVT_MENU, OnChangeEsd, id=G2gd.wxID_RESTCHANGEESD)
719            mainSizer.Add(Planes,0,)
720        else:
721            mainSizer.Add(wx.StaticText(PlaneRestr,-1,'No plane restraints for this phase'),0,)
722
723        PlaneRestr.SetSizer(mainSizer)
724        Size = mainSizer.Fit(G2frame.dataFrame)
725        Size[0] += 5
726        Size[1] += 50       #make room for tab
727        PlaneRestr.SetSize(Size)
728        PlaneRestr.SetScrollbars(10,10,Size[0]/10-4,Size[1]/10-1)
729        G2frame.dataFrame.setSizePosLeft(Size)
730   
731    def UpdateChiralRestr(chiralRestData):
732
733        def OnDeleteRestraint(event):
734            rows = Volumes.GetSelectedRows()
735            if not rows:
736                return
737            rows.sort()
738            rows.reverse()
739            for row in rows:
740                volumeList.remove(volumeList[row])
741            UpdateChiralRestr(chiralRestData)               
742           
743        def OnChangeValue(event):
744            rows = Volumes.GetSelectedRows()
745            if not rows:
746                return
747            Volumes.ClearSelection()
748            val = volumeList[rows[0]][3]
749            dlg = G2phG.SingleFloatDialog(G2frame,'New value','Enter new value for chiral volume',val,[0.,360.],'%.2f')
750            if dlg.ShowModal() == wx.ID_OK:
751                parm = dlg.GetValue()
752                for r in rows:
753                    volumeList[r][3] = parm
754            dlg.Destroy()
755            UpdateChiralRestr(chiralRestData)               
756
757        def OnChangeEsd(event):
758            rows = Volumes.GetSelectedRows()
759            if not rows:
760                return
761            Volumes.ClearSelection()
762            val = volumeList[rows[0]][4]
763            dlg = G2phG.SingleFloatDialog(G2frame,'New value','Enter new esd for chiral volume',val,[0.,5.],'%.2f')
764            if dlg.ShowModal() == wx.ID_OK:
765                parm = dlg.GetValue()
766                for r in rows:
767                    volumeList[r][4] = parm
768            dlg.Destroy()
769            UpdateChiralRestr(chiralRestData)               
770                                           
771        ChiralRestr.DestroyChildren()
772        dataDisplay = wx.Panel(ChiralRestr)
773        mainSizer = wx.BoxSizer(wx.VERTICAL)
774        mainSizer.Add((5,5),0)
775        mainSizer.Add(WtBox(ChiralRestr,chiralRestData),0,wx.ALIGN_CENTER_VERTICAL)
776
777        volumeList = chiralRestData['Volumes']
778        if len(volumeList):
779            table = []
780            rowLabels = []
781            Types = [wg.GRID_VALUE_STRING,]+3*[wg.GRID_VALUE_FLOAT+':10,2',]
782            if 'macro' in General['Type']:
783                colLabels = ['(res) O (res) A (res) B (res) C','calc','obs','esd']
784                for i,[indx,ops,dcalc,dobs,esd] in enumerate(volumeList):
785                    atoms = G2mth.GetAtomItemsById(Atoms,AtLookUp,indx,0,4)
786                    name = ''
787                    for atom in atoms:
788                        name += '('+atom[1]+atom[0].strip()+atom[2]+') '+atom[3]+' '
789                    table.append([name,dcalc,dobs,esd])
790                    rowLabels.append(str(i))
791            else:
792                colLabels = ['O+SymOp  A+SymOp  B+SymOp  C+SymOp)','calc','obs','esd']
793                for i,[indx,ops,dcalc,dobs,esd] in enumerate(volumeList):
794                    atoms = G2mth.GetAtomItemsById(Atoms,AtLookUp,indx,ct-1)
795                    table.append([atoms[0]+'+('+ops[0]+') '+atoms[1]+'+('+ops[1]+') '+atoms[2]+ \
796                    '+('+ops[2]+') '+atoms[3]+'+('+ops[3]+')',dcalc,dobs,esd])
797                    rowLabels.append(str(i))
798            volumeTable = G2gd.Table(table,rowLabels=rowLabels,colLabels=colLabels,types=Types)
799            Volumes = G2gd.GSGrid(ChiralRestr)
800            Volumes.SetTable(volumeTable, True)
801            Volumes.AutoSizeColumns(False)
802            for r in range(len(volumeList)):
803                for c in range(2):
804                    Volumes.SetReadOnly(r,c,True)
805                    Volumes.SetCellStyle(r,c,VERY_LIGHT_GREY,True)
806            G2frame.dataFrame.Bind(wx.EVT_MENU, OnDeleteRestraint, id=G2gd.wxID_RESTDELETE)
807            G2frame.dataFrame.Bind(wx.EVT_MENU, OnChangeValue, id=G2gd.wxID_RESRCHANGEVAL)
808            G2frame.dataFrame.Bind(wx.EVT_MENU, OnChangeEsd, id=G2gd.wxID_RESTCHANGEESD)
809            mainSizer.Add(Volumes,0,)
810        else:
811            mainSizer.Add(wx.StaticText(ChiralRestr,-1,'No chiral volume restraints for this phase'),0,)
812
813        ChiralRestr.SetSizer(mainSizer)
814        Size = mainSizer.Fit(G2frame.dataFrame)
815        Size[0] += 5
816        Size[1] += 50       #make room for tab
817        ChiralRestr.SetSize(Size)
818        ChiralRestr.SetScrollbars(10,10,Size[0]/10-4,Size[1]/10-1)
819        G2frame.dataFrame.setSizePosLeft(Size)
820   
821    def UpdateTorsionRestr(torsionRestData):
822
823        def OnDeleteRestraint(event):
824            rows = Torsions.GetSelectedRows()
825            if not rows:
826                return
827            rows.sort()
828            rows.reverse()
829            for row in rows:
830                torsionList.remove(torsionList[row])
831            UpdateTorsionRestr(torsionRestData)               
832           
833        TorsionRestr.DestroyChildren()
834        dataDisplay = wx.Panel(TorsionRestr)
835        mainSizer = wx.BoxSizer(wx.VERTICAL)
836        mainSizer.Add((5,5),0)
837        mainSizer.Add(WtBox(TorsionRestr,torsionRestData),0,wx.ALIGN_CENTER_VERTICAL)
838
839        torsionList = torsionRestData['Torsions']
840        if len(torsionList):
841            table = []
842            rowLabels = []
843            Types = 2*[wg.GRID_VALUE_STRING,]+3*[wg.GRID_VALUE_FLOAT+':10,2',]
844            if 'macro' in General['Type']:
845                colLabels = ['(res) A (res) B (res) C (res) D','coef name','calc','obs','esd']
846                for i,[indx,ops,cofName,dcalc,dobs,esd] in enumerate(torsionList):
847                    atoms = G2mth.GetAtomItemsById(Atoms,AtLookUp,indx,0,4)
848                    name = ''
849                    for atom in atoms:
850                        name += '('+atom[1]+atom[0].strip()+atom[2]+') '+atom[3]+' '
851                    table.append([name,cofName,dcalc,dobs,esd])
852                    rowLabels.append(str(i))
853            else:
854                colLabels = ['A+SymOp  B+SymOp  C+SymOp  D+SymOp)','coef name','calc','obs','esd']
855                for i,[indx,ops,cofName,dcalc,dobs,esd] in enumerate(torsionList):
856                    atoms = G2mth.GetAtomItemsById(Atoms,AtLookUp,indx,ct-1)
857                    table.append([atoms[0]+'+('+ops[0]+') '+atoms[1]+'+('+ops[1]+') '+atoms[2]+ \
858                    '+('+ops[2]+') '+atoms[3]+'+('+ops[3]+')',cofName,dcalc,dobs,esd])
859                    rowLabels.append(str(i))
860            torsionTable = G2gd.Table(table,rowLabels=rowLabels,colLabels=colLabels,types=Types)
861            Torsions = G2gd.GSGrid(TorsionRestr)
862            Torsions.SetTable(torsionTable, True)
863            Torsions.AutoSizeColumns(False)
864            for r in range(len(torsionList)):
865                for c in range(2):
866                    Torsions.SetReadOnly(r,c,True)
867                    Torsions.SetCellStyle(r,c,VERY_LIGHT_GREY,True)
868            G2frame.dataFrame.Bind(wx.EVT_MENU, OnDeleteRestraint, id=G2gd.wxID_RESTDELETE)
869            G2frame.dataFrame.Bind(wx.EVT_MENU, OnChangeEsd, id=G2gd.wxID_RESTCHANGEESD)
870            mainSizer.Add(Torsions,0,)
871        else:
872            mainSizer.Add(wx.StaticText(TorsionRestr,-1,'No torsion restraints for this phase'),0,)
873
874        TorsionRestr.SetSizer(mainSizer)
875        Size = mainSizer.Fit(G2frame.dataFrame)
876        Size[0] += 5
877        Size[1] += 50       #make room for tab
878        TorsionRestr.SetSize(Size)
879        TorsionRestr.SetScrollbars(10,10,Size[0]/10-4,Size[1]/10-1)
880        G2frame.dataFrame.setSizePosLeft(Size)
881
882    def UpdateRamaRestr(ramaRestData):
883
884        def OnDeleteRestraint(event):
885            rows = Volumes.GetSelectedRows()
886            if not rows:
887                return
888            rows.sort()
889            rows.reverse()
890            for row in rows:
891                ramaList.remove(ramaList[row])
892            UpdateRamaRestr(ramaRestData)               
893           
894        RamaRestr.DestroyChildren()
895        dataDisplay = wx.Panel(RamaRestr)
896        mainSizer = wx.BoxSizer(wx.VERTICAL)
897        mainSizer.Add((5,5),0)
898        mainSizer.Add(WtBox(RamaRestr,ramaRestData),0,wx.ALIGN_CENTER_VERTICAL)
899
900        ramaList = ramaRestData['Ramas']
901        if len(ramaList):
902            table = []
903            rowLabels = []
904            Types = 2*[wg.GRID_VALUE_STRING,]+3*[wg.GRID_VALUE_FLOAT+':10,2',]
905            if 'macro' in General['Type']:
906                colLabels = ['(res) A (res) B (res) C (res) D (res) E','coef name','calc','obs','esd']
907                for i,[indx,ops,cofName,dcalc,dobs,esd] in enumerate(ramaList):
908                    atoms = G2mth.GetAtomItemsById(Atoms,AtLookUp,indx,0,4)
909                    name = ''
910                    for atom in atoms:
911                        name += '('+atom[1]+atom[0].strip()+atom[2]+') '+atom[3]+' '
912                    table.append([name,cofName,dcalc,dobs,esd])
913                    rowLabels.append(str(i))
914            else:
915                colLabels = ['A+SymOp  B+SymOp  C+SymOp  D+SymOp  E+SymOp)','coef name','calc','obs','esd']
916                for i,[indx,ops,cofName,dcalc,dobs,esd] in enumerate(ramaList):
917                    atoms = G2mth.GetAtomItemsById(Atoms,AtLookUp,indx,ct-1)
918                    table.append([atoms[0]+'+('+ops[0]+') '+atoms[1]+'+('+ops[1]+') '+atoms[2]+ \
919                    '+('+ops[2]+') '+atoms[3]+'+('+ops[3]+')',cofName,dcalc,dobs,esd])
920                    rowLabels.append(str(i))
921            ramaTable = G2gd.Table(table,rowLabels=rowLabels,colLabels=colLabels,types=Types)
922            Ramas = G2gd.GSGrid(RamaRestr)
923            Ramas.SetTable(ramaTable, True)
924            Ramas.AutoSizeColumns(False)
925            for r in range(len(ramaList)):
926                for c in range(2):
927                    Ramas.SetReadOnly(r,c,True)
928                    Ramas.SetCellStyle(r,c,VERY_LIGHT_GREY,True)
929            G2frame.dataFrame.Bind(wx.EVT_MENU, OnDeleteRestraint, id=G2gd.wxID_RESTDELETE)
930            G2frame.dataFrame.Bind(wx.EVT_MENU, OnChangeEsd, id=G2gd.wxID_RESTCHANGEESD)
931            mainSizer.Add(Ramas,0,)
932        else:
933            mainSizer.Add(wx.StaticText(RamaRestr,-1,'No Ramachandran restraints for this phase'),0,)
934
935        RamaRestr.SetSizer(mainSizer)
936        Size = mainSizer.Fit(G2frame.dataFrame)
937        Size[0] += 5
938        Size[1] += 50       #make room for tab
939        RamaRestr.SetSize(Size)
940        RamaRestr.SetScrollbars(10,10,Size[0]/10-4,Size[1]/10-1)
941        G2frame.dataFrame.setSizePosLeft(Size)
942
943    def OnPageChanged(event):
944        page = event.GetSelection()
945        text = G2frame.dataDisplay.GetPageText(page)
946        if text == 'Bond restraints':
947            G2gd.SetDataMenuBar(G2frame,G2frame.dataFrame.RestraintMenu)
948            bondRestData = restrData['Bond']
949            UpdateBondRestr(bondRestData)
950        elif text == 'Angle restraints':
951            G2gd.SetDataMenuBar(G2frame,G2frame.dataFrame.RestraintMenu)
952            angleRestData = restrData['Angle']
953            UpdateAngleRestr(angleRestData)
954        elif text == 'Plane restraints':
955            G2gd.SetDataMenuBar(G2frame,G2frame.dataFrame.RestraintMenu)
956            planeRestData = restrData['Plane']
957            UpdatePlaneRestr(planeRestData)
958        elif text == 'Chiral restraints':
959            G2gd.SetDataMenuBar(G2frame,G2frame.dataFrame.RestraintMenu)
960            chiralRestData = restrData['Chiral']
961            UpdateChiralRestr(chiralRestData)
962        elif text == 'Torsion restraints':
963            G2gd.SetDataMenuBar(G2frame,G2frame.dataFrame.RestraintMenu)
964            torsionRestData = restrData['Torsion']
965            UpdateTorsionRestr(torsionRestData)
966        elif text == 'Ramachandran restraints':
967            G2gd.SetDataMenuBar(G2frame,G2frame.dataFrame.RestraintMenu)
968            ramaRestData = restrData['Rama']
969            UpdateRamaRestr(ramaRestData)
970        event.Skip()
971
972    def SetStatusLine(text):
973        Status.SetStatusText(text)                                     
974       
975    if G2frame.dataDisplay:
976        G2frame.dataDisplay.Destroy()
977       
978    G2gd.SetDataMenuBar(G2frame,G2frame.dataFrame.RestraintMenu)
979    G2frame.dataFrame.SetLabel('restraints for '+phaseName)
980    if not G2frame.dataFrame.GetStatusBar():
981        Status = G2frame.dataFrame.CreateStatusBar()
982    SetStatusLine('')
983   
984    G2frame.dataFrame.RestraintEdit.Enable(G2gd.wxID_RESTSELPHASE,False)
985    if len(Phases) > 1:
986        G2frame.dataFrame.RestraintEdit.Enable(G2gd.wxID_RESTSELPHASE,True)
987        G2frame.dataFrame.Bind(wx.EVT_MENU, OnSelectPhase, id=G2gd.wxID_RESTSELPHASE)
988    G2frame.dataFrame.Bind(wx.EVT_MENU, OnAddRestraint, id=G2gd.wxID_RESTRAINTADD)
989    if 'macro' in phasedata['General']['Type']:
990        G2frame.dataFrame.RestraintEdit.Enable(G2gd.wxID_AARESTRAINTADD,True)
991        G2frame.dataFrame.Bind(wx.EVT_MENU, OnAddAARestraint, id=G2gd.wxID_AARESTRAINTADD)
992    G2frame.dataDisplay = G2gd.GSNoteBook(parent=G2frame.dataFrame,size=G2frame.dataFrame.GetClientSize())
993   
994    BondRestr = wx.ScrolledWindow(G2frame.dataDisplay)
995    G2frame.dataDisplay.AddPage(BondRestr,'Bond restraints')
996    AngleRestr = wx.ScrolledWindow(G2frame.dataDisplay)
997    G2frame.dataDisplay.AddPage(AngleRestr,'Angle restraints')
998    PlaneRestr = wx.ScrolledWindow(G2frame.dataDisplay)
999    G2frame.dataDisplay.AddPage(PlaneRestr,'Plane restraints')
1000    ChiralRestr = wx.ScrolledWindow(G2frame.dataDisplay)
1001    G2frame.dataDisplay.AddPage(ChiralRestr,'Chiral restraints')
1002    TorsionRestr = wx.ScrolledWindow(G2frame.dataDisplay)
1003    G2frame.dataDisplay.AddPage(TorsionRestr,'Torsion restraints')
1004    RamaRestr = wx.ScrolledWindow(G2frame.dataDisplay)
1005    G2frame.dataDisplay.AddPage(RamaRestr,'Ramachandran restraints')
1006   
1007    UpdateBondRestr(restrData['Bond'])
1008
1009    G2frame.dataDisplay.Bind(wx.EVT_NOTEBOOK_PAGE_CHANGED, OnPageChanged)
Note: See TracBrowser for help on using the repository browser.