Skip to content

Commit 801f077

Browse files
committed
work on ValidatedTxtCtrl & GSASIIimgGUI for Linux (see #114); start process of replacing TextCtrl.SetValue to .ChangeValue in callbacks
1 parent 5e15c35 commit 801f077

File tree

2 files changed

+96
-58
lines changed

2 files changed

+96
-58
lines changed

GSASII/GSASIIctrlGUI.py

Lines changed: 66 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -271,9 +271,12 @@ class ValidatedTxtCtrl(wx.TextCtrl):
271271
came from. The type of the initial value must be int,
272272
float or str or None (see :obj:`key` and :obj:`typeHint`);
273273
this type (or the one in :obj:`typeHint`) is preserved.
274+
Values are processed and saved when Enter is pressed, when the
275+
mouse is moved to another control (leave window or focus is lost)
276+
or after a change and a delay of two seconds.
274277
275278
Float values can be entered in the TextCtrl as numbers or also
276-
as algebraic expressions using operators + - / \\* () and \\*\\*,
279+
as algebraic expressions using operators (+ - / \\* () and \\*\\*),
277280
in addition pi, sind(), cosd(), tand(), and sqrt() can be used,
278281
as well as appreviations s, sin, c, cos, t, tan and sq.
279282
@@ -364,7 +367,6 @@ class ValidatedTxtCtrl(wx.TextCtrl):
364367
def __init__(self,parent,loc,key,nDig=None,notBlank=True,xmin=None,xmax=None,
365368
OKcontrol=None,OnLeave=None,typeHint=None,CIFinput=False,exclLim=[False,False],
366369
OnLeaveArgs={}, ASCIIonly=False,
367-
min=None, max=None, # patch: remove this eventually
368370
**kw):
369371
# save passed values needed outside __init__
370372
self.result = loc
@@ -376,25 +378,12 @@ def __init__(self,parent,loc,key,nDig=None,notBlank=True,xmin=None,xmax=None,
376378
self.CIFinput = CIFinput
377379
self.notBlank = notBlank
378380
self.ASCIIonly = ASCIIonly
379-
380-
# patch: remove this when min & max are no longer used to call this
381-
if min is not None:
382-
xmin=min
383-
if GSASIIpath.GetConfigValue('debug'):
384-
print('Call to ValidatedTxtCtrl using min (change to xmin) here:')
385-
G2obj.HowDidIgetHere(True)
386-
if max is not None:
387-
xmax=max
388-
if GSASIIpath.GetConfigValue('debug'):
389-
print('Call to ValidatedTxtCtrl using max (change to xmax) here:')
390-
G2obj.HowDidIgetHere(True)
391-
# end patch
392-
381+
393382
# initialization
394383
self.invalid = False # indicates if the control has invalid contents
395384
self.evaluated = False # set to True when the validator recognizes an expression
396385
self.timer = None # tracks pending updates for expressions in float textctrls
397-
self.delay = 5000 # delay for timer update (5 sec)
386+
self.delay = 2000 # delay for timer update (2 sec)
398387
self.type = str
399388

400389
val = loc[key]
@@ -447,7 +436,7 @@ def __init__(self,parent,loc,key,nDig=None,notBlank=True,xmin=None,xmax=None,
447436
else:
448437
wx.TextCtrl.__init__(self,parent,wx.ID_ANY,**kw)
449438
if val is not None:
450-
self.SetValue(val)
439+
self.ChangeValue(val)
451440
if notBlank:
452441
self.Bind(wx.EVT_CHAR,self._onStringKey)
453442
self.ShowStringValidity() # test if valid input
@@ -460,15 +449,42 @@ def __init__(self,parent,loc,key,nDig=None,notBlank=True,xmin=None,xmax=None,
460449
self.Bind(wx.EVT_LEAVE_WINDOW, self._onLeaveWindow)
461450
self.Bind(wx.EVT_KILL_FOCUS, self._onLoseFocus)
462451
self.Bind(wx.EVT_TEXT_ENTER, self._onLoseFocus)
463-
# patch for wx 2.9 on Mac
464-
i,j= wx.__version__.split('.')[0:2]
465-
if int(i)+int(j)/10. > 2.8 and 'wxOSX' in wx.PlatformInfo:
466-
self.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
467452

468-
def SetValue(self,val):
469-
if self.result is not None: # note that this bypasses formatting
453+
def SetValue(self,val,warn=True):
454+
'''Place a value into the text widget and save it into the
455+
associated array element. Note that, unlike the stock wx.TextCtrl,
456+
val is expected to be in the form expected by the widget
457+
(float/int/str) rather than only str's.
458+
For float val values, the value is formatted when placed in the
459+
TextCtrl, but the supplied value is what is actually saved.
460+
461+
This routine triggers a wx.EVT_TEXT event
462+
'''
463+
# GSAS-II callback routines should call ChangeValue not SetValue
464+
# for debugging flag calls. Set warn to False for calls that are not in callbacks
465+
# and thus are OK
466+
#if GSASIIpath.GetConfigValue('debug') and warn:
467+
# G2obj.HowDidIgetHere(True)
468+
if self.result is not None:
470469
self.result[self.key] = val
471470
self._setValue(val)
471+
# Direct calls to SetValue should trigger an event
472+
wx.TextCtrl.SetValue(self,wx.TextCtrl.GetValue(self))
473+
474+
def ChangeValue(self,val):
475+
'''Place a value into the text widget and save it into the
476+
associated array element. Note that, unlike the stock wx.TextCtrl,
477+
val is expected to be in the form expected by the widget
478+
(float/int/str) rather than only str's.
479+
For float val values, the value is formatted when placed in the
480+
TextCtrl, but the supplied value is what is actually saved.
481+
482+
This routine does not trigger a wx.EVT_TEXT event. This is what
483+
should be used inside event callbacks, not :meth:`SetValue`.
484+
'''
485+
if self.result is not None:
486+
self.result[self.key] = val
487+
self._setValue(val)
472488

473489
def _setValue(self,val,show=True):
474490
'''Check the validity of an int or float value and convert to a str.
@@ -487,7 +503,7 @@ def _setValue(self,val,show=True):
487503
pass
488504
else:
489505
self.invalid = True
490-
if show and not self.invalid: wx.TextCtrl.SetValue(self,str(val))
506+
if show and not self.invalid: wx.TextCtrl.ChangeValue(self,str(val))
491507
elif self.type is float:
492508
try:
493509
if type(val) is str: val = val.replace(',','.')
@@ -498,10 +514,10 @@ def _setValue(self,val,show=True):
498514
else:
499515
self.invalid = True
500516
if self.nDig and show and not self.invalid:
501-
wx.TextCtrl.SetValue(self,str(G2fil.FormatValue(val,self.nDig)))
517+
wx.TextCtrl.ChangeValue(self,str(G2fil.FormatValue(val,self.nDig)))
502518
self.evaluated = False # expression has been recast as value, reset flag
503519
elif show and not self.invalid:
504-
wx.TextCtrl.SetValue(self,str(G2fil.FormatSigFigs(val)).rstrip('0'))
520+
wx.TextCtrl.ChangeValue(self,str(G2fil.FormatSigFigs(val)).rstrip('0'))
505521
self.evaluated = False # expression has been recast as value, reset flag
506522
else:
507523
if self.ASCIIonly:
@@ -516,9 +532,9 @@ def _setValue(self,val,show=True):
516532
show = True
517533
if show:
518534
try:
519-
wx.TextCtrl.SetValue(self,str(val))
535+
wx.TextCtrl.ChangeValue(self,str(val))
520536
except:
521-
wx.TextCtrl.SetValue(self,val)
537+
wx.TextCtrl.ChangeValue(self,val)
522538
self.ShowStringValidity() # test if valid input
523539
return
524540

@@ -550,15 +566,17 @@ def _IndicateValidity(self):
550566
ins = self.GetInsertionPoint()
551567
self.SetForegroundColour("red")
552568
self.SetBackgroundColour("yellow")
553-
self.SetFocus()
569+
if not sys.platform.startswith("linux"):
570+
self.SetFocus()
554571
self.Refresh() # this selects text on some Linuxes
555572
self.SetSelection(0,0) # unselect
556573
self.SetInsertionPoint(ins) # put insertion point back
557574
else: # valid input
558575
self.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOW))
559576
self.SetForegroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNTEXT))
560577
self.Refresh()
561-
self.SetFocus() # seems needed, at least on MacOS to get color change
578+
if not sys.platform.startswith("linux"):
579+
self.SetFocus() # seems needed, at least on MacOS to get color change
562580

563581
def _GetNumValue(self):
564582
'Get and where needed convert string from GetValue into int or float'
@@ -605,7 +623,10 @@ def _GetStringValue(self,event):
605623
wx.CallAfter(self._SaveStringValue)
606624

607625
def _SaveStringValue(self):
608-
val = self.GetValue().strip()
626+
try:
627+
val = self.GetValue().strip()
628+
except RuntimeError: # ignore if control has been deleted
629+
return
609630
# always store the result
610631
if self.CIFinput and '2' in platform.python_version_tuple()[0]: # Py2/CIF make results ASCII
611632
self.result[self.key] = val.encode('ascii','replace')
@@ -5787,8 +5808,20 @@ class HelpButton(wx.Button):
57875808
def __init__(self,parent,msg='',helpIndex='',wrap=None):
57885809
if sys.platform == "darwin":
57895810
wx.Button.__init__(self,parent,wx.ID_HELP)
5790-
else:
5811+
elif sys.platform.startswith("linux"):
57915812
wx.Button.__init__(self,parent,wx.ID_ANY,'?',style=wx.BU_EXACTFIT)
5813+
self.SetBackgroundColour('yellow')
5814+
self.SetForegroundColour('black')
5815+
f = wx.Font(self.GetFont()).Bold()
5816+
f.SetPointSize(f.PointSize+1)
5817+
self.SetFont(f)
5818+
else: # button needs to be exaggerated in Windows
5819+
wx.Button.__init__(self,parent,wx.ID_ANY,' ? ',style=wx.BU_EXACTFIT)
5820+
self.SetBackgroundColour('yellow')
5821+
self.SetForegroundColour('black')
5822+
f = wx.Font(self.GetFont()).Bold()
5823+
f.SetPointSize(f.PointSize+4)
5824+
self.SetFont(f)
57925825
self.Bind(wx.EVT_BUTTON,self._onPress)
57935826
if wrap:
57945827
self.msg=StripIndents(msg,True)

GSASII/GSASIIimgGUI.py

Lines changed: 30 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -933,11 +933,11 @@ def OnNewVal(invalid,value,tc):
933933
r11 = min(max(Range[1][1],Range[1][0]+1),Range[0][1]) # keep values in range
934934
if r11 != Range[1][1]:
935935
Range[1][1] = r11
936-
maxVal.SetValue(int(Range[1][1]))
936+
maxVal.ChangeValue(int(Range[1][1]))
937937
r10 = max(min(Range[1][0],Range[1][1]-1),Range[0][0])
938938
if r10 != Range[1][0]:
939939
Range[1][0] = r10
940-
minVal.SetValue(int(Range[1][0]))
940+
minVal.ChangeValue(int(Range[1][0]))
941941
sqrtDeltZero = math.sqrt(max(1.0,Range[0][1]-max(0.0,Range[1][0])-1)) # sqrt(Imax0-Imin-1)
942942
sqrtDeltOne = math.sqrt(max(1.0,Range[1][1]-max(0.0,Range[1][0])-1)) # sqrt(Imax-Imin-1)
943943
sv1 = min(100,max(0,int(0.5+100.*sqrtDeltOne/sqrtDeltZero)))
@@ -960,7 +960,7 @@ def OnMaxSlider(event):
960960
G2frame.prevMaxValue = val
961961
sqrtDeltZero = math.sqrt(max(1.0,Range[0][1]-max(0.0,Range[1][0])-1)) # sqrt(Imax0-Imin-1)
962962
Range[1][1] = int(0.5 + (val * sqrtDeltZero / 100.)**2 + Range[1][0] + 1)
963-
maxVal.SetValue(int(0.5+Range[1][1]))
963+
maxVal.ChangeValue(int(0.5+Range[1][1]))
964964
DeltOne = max(1.0,Range[1][1]-max(0.0,Range[0][0])-1)
965965
minSel.SetValue(int(0.5 + 100*(Range[1][0]/DeltOne)))
966966
sv0 = min(100,max(0,int(0.5+100.*(Range[1][0]-Range[0][0])/DeltOne)))
@@ -980,7 +980,7 @@ def OnMinSlider(event):
980980
G2frame.prevMinValue = val
981981
DeltOne = max(1.0,Range[1][1]-max(0.0,Range[0][0])-1) # Imax-Imin0-1
982982
Range[1][0] = max(0,int(0.5 + val * DeltOne / 100 + Range[0][0]))
983-
minVal.SetValue(int(Range[1][0]))
983+
minVal.ChangeValue(int(Range[1][0]))
984984
sqrtDeltZero = math.sqrt(max(1.0,Range[0][1]-max(0.0,Range[1][0])-1)) # sqrt(Imax0-Imin-1)
985985
sqrtDeltOne = math.sqrt(max(1.0,Range[1][1]-max(0.0,Range[1][0])-1)) # sqrt(Imax-Imin-1)
986986
sv1 = min(100,max(0,int(0.5+100.*sqrtDeltOne/sqrtDeltZero)))
@@ -1016,8 +1016,8 @@ def OnAutoSet(event):
10161016
DeltOne = max(1.0,Range[1][1]-max(0.0,Range[0][0])-1)
10171017
sv0 = min(100,max(0,int(0.5+100.*(Range[1][0]-Range[0][0])/DeltOne)))
10181018
minSel.SetValue(sv0)
1019-
minVal.SetValue(int(Range[1][0]))
1020-
maxVal.SetValue(int(Range[1][1]))
1019+
minVal.ChangeValue(int(Range[1][0]))
1020+
maxVal.ChangeValue(int(Range[1][1]))
10211021
new,plotNum,Page,Plot,lim = G2frame.G2plotNB.FindPlotTab('2D Powder Image','mpl',newImage=False)
10221022
Page.ImgObj.set_clim([Range[1][0],Range[1][1]])
10231023
if mplOld:
@@ -1148,36 +1148,41 @@ def OnNewBinType(event):
11481148
wx.CallLater(100,UpdateImageControls,G2frame,data,masks)
11491149

11501150
def OnIOtth(invalid,value,tc):
1151-
Ltth = float(G2frame.InnerTth.GetValue())
1152-
Utth = float(G2frame.OuterTth.GetValue())
1151+
'''Respond to a change in integration 2theta range
1152+
'''
1153+
Ltth,Utth = IOtth
11531154
if Ltth > Utth:
11541155
Ltth,Utth = Utth,Ltth
1156+
G2frame.InnerTth.ChangeValue(Ltth)
1157+
G2frame.OuterTth.ChangeValue(Utth)
11551158
if 'q' in data['binType'].lower():
11561159
data['IOtth'] = [2.*asind(Ltth*wave/(4.*math.pi)),2.*asind(Utth*wave/(4.*math.pi))]
11571160
else:
11581161
data['IOtth'] = [Ltth,Utth]
1159-
G2frame.InnerTth.SetValue(Ltth)
1160-
G2frame.OuterTth.SetValue(Utth)
11611162
wx.CallAfter(G2plt.PlotExposedImage,G2frame,event=tc.event)
11621163

11631164
def OnLRazim(invalid,value,tc):
1164-
Lazm = float(G2frame.Lazim.GetValue())%360.
1165-
Razm = float(G2frame.Razim.GetValue())%360.
1165+
'''Respond to a change in integration azimuth range
1166+
'''
1167+
Lazm = data['LRazimuth'][0] % 360.
1168+
Razm = data['LRazimuth'][1] % 360.
11661169
if Lazm > Razm:
11671170
Razm += 360.
11681171
if data['fullIntegrate']:
11691172
Razm = Lazm+360.
1170-
G2frame.Lazim.SetValue(Lazm)
1171-
G2frame.Razim.SetValue(Razm)
1172-
data['LRazimuth'] = [Lazm,Razm]
1173+
if data['LRazimuth'][0] != Lazm or data['LRazimuth'][1] != Razm:
1174+
G2frame.Lazim.ChangeValue(Lazm)
1175+
G2frame.Razim.ChangeValue(Razm)
1176+
data['LRazimuth'] = [Lazm,Razm]
11731177
wx.CallAfter(G2plt.PlotExposedImage,G2frame,event=tc.event)
11741178

11751179
def OnNumOutAzms(invalid,value,tc):
11761180
wx.CallAfter(G2plt.PlotExposedImage,G2frame,event=tc.event)
11771181

11781182
def OnNumOutBins(invalid,value,tc):
1183+
# make sure # channels is divisible by 4
11791184
data['outChannels'] = (data['outChannels']//4)*4
1180-
outChan.SetValue(data['outChannels'])
1185+
outChan.ChangeValue(data['outChannels'])
11811186

11821187
def OnOblique(event):
11831188
data['Oblique'][1] = not data['Oblique'][1]
@@ -1199,7 +1204,7 @@ def OnShowLines(event):
11991204
G2plt.PlotExposedImage(G2frame,event=event)
12001205

12011206
def OnFullIntegrate(event):
1202-
Lazm = float(G2frame.Lazim.GetValue())
1207+
Lazm = data['LRazimuth'][0]
12031208
if data['fullIntegrate']:
12041209
data['fullIntegrate'] = False
12051210
data['LRazimuth'] = [Lazm,Lazm+20.]
@@ -1271,9 +1276,9 @@ def OnOchoice(event):
12711276
littleSizer.Add(G2frame.Lazim,0,WACV)
12721277
G2frame.Razim = G2G.ValidatedTxtCtrl(G2frame.dataWindow,LRazim,1,nDig=(6,1,'f'),typeHint=float,OnLeave=OnLRazim)
12731278
if data['fullIntegrate']:
1279+
G2frame.Razim.ChangeValue(LRazim[0]+360.)
12741280
G2frame.Razim.Enable(False)
1275-
G2frame.Razim.SetBackgroundColour(VERY_LIGHT_GREY)
1276-
G2frame.Razim.SetValue(LRazim[0]+360.)
1281+
#G2frame.Razim.SetBackgroundColour(VERY_LIGHT_GREY)
12771282
littleSizer.Add(G2frame.Razim,0,WACV)
12781283
dataSizer.Add(littleSizer,0,)
12791284
dataSizer.Add(wx.StaticText(parent=G2frame.dataWindow,label=' No. 2-theta/azimuth bins'),0,WACV)
@@ -2028,11 +2033,11 @@ def OnNewVal(invalid,value,tc):
20282033
r11 = min(max(Range[1][1],Range[1][0]+1),Range[0][1]) # keep values in range
20292034
if r11 != Range[1][1]:
20302035
Range[1][1] = r11
2031-
maxVal.SetValue(int(Range[1][1]))
2036+
maxVal.ChangeValue(int(Range[1][1]))
20322037
r10 = max(min(Range[1][0],Range[1][1]-1),Range[0][0])
20332038
if r10 != Range[1][0]:
20342039
Range[1][0] = r10
2035-
minVal.SetValue(int(Range[1][0]))
2040+
minVal.ChangeValue(int(Range[1][0]))
20362041
sqrtDeltZero = math.sqrt(max(1.0,Range[0][1]-max(0.0,Range[1][0])-1)) # sqrt(Imax0-Imin-1)
20372042
sqrtDeltOne = math.sqrt(max(1.0,Range[1][1]-max(0.0,Range[1][0])-1)) # sqrt(Imax-Imin-1)
20382043
sv1 = min(100,max(0,int(0.5+100.*sqrtDeltOne/sqrtDeltZero)))
@@ -2055,7 +2060,7 @@ def OnMaxSlider(event):
20552060
G2frame.prevMaxValue = val
20562061
sqrtDeltZero = math.sqrt(max(1.0,Range[0][1]-max(0.0,Range[1][0])-1)) # sqrt(Imax0-Imin-1)
20572062
Range[1][1] = int(0.5 + (val * sqrtDeltZero / 100.)**2 + Range[1][0] + 1)
2058-
maxVal.SetValue(int(0.5+Range[1][1]))
2063+
maxVal.ChangeValue(int(0.5+Range[1][1]))
20592064
DeltOne = max(1.0,Range[1][1]-max(0.0,Range[0][0])-1)
20602065
minSel.SetValue(int(0.5 + 100*(Range[1][0]/DeltOne)))
20612066
sv0 = min(100,max(0,int(0.5+100.*(Range[1][0]-Range[0][0])/DeltOne)))
@@ -2075,7 +2080,7 @@ def OnMinSlider(event):
20752080
G2frame.prevMinValue = val
20762081
DeltOne = max(1.0,Range[1][1]-max(0.0,Range[0][0])-1) # Imax-Imin0-1
20772082
Range[1][0] = max(0,int(0.5 + val * DeltOne / 100 + Range[0][0]))
2078-
minVal.SetValue(int(Range[1][0]))
2083+
minVal.ChangeValue(int(Range[1][0]))
20792084
sqrtDeltZero = math.sqrt(max(1.0,Range[0][1]-max(0.0,Range[1][0])-1)) # sqrt(Imax0-Imin-1)
20802085
sqrtDeltOne = math.sqrt(max(1.0,Range[1][1]-max(0.0,Range[1][0])-1)) # sqrt(Imax-Imin-1)
20812086
sv1 = min(100,max(0,int(0.5+100.*sqrtDeltOne/sqrtDeltZero)))
@@ -2111,8 +2116,8 @@ def OnAutoSet(event):
21112116
DeltOne = max(1.0,Range[1][1]-max(0.0,Range[0][0])-1)
21122117
sv0 = min(100,max(0,int(0.5+100.*(Range[1][0]-Range[0][0])/DeltOne)))
21132118
minSel.SetValue(sv0)
2114-
minVal.SetValue(int(Range[1][0]))
2115-
maxVal.SetValue(int(Range[1][1]))
2119+
minVal.ChangeValue(int(Range[1][0]))
2120+
maxVal.ChangeValue(int(Range[1][1]))
21162121
new,plotNum,Page,Plot,lim = G2frame.G2plotNB.FindPlotTab('2D Powder Image','mpl',newImage=False)
21172122
Page.ImgObj.set_clim([Range[1][0],Range[1][1]])
21182123
if mplOld:

0 commit comments

Comments
 (0)