awips2/cave/com.raytheon.viz.gfe/localization/gfe/userPython/procedures/BOIVerifyInfo.py

2274 lines
88 KiB
Python
Raw Normal View History

2022-05-05 12:34:50 -05:00
# ----------------------------------------------------------------------------
# This software is in the public domain, furnished "as is", without technical
# support, and with no warranty, express or implied, as to its usefulness for
# any purpose.
#
# BOIVerifyInfo - version 2.0.5
#
# Allows changes to forecaster numbers - and changes any current records
# to match the new numbers. Allows deletion of grids in the archived
# database (i.e. to remove bad grids)
#
# Author: Tim Barker - SOO BOI
# 2007/11/06 - version 2.0 - Original Documented Implementation
# 2008/05/28 - version 2.0.5 - added ability to show and delete grids
# from obs models
#
#
# 2010/04/23 ryu Initial port to AWIPS II.
#
# ----------------------------------------------------------------------------
##
# This is an absolute override file, indicating that a higher priority version
# of the file will completely replace a lower priority version of the file.
##
MenuItems = ["Verify"]
from numpy import *
from math import *
import tkinter,tkinter.font,tkinter.messagebox
import TkDefaults
import time
import SmartScript
import BOIVerifyUtility
PROGNAME="BOIVerifyInfo" # you can change it if you dont like BOI. Shame on you!
MONS=["DUM","Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]
DAYS=["Mon","Tue","Wed","Thu","Fri","Sat","Sun"]
COLORLIST=["Cyan","Orange","PaleGreen","Red","Blue","Magenta","ForestGreen","Sienna",
"Pink","Green","purple","Yellow","Tan","Turquoise","SteelBlue",
"chartreuse","Gold","tomato","Violet","aquamarine","Coral"]
#=====================================================================
# The dummy procedure - which does nothing more than start the
# non-modal BOIVerifyInfo dialog box.
#
class Procedure (SmartScript.SmartScript):
def __init__(self, dbss):
self._dbss=dbss
SmartScript.SmartScript.__init__(self, dbss)
return
#------------------------------------------------------------------
# execute - simply starts the non-modal "Info" dialog box
#
def execute(self):
self.statusBarMsg("Starting %s"%PROGNAME,"R")
tk = tkinter.Tk()
sw = tk.winfo_screenwidth()
sh = tk.winfo_screenheight()
tk.geometry("%dx%d+0+0" % (sw,sh))
self.root = tk
TkDefaults.setDefaults(tk)
self.VU=BOIVerifyUtility.BOIVerifyUtility(self._dbss, None)
self.dialog=VerifInfo(self.VU, parent=tk,
name="BOIVerify Grid Archive Info",
callbackMethod=self.doneInfo,
modal=0,
)
tk.withdraw()
tk.mainloop()
self.statusBarMsg("Finished starting %s"%PROGNAME,"R")
return
#------------------------------------------------------------------
# doneInfo - called when the Info dialog is closed (with the button
# name as an argument
#
def doneInfo(self,buttonType):
self.root.destroy()
return
#=====================================================================
#
# Basic Class for a dialog - similar to IFPDialog.Dialog
#
class Dialog(tkinter.Toplevel):
def __init__(self,parent=None,title=None,modal=0):
if parent is None:
return
tkinter.Toplevel.__init__(self,parent)
#self.transient(parent)
if title:
self.title(title)
self.parent=parent
self.result=None
self.buttonbox()
bodyFrame=tkinter.Frame(self)
self.initial_focus=self.body(bodyFrame)
bodyFrame.pack(padx=5,pady=5,fill=tkinter.BOTH,expand=1)
bodyFrame.pack_propagate(1)
#
if not self.initial_focus:
self.initial_focus=self
self.protocol("WM_DELETE_WINDOW", self.cancel)
self.geometry("+%d+%d"%(parent.winfo_rootx()+50,
parent.winfo_rooty()+50))
self.initial_focus.focus_set()
self.wait_visibility()
self.update_idletasks()
geom=self.geometry()
(wh,rest)=geom.split("+",1)
(wid,hgt)=wh.split("x",1)
self.minsize(wid,hgt)
if modal==1:
self.grab_set()
self.wait_window(self)
return self.result
else:
return self
#------------------------------------------------------------------
# body - normally overridden with the stuff you want to display
# in the dialog box
#
def body(self,master):
pass
#------------------------------------------------------------------
# buttonbox - displays the buttonbox at the bottom of the dialog.
# Normally has OK and Cancel buttons - but can be
# overridden to have any buttons desired
#
def buttonbox(self):
box=tkinter.Frame(self)
w=tkinter.Button(box,text="Ok",width=10,command=self.ok,
default=tkinter.ACTIVE)
w.pack(side=tkinter.LEFT,padx=5,pady=5)
w=tkinter.Button(box,text="Cancel",width=10,command=self.cancel)
w.pack(side=tkinter.LEFT,padx=5,pady=5)
box.pack(side=tkinter.BOTTOM)
#------------------------------------------------------------------
# ok - called when the OK button is pressed. Calls validate to
# see if the input is OK. If the input is OK it removes
# the dialog and does the action specified in apply
# If the input has some problem - it returns to the dialog
#
def ok(self,event=None):
if not self.validate():
self.initial_focus.focus_set()
return
self.withdraw()
self.update_idletasks()
self.apply()
self.cancel()
#------------------------------------------------------------------
# cancel - called when the Cancel button is pressed - and when
# everything else is done. Destroys the dialog
#
def cancel(self,event=None):
self.parent.focus_set()
self.destroy()
#------------------------------------------------------------------
# validate - normally overridden with stuff that checks the input
# on the dialog box. Should return 1 if input is OK,
# and 0 if not.
#
def validate(self):
return 1
#------------------------------------------------------------------
# apply - normally overridden with stuff that needs to be done
# when the dialog input has been validated and it is
# OK to proceed.
#
def apply(self):
pass
#=====================================================================
# ChangeCancelDialog - a Dialog to change the forecaster number,
# forecaster ID, or forecater name
#
class ChangeCancelDialog(Dialog):
def __init__(self, VU, numberVar, idVar, nameVar,
parent=None, name="Edit Forecaster", callbackMethod=None,
modal=1):
self.__parent = parent
self.__name = name
self.__modal = modal
self.__callbackMethod = callbackMethod
self.__VU=VU
self.__numberVar=numberVar
self.__idVar=idVar
self.__nameVar=nameVar
self.__oldNum=self.__numberVar.get()
self.__oldID=self.__idVar.get()
self.__oldName=self.__nameVar.get()
self.__dialog=Dialog.__init__(self,parent=self.__parent,
title=self.__name,
modal=self.__modal)
return
#-----------------------------------------------------------------
# buttonbox - special buttonbox with Change and Cancel buttons
#
def buttonbox(self):
buttonFrame = tkinter.Frame(self)
tkinter.Button(buttonFrame, text="Change", width=7,
command=self.changeCB).pack(side=tkinter.LEFT, pady=5, padx=10)
tkinter.Button(buttonFrame, text="Cancel",width=7,
command=self.cancelCB).pack(side=tkinter.LEFT,pady=5, padx=10)
buttonFrame.pack(side=tkinter.BOTTOM,expand=0)
#------------------------------------------------------------------
# body - special body with the current number, username, and
# display name shown
#
def body(self, master):
tkinter.Label(master,text="Number:").grid(column=0,row=0,sticky=tkinter.E)
tkinter.Label(master,text="Username:").grid(column=0,row=1,sticky=tkinter.E)
tkinter.Label(master,text="Display Name:").grid(column=0,row=2,sticky=tkinter.E)
self.numEntry=tkinter.Entry(master,textvariable=self.__numberVar,width=2)
self.numEntry.grid(column=1,row=0,sticky=tkinter.W)
self.idEntry=tkinter.Entry(master,textvariable=self.__idVar,width=8)
self.idEntry.grid(column=1,row=1,sticky=tkinter.W)
self.nameEntry=tkinter.Entry(master,textvariable=self.__nameVar,width=25)
self.nameEntry.grid(column=1,row=2,sticky=tkinter.W)
#------------------------------------------------------------------
# changeCB - called when they click on Change. Need to validate
# everything to make sure the changes are OK.
#
def changeCB(self):
#
# Check forecaster number for just a number, between 1 and 99
#
newNumStr=self.__numberVar.get().strip()
try:
num=int(newNumStr)
except:
tkinter.messagebox.showerror("Error","Forecaster Number needs to be an integer",parent=self)
self.numEntry.selection_range(0,tkinter.END)
self.numEntry.focus_set()
return
if ((num<0)or(num>99)):
tkinter.messagebox.showerror("Error","Forecater Number needs to be between 1 and 99",
parent=self)
self.numEntry.selection_range(0,tkinter.END)
self.numEntry.focus_set()
return
if (num==0):
tkinter.messagebox.showerror("Error","Forecaster Number 0 cannot be used",
parent=self)
self.numEntry.selection_range(0,tkinter.END)
self.numEntry.focus_set()
return
#
# If changing numbers - make sure the new number is not already
# in use.
#
if num!=int(self.__oldNum):
curNums=self.__VU.getFcstrNums()
for testNum in curNums:
if int(testNum)==num:
tkinter.messagebox.showerror("Errror","Forecaster Number %d is already in use"%num,
parent=self)
self.numEntry.selection_range(0,tkinter.END)
self.numEntry.focus_set()
return
#
# Must provide a username
#
newID=self.__idVar.get().strip()
if len(newID)==0:
tkinter.messagebox.showerror("Error","You must provide a username",
parent=self)
self.idEntry.selection_range(0,tkinter.END)
self.idEntry.focus_set()
return
#
# username cannot be the same as any other current username
#
if newID!=self.__oldID:
curIDs=self.__VU.getFcstrIDs()
for testID in curIDs.values():
if testID==newID:
tkinter.messagebox.showerror("Error","Username %s is already in use"%newID,
parent=self)
self.idEntry.selection_range(0,tkinter.END)
self.idEntry.focus_set()
return
#
# Must provide a display name
#
newName=self.__nameVar.get().strip()
if len(newName)==0:
tkinter.messagebox.showerror("Error","You must provide a display name",
parent=self)
self.nameEntry.selection_range(0,tkinter.END)
self.nameEntry.focus_set()
return
#
# If everything the same as when we started - treat this
# the same as a cancel
#
if ((num==int(self.__oldNum))and(newID==self.__oldID)and(newName==self.__oldName)):
self.cancel()
return
#
# If number changes - need to do a lot more stuff - so ask them
# if they are sure - and do it if they say yes
#
if (num!=int(self.__oldNum)):
oldnum=int(self.__oldNum)
text= "It will take a while to change forecasts "
text+="attributed to old forecaster number %d "%oldnum
text+="to new forecaster number %d.\n"%num
text+="\n"
text+="Are you sure you want to proceed?"
ynDiag=tkinter.messagebox.askyesno("Are you sure?",text,
parent=self,default=tkinter.messagebox.NO)
if not ynDiag:
self.__callbackMethod("Change")
self.cancel()
return
#
# OK - all input is valid and user wants to proceed
# so remove the 'change' dialog and actually make
# the changes
#
self.withdraw()
self.update_idletasks()
#
# make number changes (could take some time)
#
if (num!=int(self.__oldNum)):
self.changeNumbers()
#
# Change IDs and names - should be fast
#
Names=self.__VU.getFcstrNames()
IDs=self.__VU.getFcstrIDs()
numStr="%2.2d"%num
Names[numStr]=newName
IDs[numStr]=newID
self.__VU.setFcstrs(Names,IDs)
self.__VU.saveFcstrNums()
#
if self.__callbackMethod is not None:
self.__callbackMethod("Change")
self.cancel()
#------------------------------------------------------------------
# cancelCB - called when they click on Cancel when making
# changes
#
def cancelCB(self):
if self.__callbackMethod is not None:
self.__callbackMethod("Cancel")
self.cancel()
#------------------------------------------------------------------
# changeNumbers - gets called if they REALLY want to change
# numbers for a forecaster. Goes through all
# Official datafiles and changes all grids
# associated with old number to new number
#
def changeNumbers(self):
newNumStr=self.__numberVar.get().strip()
num=int(newNumStr)
oldnum=int(self.__oldNum)
for parm in self.__VU.getVerParms():
datatype=self.__VU.getVerParmType(parm)
if not self.__VU.checkFile(parm,"Official",modify=1,datatype=datatype):
continue
fnc=self.__VU.fncFcstr[:,:]
count=add.reduce(add.reduce(equal(fnc,oldnum)))
nnc=where(equal(fnc,oldnum),num,fnc)
self.__VU.fncFcstr[:,:]=nnc[:,:].astype(int8)
self.__VU.closeFcstFile()
newID=self.__idVar.get().strip()
newName=self.__nameVar.get().strip()
Names=self.__VU.getFcstrNames()
IDs=self.__VU.getFcstrIDs()
del Names[self.__oldNum]
del IDs[self.__oldNum]
newNumStr="%2.2d"%num
Names[newNumStr]=newName
IDs[newNumStr]=newID
self.__VU.setFcstrs(Names,IDs)
self.__VU.saveFcstrNums()
return
#=====================================================================
# AddCancelDialog - for when they want to add a forecaster
#
class AddCancelDialog(Dialog):
def __init__(self, VU, numberVar, idVar, nameVar,
parent=None, name="Add Forecaster", callbackMethod=None,
modal=1):
self.__parent = parent
self.__name = name
self.__modal = modal
self.__callbackMethod = callbackMethod
self.__VU=VU
self.__numberVar=numberVar
self.__idVar=idVar
self.__nameVar=nameVar
self.__dialog=Dialog.__init__(self,parent=self.__parent,
title=self.__name,
modal=self.__modal)
return
#------------------------------------------------------------------
# buttonbox - special buttonbox with Add and Cancel buttons
#
def buttonbox(self):
buttonFrame = tkinter.Frame(self)
tkinter.Button(buttonFrame, text="Add", width=7,
command=self.addCB).pack(side=tkinter.LEFT, pady=5, padx=10)
tkinter.Button(buttonFrame, text="Cancel",width=7,
command=self.cancelCB).pack(side=tkinter.LEFT,pady=5, padx=10)
buttonFrame.pack(side=tkinter.BOTTOM,expand=0)
#------------------------------------------------------------------
# body - special body with the current number, username, and
# display name shown
#
def body(self, master):
tkinter.Label(master,text="Number:").grid(column=0,row=0,sticky=tkinter.E)
tkinter.Label(master,text="Username:").grid(column=0,row=1,sticky=tkinter.E)
tkinter.Label(master,text="Display Name:").grid(column=0,row=2,sticky=tkinter.E)
self.numEntry=tkinter.Entry(master,textvariable=self.__numberVar,width=2)
self.numEntry.grid(column=1,row=0,sticky=tkinter.W)
self.idEntry=tkinter.Entry(master,textvariable=self.__idVar,width=8)
self.idEntry.grid(column=1,row=1,sticky=tkinter.W)
self.nameEntry=tkinter.Entry(master,textvariable=self.__nameVar,width=25)
self.nameEntry.grid(column=1,row=2,sticky=tkinter.W)
#------------------------------------------------------------------
# addCB - called when they click on Add. Need to validate
# everything to make sure the changes are OK.
#
def addCB(self):
#
# Check forecaster number for just a number, between 1 and 99
#
newNumStr=self.__numberVar.get().strip()
try:
num=int(newNumStr)
except:
tkinter.messagebox.showerror("Error","Forecaster Number needs to be an integer number",
parent=self)
self.numEntry.selection_range(0,tkinter.END)
self.numEntry.focus_set()
return
if ((num<0)or(num>99)):
tkinter.messagebox.showerror("Error","Forecater Number needs to be between 1 and 99",
parent=self)
self.numEntry.selection_range(0,tkinter.END)
self.numEntry.focus_set()
return
if (num==0):
tkinter.messagebox.showerror("Error","Forecaster Number 0 cannot be used",
parent=self)
self.numEntry.selection_range(0,tkinter.END)
self.numEntry.focus_set()
return
#
# Make sure the new number is not already
# in use.
#
curNums=self.__VU.getFcstrNums()
for testNum in curNums:
if int(testNum)==num:
tkinter.messagebox.showerror("Error","Forecaster Number %d is already in use"%num,
parent=self)
self.numEntry.selection_range(0,tkinter.END)
self.numEntry.focus_set()
return
#
# Must provide a username
#
newID=self.__idVar.get().strip()
if len(newID)==0:
tkinter.messagebox.showerror("Error","You must provide a username",
parent=self)
self.idEntry.selection_range(0,tkinter.END)
self.idEntry.focus_set()
return
#
# username cannot be the same as any other current username
#
curIDs=self.__VU.getFcstrIDs()
for testID in curIDs.values():
if testID==newID:
tkinter.messagebox.showerror("Error","Username %s is already in use"%newID,
parent=self)
self.idEntry.selection_range(0,tkinter.END)
self.idEntry.focus_set()
return
#
# Must provide a display name
#
newName=self.__nameVar.get().strip()
if len(newName)==0:
tkinter.messagebox.showerror("Error","You must provide a display name",
parent=self)
self.nameEntry.selection_range(0,tkinter.END)
self.nameEntry.focus_set()
return
#
# No number changes - but change IDs and names
#
self.withdraw()
self.update_idletasks()
Names=self.__VU.getFcstrNames()
IDs=self.__VU.getFcstrIDs()
numStr="%2.2d"%num
Names[numStr]=newName
IDs[numStr]=newID
self.__VU.setFcstrs(Names,IDs)
self.__VU.saveFcstrNums()
#
if self.__callbackMethod is not None:
self.__callbackMethod("Add")
self.cancel()
#------------------------------------------------------------------
# cancelCB - called when they click on Cancel when making
# changes
#
def cancelCB(self):
if self.__callbackMethod is not None:
self.__callbackMethod("Cancel")
self.cancel()
#==============================================================================
# doneDialog - a generic dialog class with a single DONE button at the bottom
#
class doneDialog(Dialog):
def __init__(self, parent=None, name="nonModal Dialog", callbackMethod=None,
modal=1):
self.__parent = parent
self.__name = name
self.__modal = modal
self.__callbackMethod = callbackMethod
self.__dialog=Dialog.__init__(self,parent=self.__parent,
title=self.__name,
modal=self.__modal)
return self.__dialog
def buttonbox(self):
buttonFrame = tkinter.Frame(self)
tkinter.Button(buttonFrame, text="Done", width=10,
command=self.doneCB).pack(side=tkinter.RIGHT, pady=5, padx=10)
buttonFrame.pack(side=tkinter.BOTTOM,expand=0)
def body(self, master):
bodylabel=tkinter.Label(master,text="This is the body of doneDialog")
bodylabel.pack(side=tkinter.BOTTOM)
def doneCB(self):
if self.__callbackMethod is not None:
self.__callbackMethod("Done")
self.cancel()
#=======================================================================
# ListDialog - shows all the forecasts made by the specified
# forecaster number
#
class ListDialog(doneDialog):
def __init__(self, VU, numstr, idstr, namestr, parent=None,
name="List of forecasts", callbackMethod=None, modal=1):
self.__VU=VU
self.__numstr=numstr
self.__idstr=idstr
self.__namestr=namestr
self.__parent=parent
doneDialog.__init__(self,parent=parent,name=name,callbackMethod=callbackMethod,
modal=modal)
def body(self,master):
sb=tkinter.Scrollbar(master=master)
sb.pack(side=tkinter.RIGHT,fill=tkinter.Y)
self.cf=tkinter.font.Font(family="Courier",size=-12)
txt=tkinter.Text(master=master,width=60,height=30,yscrollcommand=sb.set,
font=self.cf)
txt.pack(side=tkinter.LEFT,fill=tkinter.BOTH,expand=1)
sb.config(command=txt.yview)
text=""
maxparmwidth=0
totalgrids=0
oldnum=int(self.__numstr)
fcstsMade={}
for parm in self.__VU.getVerParms():
maxparmwidth=max(maxparmwidth,len(parm))
datatype=self.__VU.getVerParmType(parm)
if not self.__VU.checkFile(parm,"Official",modify=0,datatype=datatype):
continue
fnc=self.__VU.fncFcstr[:,:]
involved=logical_or.reduce(equal(fnc,oldnum),1)
recsUsed=compress(involved,self.__VU.fncRecs)
for i in range(recsUsed.shape[0]):
rec=int(recsUsed[i])
totalgrids+=1
basekey="%d"%self.__VU.fncBtime[rec]
if basekey not in fcstsMade:
fcstsMade[basekey]={}
if parm not in fcstsMade[basekey]:
fcstsMade[basekey][parm]=(1,self.__VU.fncStime[rec],self.__VU.fncEtime[rec])
else:
(num,start,end)=fcstsMade[basekey][parm]
num+=1
start=min(self.__VU.fncStime[rec],start)
end=max(self.__VU.fncEtime[rec],end)
fcstsMade[basekey][parm]=(num,start,end)
self.__VU.closeFcstFile()
#
# Display the data
#
cbb=tkinter.font.Font(family="Courier",size=-14,weight=tkinter.font.BOLD)
cb=tkinter.font.Font(family="Courier",size=-12,weight=tkinter.font.BOLD)
txt.tag_config("title",font=cbb)
txt.tag_config("date",font=cb)
txt.insert(tkinter.END,"%s Grids:\n\n"%self.__namestr,("title"))
if totalgrids==0:
txt.insert(tkinter.END," NONE\n\n",("date"))
bases=list(fcstsMade.keys())
bases.sort()
bases.reverse()
fmtn=" %%%ds: %%3d grids made from %%3d to %%3d hours\n"%(maxparmwidth)
fmt1=" %%%ds: %%3d grid made from %%3d to %%3d hours\n"%(maxparmwidth)
for base in bases:
(gyea,gmon,gday,ghou,gmin,gsec,gwda,gyda,gdst)=time.gmtime(int(base))
txt.insert(tkinter.END,"%s %s %d, %4.4d %2.2dZ:\n"%(DAYS[gwda],
MONS[gmon],gday,gyea,ghou),("date"))
made=fcstsMade[base]
parms=list(made.keys())
parms.sort()
for parm in parms:
(num,start,end)=made[parm]
starthr=int((start-int(base))/3600.)
endhr=int((end-int(base))/3600.)
fmt=fmtn
if num==1:
fmt=fmt1
txt.insert(tkinter.END,fmt%(parm,num,starthr,endhr))
txt.insert(tkinter.END,"\n")
txt.config(state=tkinter.DISABLED)
return
#=======================================================================
# InfoDialog - shows info about the specified grid
#
class InfoDialog(doneDialog):
def __init__(self, VU, model, parm, record, parent=None,
name="Grid Info", callbackMethod=None, modal=1):
self.__VU=VU
self.__model=model
self.__parm=parm
self.__record=record
self.__parent=parent
doneDialog.__init__(self,parent=parent,name=name,callbackMethod=callbackMethod,
modal=modal)
def body(self,master):
sb=tkinter.Scrollbar(master=master)
sb.pack(side=tkinter.RIGHT,fill=tkinter.Y)
self.cf=tkinter.font.Font(family="Courier",size=-12)
txt=tkinter.Text(master=master,width=60,height=20,yscrollcommand=sb.set,
font=self.cf)
txt.pack(side=tkinter.LEFT,fill=tkinter.BOTH,expand=1)
sb.config(command=txt.yview)
obsmodels=self.__VU.getCFG('OBSMODELS')
text=""
#
if not self.__VU.checkFile(self.__parm,self.__model,modify=0):
text+="\n\nCould not read info for %s %s grid! \n\n"%(self.__model,self.__parm)
else:
if self.__model in obsmodels:
btime=self.__VU.oncBtime[self.__record]
stime=self.__VU.oncStime[self.__record]
etime=self.__VU.oncEtime[self.__record]
vtime=self.__VU.oncVtime[self.__record]
fnums=[]
else:
btime=self.__VU.fncBtime[self.__record]
stime=self.__VU.fncStime[self.__record]
etime=self.__VU.fncEtime[self.__record]
vtime=self.__VU.fncVtime[self.__record]
fnums=self.__VU.fncFcstr[self.__record,:]
datatype=self.__VU.getVerParmType(self.__parm)
gridData=self.__VU.readRecord(self.__parm,self.__model,self.__record)
if datatype!=1:
minval=minimum.reduce(minimum.reduce(gridData))
maxval=maximum.reduce(maximum.reduce(gridData))
sum=add.reduce(add.reduce(gridData))
sumsqr=add.reduce(add.reduce(gridData*gridData))
else:
(mag,direc)=gridData
minval=minimum.reduce(minimum.reduce(mag))
maxval=maximum.reduce(maximum.reduce(mag))
sum=add.reduce(add.reduce(mag))
sumsqr=add.reduce(add.reduce(mag*mag))
gs = self.__VU.getGridShape()
numpts=gs[0]*gs[1]
avg=sum/numpts
std=sqrt((sumsqr/numpts)-(avg*avg))
self.__VU.closeFcstFile()
prec=self.__VU.getParmPrecision(self.__model,self.__parm)
if prec>0:
fmt1="%%.%df"%prec
fmt2="%%.%df"%(prec+1)
else:
fmt1="%d"
fmt2="%.1f"
(byea,bmon,bday,bhou,bmin,bsec,bwda,byda,bdst)=time.gmtime(btime)
(syea,smon,sday,shou,smin,ssec,swda,syda,sdst)=time.gmtime(stime)
(eyea,emon,eday,ehou,emin,esec,ewda,eyda,edst)=time.gmtime(etime)
(vyea,vmon,vday,vhou,vmin,vsec,vwda,vyda,vdst)=time.gmtime(vtime)
if self.__model in obsmodels:
if (sday==eday):
text+="\n%s %s grid from %2.2d-%2.2dZ %4.4d/%2.2d/%2.2d\n"%(self.__model,
self.__parm,shou,ehou,syea,smon,sday)
else:
if (smon==emon):
text+="\n%s %s grid from %2.2d %2.2dZ through %2.2d %2.2dZ %4.4d/%2.2d\n"%(self.__model,
self.__parm,sday,shou,eday,ehou,syea,smon)
else:
text+="\n%s %s grid from %4.4d/%2.2d/%2.2d %2.2dZ through %4.4d/%2.2d/%2.2d %2.2dZ\n"%(self.__model,
self.__parm,syea,smon,sday,shou,eyea,emon,eday,ehou)
else:
text+="\n%s %s grid from %2.2dZ run %4.4d/%2.2d/%2.2d\n"%(self.__model,
self.__parm,bhou,byea,bmon,bday)
text+="\n"
#
# Show forecast hour and valid time
#
if self.__model not in obsmodels:
fhr=int((stime-btime)/3600.0)
text+=" %d-hr forecast\n"%fhr
if (sday==eday):
text+=" Valid: %2.2d-%2.2dZ %4.4d/%2.2d/%2.2d\n"%(shou,ehou,
syea,smon,sday)
else:
if (smon==emon):
text+=" Valid: %2.2d %2.2dZ through %2.2d %2.2dZ %4.4d/%2.2d\n"%(sday,
shou,eday,ehou,syea,smon)
else:
text+=" Valid: %4.4d/%2.2d/%2.2d %2.2dZ through %4.4d/%2.2d/%2.2d %2.2dZ\n"%(syea,
smon,sday,shou,eyea,emon,eday,ehou)
text+="\n"
#
# Show archive time
#
text+=" Archived at %2.2d:%2.2dZ %4.4d/%2.2d/%2.2d\n"%(vhou,vmin,vyea,vmon,vday)
text+="\n"
#
# Show forecasters
#
if self.__model=="Official":
text+=" Forecasters:\n"
for j in range(fnums.shape[0]):
if fnums[j]>0:
text+=" %2.2d - %s \n"%(fnums[j],self.__VU.getFcstrName(fnums[j]))
text+="\n"
#
# Show stats
#
minvalStr=fmt1%minval
maxvalStr=fmt1%maxval
avgStr=fmt2%avg
stdStr=fmt2%std
text+=" Minimum: %s\n"%minvalStr
text+=" Maximum: %s\n"%maxvalStr
text+=" Average: %s\n"%avgStr
text+=" Std Dev: %s\n"%stdStr
text+="\n"
#
#
#
txt.insert(tkinter.END,text)
txt.config(state=tkinter.DISABLED)
return
#=======================================================================
#
# The main BOIVerify Info dialog box
#
class VerifInfo(doneDialog):
def __init__(self, VU, parent=None, name="nonModal Dialog",
callbackMethod=None, modal=1):
self.__parent=parent
self.__VU=VU
self.fontHeight=18 # in pixels (well, not quite, but close)
self.boxWidth=7 # width of hour, in pixels
self.hourWidth=self.boxWidth+3
self.rowH=self.fontHeight+5 # 5 pixels to surround box and allow sep line
self.scrollIncY = self.rowH // 3
self.scrollIncX=self.scrollIncY*2
self.scrbuffer=10 # within this many pixels of edge - it auto-scrolls
self.yoff=2 # to space down past border
self.cfb=tkinter.font.Font(family="Arial",size=-self.fontHeight,weight=tkinter.font.BOLD)
self.cf=tkinter.font.Font(family="Arial",size=-10)
self.fcstrNames=self.__VU.getFcstrNames()
self.fcstrIDs=self.__VU.getFcstrIDs()
self.fcstrNums=self.__VU.getFcstrNums()
self.usedFcstrs=[]
self.fcbstates=[]
dialog=doneDialog.__init__(self,parent=parent,name=name,
callbackMethod=callbackMethod,
modal=modal)
#
# Now that dialog exists - set minimum size on dialog box - then expand
#
geom=dialog.geometry()
(wh,rest)=geom.split("+",1)
(wid,hgt)=wh.split("x",1)
#
# Now make it a more reasonable width
#
iwid=int(wid)+300
geom="%dx%s+%s"%(iwid,hgt,rest)
dialog.geometry(geom)
#
# Setup dialog for latest date
#
self.displayDate()
return
#------------------------------------------------------------------
# newModel - called when a new model is chosen from the list of
# models. Have to read in all the model basetimes and
# find the basetime closest to the currently displayed
# basetime
#
def newModel(self):
#
# Get new model name and setup message while working
#
model=self.ml.getCurrentSelection()
msgWindow=messageWindow("Searching %s Grids"%model,self)
try:
#
# Get new parms and put them in order
#
parmList=self.__VU.listModelParms(model)
self.parmList=self.orderParms(parmList)
self.drawParmNames()
#
# Get time being shown for current model...will
# try to match this time for the new model
#
timeindex=self.tl.getCurrentIndex()
oldbasetime=self.times[timeindex]
#
# Get new basetimes for this new model - want to search through
# parm that has the least grids. We'll guess MaxT - but if
# there is no MaxT for this model - use the first parm and
# get all the basetimes for that parm
#
self.times=[]
if "MaxT" in self.parmList:
self.times=self.__VU.getBases("MaxT",model)
else:
self.times=self.__VU.getBases(self.parmList[0],model)
#
# Search through basetimes trying to find the index
# with the basetime closest to the basetime we had
# before
#
self.times.sort()
self.times.reverse()
self.timestrs=[]
defentry=0
defindex=0
mindiff=abs(self.times[0]-oldbasetime)
for i in range(len(self.times)):
btime=self.times[i]
(gyea,gmon,gday,ghou,gmin,gsec,gwda,gyda,gdst)=time.gmtime(btime)
self.timestrs.append("%2.2d/%2.2d/%4.4d %2.2dZ"%(gmon,gday,gyea,ghou))
diff=abs(btime-oldbasetime)
if diff<mindiff:
mindiff=diff
defindex=i
#
# Now set the basetime to the new defindex basetime
#
defentry=self.timestrs[defindex] # ?? what if no entries in list?
self.tl.newEntries(self.timestrs,defentry)
except:
pass
#
# Close the message window...we're done working
#
msgWindow.destroy()
#
# Now show the data for the new model for the default basetime
# just selected
#
self.displayDate()
#
return
#------------------------------------------------------------------
#
# For a set of inputParms, put them in the order specified by
# PARMINFO_ORDER. If there are other parms - put them alphabetically
# after the parms in PARMINFO_ORDER.
#
def orderParms(self,inputParms):
inputParms.sort()
parmOrder=self.__VU.getCFG('PARMINFO_ORDER')
outputParms=[]
for parm in parmOrder:
if parm in inputParms:
outputParms.append(parm)
inputParms.remove(parm)
for parm in inputParms:
outputParms.append(parm)
return outputParms
#------------------------------------------------------------------
# redisplayDate - re-draw timeblocks...but scroll back to where
# it is currently displayed
#
def redisplayDate(self):
#
# Get current canvas location and percentage within
# the scrolling areas
#
cx=self.cGrd.canvasx(0)
cy=self.cGrd.canvasy(0)
sr=self.cGrd.cget("scrollregion")
(xmin,ymin,xmax,ymax)=sr.split()
xp=(cx-int(xmin))/(int(xmax)-int(xmin))
yp=(cy-int(ymin))/(int(ymax)-int(ymin))
#
# Re-draw date
#
self.displayDate()
#
# Scroll back to old location
#
self.cGrd.xview("moveto",xp)
self.cTim.xview("moveto",xp)
self.cGrd.yview("moveto",yp)
self.cLab.yview("moveto",yp)
return
#------------------------------------------------------------------
# changeDate - possibly change to a new date - but if the new
# date is the same as that currently displayed - then
# just re-display the current date (this keeps it
# from scrolling off to a different location within
# the curent dates forecasts from being shown).
#
def changeDate(self):
index=self.tl.getCurrentIndex()
basetime=self.times[index]
if basetime==self.baseDisplayed:
self.redisplayDate()
else:
self.displayDate()
return
#------------------------------------------------------------------
# displayDate - Display timeblocks for the date currently selected
# in the "tl" timelist (self.times)
#
def displayDate(self):
#
# Get the basetime and setup message while working
#
index=self.tl.getCurrentIndex()
basetime=self.times[index]
model=self.ml.getCurrentSelection()
obsmodels=self.__VU.getCFG('OBSMODELS')
if model in obsmodels:
msgWindow=messageWindow("Reading %s Grids"%model,self)
else:
msgWindow=messageWindow("Reading %s Grids"%self.timestrs[index],self)
try:
self.usedFcstrs=[]
model=self.ml.getCurrentSelection()
#
# Stop any timing events for the current canvas
#
if self.screvent is not None:
self.cGrd.after_cancel(self.screvent)
self.screvent=None
#
# Clear the canvas
#
self.cGrd.delete(tkinter.ALL)
self.cTim.delete(tkinter.ALL)
#
#
#
minpix=0
mintime=basetime
maxpix=0
maxtime=basetime
fnums=self.__VU.getFcstrNums()
fnums.sort()
colorgroups=[]
#
# Loop over parms to display
#
for i in range(len(self.parmList)):
parm=self.parmList[i]
#
# Loop over records for this basetime for
# the parm/model
#
if model not in obsmodels:
recs=self.__VU.getFcstRecords(parm,model,basetime)
else:
ret=self.__VU.checkFile(parm,model)
recs=list(self.__VU.oncRecs)
for rec in recs:
rec = int(rec)
#
# Get times for this record and keep track of
# mintime/maxtime
#
if model not in obsmodels:
stime=self.__VU.fncStime[rec]
etime=self.__VU.fncEtime[rec]
else:
stime=self.__VU.oncStime[rec]
etime=self.__VU.oncEtime[rec]
#
# 'deleted' records have a 'zero' start/end time
# we don't want to display those - we ignore them
#
if ((stime<5000)or(etime<5000)):
continue
maxtime=max(maxtime,etime)
mintime=min(mintime,stime)
#
# For Official model..find color/pattern based
# on forecaster numbers
#
if model=="Official":
fcstrs=self.__VU.fncFcstr[rec,:]
flist=list(fcstrs)
flist.sort()
flist.reverse()
somebody=greater(fcstrs,0)
numFcstrs=add.reduce(somebody) # number of forecasters who touched grid
if numFcstrs==0: # no forecasters - use unknown 'white' color
colorfill="white"
stippletype=""
elif numFcstrs==1:
colorfill="white"
stippletype=""
fnum="%2.2d"%compress(somebody,fcstrs)
if fnum not in self.usedFcstrs:
self.usedFcstrs.append(fnum)
for j in range(len(fnums)):
if fnum==fnums[j]:
colorfill=COLORLIST[j%len(COLORLIST)]
break
else:
involved=compress(somebody,fcstrs)
for f in involved:
fadd="%2.2d"%f
if fadd not in self.usedFcstrs:
self.usedFcstrs.append(fadd)
stippletype="gray50"
match=0
if len(colorgroups)>0:
for colorgroup in colorgroups:
(fcstrlist,color)=colorgroup
match=1
for j in range(len(fcstrlist)):
if fcstrlist[j]!=flist[j]:
match=0
break
if match==1:
colorfill=color
break
if match==0:
colorfill=COLORLIST[len(colorgroups)%len(COLORLIST)]
newgroup=(flist,colorfill)
colorgroups.append(newgroup)
else: # anything but Official...has white/solid timeblocks
colorfill="white"
stippletype=""
#
# Setup tags with rec:(record number),
# parm:(parm name),
# col:(original color)
#
tagtuple=("grid","rec:%d"%rec,"parm:%s"%parm,"col:%s"%colorfill)
#
# find coordinates of box based on time and row (i)
#
shr = (stime - basetime) // 3600
ehr = (etime - basetime) // 3600
x1=shr*(self.hourWidth)+2
x2=ehr*(self.hourWidth)-2
y1=(i*self.rowH)+self.yoff+2
y2=y1+self.fontHeight-1
#
# Make the timeblock box
#
self.cGrd.create_polygon(x1,y1,x1,y2,x2,y2,x2,y1,fill=colorfill,outline=colorfill,
stipple=stippletype,width=1,tags=tagtuple)
#
# Keep track of max/min times displayed
#
maxpix=max(maxpix,ehr*(self.hourWidth))
minpix=min(minpix,shr*(self.hourWidth))
#
# Setup bindings for popups on the grid boxes
#
self.cGrd.tag_bind("grid","<Button-3>",self.postPopGrid)
if model=="Official":
self.cGrd.tag_bind("grid","<Button-2>",self.extract)
self.cGrd.bind("<Button-1>",self.buttonstart)
self.cGrd.bind("<B1-Motion>",self.drag)
self.cGrd.bind("<ButtonRelease-1>",self.buttonstop)
else:
self.cGrd.tag_unbind("grid","<Button-2>")
self.cGrd.unbind("<Button-1>")
self.cGrd.unbind("<B1-Motion>")
self.cGrd.unbind("<ButtonRelease-1>")
#
# Setup scrolling regions for grid canvas and timelabel canvas
#
self.cGrd.configure(scrollregion=(minpix,0,maxpix,self.parmHeight))
self.cTim.configure(scrollregion=(minpix,0,maxpix,50))
#
# Horizontally move to the start of this basetime
# (for Official add 12 hours)
#
offset=0
if model=="Official":
offset=12*self.hourWidth
x0=float(offset-minpix)/float(maxpix-minpix)
self.cGrd.xview("moveto",x0)
self.cTim.xview("moveto",x0)
#
# Make time marks from mintime to maxtime
# tick marks at hourly intervals
# hash marks through grid canvas at 6 hourly intervals
# label centerred above 12Z each day
#
for jtim in range(mintime,maxtime,3600):
(gyea,gmon,gday,ghou,gmin,gsec,gwda,gyda,gdst)=time.gmtime(jtim)
fhr = (jtim - basetime) // 3600
x=fhr*self.hourWidth
if ghou==0:
ywid=30
self.cGrd.create_line(x,0,x,50*self.rowH,fill="blue",stipple="gray50",tags="hash")
elif (ghou%6==0):
ywid=10
self.cTim.create_text(x,30-ywid,anchor=tkinter.S,fill="white",font=self.cf,text="%2.2d"%ghou)
self.cGrd.create_line(x,0,x,50*self.rowH,fill="blue",stipple="gray50",tags="hash")
if ghou==12:
self.cTim.create_text(x,30-ywid-10,anchor=tkinter.S,
fill="white",font=self.cf,
text="%s %d (%s)"%(MONS[gmon],gday,DAYS[gwda]))
else:
ywid=5
self.cTim.create_line(x,30,x,30-ywid,fill="white")
self.cGrd.lower("hash")
#
# Update the color boxes next to forecaster names...based on
# the forecasters active making the grids currently displayed
#
self.updateFcstrButtons()
#
# Check to see if the 'set combo' button can be made active
#
self.checkSetButton()
#
# set the baseDisplayed time...so future changes in date
# can know what is on the screen now...
#
self.baseDisplayed=basetime
except:
pass
#
# Close the message window - we're done displaying this
# basetime
#
msgWindow.destroy()
return
#-----------------------------------------------------------------
# body - custom body that has GridManager-like qualities. It
# displays gridblocks with forecaster numbers associated
# with each grid
#
def body(self, master):
#
#
#
self.screvent=None # grid canvas timing events
self.baseDisplayed=0
#
self.modelstrs=self.__VU.listModels() # what if no models ?
obsmodels=self.__VU.getCFG('OBSMODELS') # what if no obs models ?
for model in obsmodels:
if model not in self.modelstrs:
self.modelstrs.append(model)
self.modelstrs.sort()
if "Official" in self.modelstrs:
defmodel="Official"
else:
defmodel=self.modelstrs[0]
#
parmList=self.__VU.listModelParms(defmodel)
self.parmList=self.orderParms(parmList)
#
# Popup Menu for forecaster actions
#
self.popFcstr=tkinter.Menu(master=master,tearoff=0)
self.popFcstr.add_command(label="Edit",command=self.editFcstr)
self.popFcstr.add_command(label="Delete",command=self.deleteFcstr)
self.popFcstr.add_command(label="List Forecasts",command=self.listFcstr)
#
# Popup Menu for grid button-3 actions
#
self.popGrid=tkinter.Menu(master=master,tearoff=0)
self.popGrid.add_command(label="Display Info",command=self.gridInfo)
self.popGrid.add_separator()
self.popGrid.add_command(label="Delete Grid",command=self.gridDelete)
#
# Get base times of model
#
self.times=self.__VU.getBases(self.parmList[0],defmodel)
self.times.sort()
self.times.reverse()
self.timestrs=[]
for i in self.times:
print("time:", i)
(gyea,gmon,gday,ghou,gmin,gsec,gwda,gyda,gdst)=time.gmtime(i)
self.timestrs.append("%2.2d/%2.2d/%4.4d %2.2dZ"%(gmon,gday,gyea,ghou))
defentry=self.timestrs[0] # ?? what if no entries in list?
#
pwid=0
for i in range(len(self.parmList)):
parm=self.parmList[i]
pwid=max(pwid,self.cfb.measure(parm))
parmWidth=pwid+(4*2) # four of the 2-pixel spacers
self.parmHeight=(len(self.parmList)*self.rowH)+(self.yoff*2)
topframe=tkinter.Frame(master)
lab=tkinter.Label(topframe,text="Model:")
lab.pack(side=tkinter.LEFT)
self.ml=comboList(defmodel,self.modelstrs,parent=topframe,
callbackMethod=self.newModel)
self.ml.cf.pack(side=tkinter.LEFT,fill=tkinter.NONE,expand=0)
fr=tkinter.Frame(topframe,width=10)
fr.pack(side=tkinter.LEFT)
lab=tkinter.Label(topframe,text="Date/Cycle:")
lab.pack(side=tkinter.LEFT,padx=10,anchor=tkinter.W)
self.prevBut=tkinter.Button(topframe,text="<<",pady=0,padx=0,command=self.prevCycle)
self.prevBut.pack(side=tkinter.LEFT)
self.tl=comboList(defentry,self.timestrs,parent=topframe,
callbackMethod=self.changeDate)
self.tl.cf.pack(side=tkinter.LEFT,fill=tkinter.NONE,expand=0)
self.nextBut=tkinter.Button(topframe,text=">>",pady=0,padx=0,command=self.nextCycle)
self.nextBut.pack(side=tkinter.LEFT)
topframe.pack(side=tkinter.TOP,anchor="w")
botframe=tkinter.Frame(master)
self.sHor=tkinter.Scrollbar(botframe,orient=tkinter.HORIZONTAL)
self.sVer=tkinter.Scrollbar(botframe,orient=tkinter.VERTICAL)
self.cLab=tkinter.Canvas(botframe,relief=tkinter.SUNKEN,width=parmWidth,
height=50,scrollregion=(0,0,parmWidth,self.parmHeight),
bg="black",
bd=2,yscrollcommand=self.sVer.set,
yscrollincrement=self.scrollIncY)
self.cTim=tkinter.Canvas(botframe,relief=tkinter.SUNKEN,width=200,
height=30,bd=2,xscrollcommand=self.sHor.set,
bg="black",
scrollregion=(0,0,400,30),
xscrollincrement=self.scrollIncX)
self.cGrd=tkinter.Canvas(botframe,relief=tkinter.SUNKEN,width=200,
height=50,bd=2,xscrollcommand=self.sHor.set,
bg="black",
scrollregion=(0,0,400,self.parmHeight),
yscrollcommand=self.sVer.set,
xscrollincrement=self.scrollIncX,yscrollincrement=self.scrollIncY)
self.cGrd.bind("<Button-1>",self.buttonstart)
self.cGrd.bind("<B1-Motion>",self.drag)
self.cGrd.bind("<ButtonRelease-1>",self.buttonstop)
self.fFcs=tkinter.Frame(botframe,width=100,height=50,relief=tkinter.SUNKEN,
bd=2)
self.updateFcstrButtons()
#
#
#
self.sHor.config(command=self.scrollBothX)
self.sVer.config(command=self.scrollBothY)
self.sHor.grid(row=0,column=1,sticky=tkinter.W+tkinter.E)
self.cTim.grid(row=1,column=1,sticky=tkinter.W+tkinter.E)
self.cLab.grid(row=2,column=0,sticky=tkinter.N+tkinter.S)
self.cGrd.grid(row=2,column=1,sticky=tkinter.W+tkinter.E+tkinter.N+tkinter.S)
self.sVer.grid(row=2,column=2,sticky=tkinter.N+tkinter.S)
self.fFcs.grid(row=2,column=3,sticky=tkinter.N+tkinter.S)
but=tkinter.Button(master=botframe,text="Add New Forecaster",
command=self.addFcstr,)
but.grid(row=1,column=3)
self.scb=tkinter.Button(master=botframe,text="Set Forecasters for Selected Grids",
command=self.setCombo,)
self.scb.grid(row=3,column=3,sticky=tkinter.N+tkinter.S)
botframe.grid_rowconfigure(0,weight=0)
botframe.grid_rowconfigure(1,weight=0)
botframe.grid_rowconfigure(2,weight=1,minsize=50)
botframe.grid_rowconfigure(3,weight=0)
botframe.grid_columnconfigure(0,weight=0,minsize=50)
botframe.grid_columnconfigure(1,weight=1,minsize=50)
botframe.grid_columnconfigure(2,weight=0)
botframe.grid_columnconfigure(3,weight=0,minsize=100)
botframe.pack(side=tkinter.TOP,expand=1,fill=tkinter.BOTH)
self.dlgtop=botframe.winfo_toplevel()
self.drawParmNames()
return
#------------------------------------------------------------------
# setCombo - set the forecaster number info on the currently
# selected grids with the forecasters currently "ON"
# in the checkboxes
def setCombo(self):
#
# scan thorugh fcbstates to get forecaster numbers of
# those that are are "ON". fnums is a list of numbers
#
fnums=[]
for i in range(len(self.fcstrNums)):
if self.fcbstates[i].get()>0:
fnums.append(self.fcstrNums[i])
#
# If too many forecasters in the combination...give them
# an error message
#
maxForecasters=self.__VU.getCFG("MAXFCSTRS")
if len(fnums)>maxForecasters:
tkinter.messagebox.showerror("Error","No more than %d forecasters on any grid"%maxForecasters,
parent=self)
return
#
# Loop through selected grids
#
selItems=self.cGrd.find_withtag("selected")
if selItems is not None:
for item in selItems:
grid=0
record=-1
parm=""
tags=self.cGrd.gettags(item)
for tag in tags:
if tag[0:4]=="grid":
grid=1
elif tag[0:4]=="rec:":
record=int(tag[4:])
elif tag[0:5]=="parm:":
parm=tag[5:]
if grid==1:
gridModel=self.ml.getCurrentSelection()
if self.__VU.checkFile(parm,gridModel,modify=1):
fcstrs=self.__VU.fncFcstr[record,:]
#print " %s %s %d fcstrs=%s"%(gridModel,parm,record,str(fcstrs))
for i in range(self.__VU.getCFG('MAXFCSTRS')):
self.__VU.fncFcstr[record,i]=0
for i in range(len(fnums)):
self.__VU.fncFcstr[record,i]=int(fnums[i])
fcstrs=self.__VU.fncFcstr[record,:]
#print " changed to %s"%str(fcstrs)
self.__VU.closeFcstFile()
self.redisplayDate()
return
#------------------------------------------------------------------
# updateFcstrButtons - update the display of forecaster buttons
# with new list of forecasters.
#
def updateFcstrButtons(self):
#
# get the current on/off states for each number
#
state={}
if len(self.fcbstates)>0:
for i in range(len(self.fcstrNums)):
num=self.fcstrNums[i]
state[num]=self.fcbstates[i].get()
stateKeys=list(state.keys())
#
# Delete any widgets currently in the frame
# the one that caused the callback will not
# be deleted (this is a memory leak!)
#
widgets=self.fFcs.pack_slaves()
if widgets is not None:
for widget in widgets:
widget.pack_forget()
del widget
#
# get the updated names/nums/IDs
#
self.fcstrNames=self.__VU.getFcstrNames()
self.fcstrIDs=self.__VU.getFcstrIDs()
self.fcstrNums=self.__VU.getFcstrNums()
#
#
maxwid=0
for num in self.fcstrNums:
maxwid=max(maxwid,len(self.fcstrNames[num]))
#
self.fcbstates=[]
for i in range(len(self.fcstrNums)):
num=self.fcstrNums[i]
label="%s - %s"%(num,self.fcstrNames[num])
rowframe=tkinter.Frame(master=self.fFcs,name="f%s"%num)
var=tkinter.IntVar()
if num in stateKeys:
var.set(state[num])
else:
var.set(0)
self.fcbstates.append(var)
if i==0:
color="white"
else:
color=COLORLIST[i%len(COLORLIST)]
cb=tkinter.Checkbutton(master=rowframe,text=label,indicatoron=1,
variable=var,padx=0,pady=0,name="c%s"%num,
command=self.checkSetButton)
#print "checking",num,"against usedFcstrs:",self.usedFcstrs
if ((i==0)or(num in self.usedFcstrs)):
mb=tkinter.Button(master=rowframe,relief=tkinter.FLAT,
command=cb.toggle,width=5,
text=" ",padx=0,pady=0,borderwidth=0,
background=color,foreground='white',name="b%s"%num,
activebackground=color,activeforeground='white')
else:
bgcol=rowframe.cget("bg")
mb=tkinter.Button(master=rowframe,
relief=tkinter.FLAT,command=cb.toggle,width=5,
text=" ",padx=0,pady=0,borderwidth=0,
background=bgcol,foreground='white',name="b%s"%num,
activebackground=bgcol,activeforeground='white')
mb.pack(side=tkinter.LEFT)
if i!=0:
mb.bind("<Button-3>",self.postPopFcstr)
cb.pack(side=tkinter.LEFT,anchor=tkinter.W)
rowframe.pack(side=tkinter.TOP,fill=tkinter.X,expand=1)
if i!=0:
rowframe.bind("<Button-3>",self.postPopFcstr)
cb.bind("<Button-3>",self.postPopFcstr)
#
#
#
df=tkinter.Frame(master=self.fFcs).pack(side=tkinter.TOP,fill=tkinter.BOTH,expand=1)
#
# give the dialog a chance to update size on its own
#
tl=self.fFcs.winfo_toplevel()
tl.update_idletasks()
#
# Set new minimum size -based on requested width/height
#
rwid=tl.winfo_reqwidth()
rhgt=tl.winfo_reqheight()
tl.minsize(rwid,rhgt)
#
# If height of current grid is not as big as the minimum
# size, then make that change manually (if they have
# modified the size earlier - the automatic propagate wont
# make it bigger)
#
geom=tl.geometry()
(wh,rest)=geom.split("+",1)
(wid,hgt)=wh.split("x",1)
if int(hgt)<int(rhgt):
tl.geometry("%sx%d"%(wid,int(rhgt)))
return
#------------------------------------------------------------------
# nextCycle - move to the next basetime in the "tl" self.times
# list of basetimes
#
def nextCycle(self):
timeindex=self.tl.getCurrentIndex()
if timeindex>0:
self.tl.setCurrentIndex(timeindex-1)
self.changeDate()
return
#------------------------------------------------------------------
# prevCycle - move to the previous basetime in the "tl" self.times
# list of basetimes
#
def prevCycle(self):
timeindex=self.tl.getCurrentIndex()
if timeindex<len(self.times)-1:
self.tl.setCurrentIndex(timeindex+1)
self.changeDate()
return
#------------------------------------------------------------------
# postPopGrid - post the popup that allows them to get info on
# a grid, or delete a grid
#
def postPopGrid(self,event):
curgrid=self.cGrd.find_withtag(tkinter.CURRENT)
grtags=self.cGrd.gettags(curgrid)
for tag in grtags:
if tag.find("rec")>-1:
self.gridRecord=int(tag[4:])
if tag.find("parm")>-1:
self.gridParm=tag[5:]
self.gridModel=self.ml.getCurrentSelection()
self.popGrid.post(event.x_root,event.y_root)
self.popGrid.grab_set()
#------------------------------------------------------------------
# gridInfo - post the dialog with info about the particular grid
# called from the popGrid popup menu
#
def gridInfo(self):
InfoDialog(self.__VU,self.gridModel,self.gridParm,self.gridRecord,parent=self)
return
#==================================================================
# gridDelete - delete the specified grid (but give them a chance
# to back out of it first)
# called from the popGrid popup menu
#
def gridDelete(self):
#
# Give them a chance to back out of deleting an archived grid.
#
obsmodels=self.__VU.getCFG('OBSMODELS')
model=self.gridModel
parm=self.gridParm
record=self.gridRecord
text=""
#
# Make sure we can open this file
#
if not self.__VU.checkFile(parm,model,modify=0):
text+="Cant delete this grid"
tkinter.messagebox.showerror("Error",text,parent=self)
return
#
# Make different warning message depending on whether
# it is a forecast grid or an observed grid
#
if model in obsmodels:
stime=self.__VU.oncStime[record]
etime=self.__VU.oncEtime[record]
(syea,smon,sday,shou,smin,ssec,swda,syda,sdst)=time.gmtime(stime)
(eyea,emon,eday,ehou,emin,esec,ewda,eyda,edst)=time.gmtime(etime)
text+="Are you sure you want to delete the %s %s grid "%(model,parm)
text+="from %2.2dZ %4.4d/%2.2d/%2.2d through %2.2dZ %4.4d/%2.2d/%2.2d\n\n"%(shou,
syea,smon,sday,ehou,eyea,emon,eday)
else:
btime=self.__VU.fncBtime[self.gridRecord]
(byea,bmon,bday,bhou,bmin,bsec,bwda,byda,bdst)=time.gmtime(btime)
stime=self.__VU.fncStime[self.gridRecord]
fhr=int((stime-btime)/3600.0)
text+="Are you sure you want to delete the %d-hr %s forecast "%(fhr,self.gridParm)
text+="from the %2.2dZ %4.4d/%2.2d/%2.2d run from %s?\n\n"%(bhou,
byea,bmon,bday,self.gridModel)
#
# But in all cases...give DIRE warning in messsage
# so that they think this through
#
text+="Once deleted it cannot be retreived!\n\n"
text+="CAREFULLY CONSIDER WHAT YOU ARE DOING!"
#
# Make sure that they want to continue
#
ynDiag=tkinter.messagebox.askyesno("Are you sure?",text,
parent=self,default=tkinter.messagebox.NO)
if not ynDiag:
return
#
# Delete the grid...and if there is an error doing that
# tell them
#
reclist=[record,]
if not self.__VU.deleteRecord(parm,model,reclist):
tkinter.messagebox.showerror("Error","Could not delete grid",parent=self)
return
#
# Finally...redisplay the grids for the current date
#
self.redisplayDate()
return
#------------------------------------------------------------------
# postPopFcstr - post the popup menu that allows them to edit info
# about a forecaster, list forecasts made by a
# forecaster, or delete a forecaster
#
def postPopFcstr(self,event):
self.editFnum=str(event.widget)[-2:]
self.editFID=self.fcstrIDs[self.editFnum]
self.editFname=self.fcstrNames[self.editFnum]
self.popFcstr.post(event.x_root,event.y_root)
self.popFcstr.grab_set()
#-----------------------------------------------------------------
# editFcstr - post the dialog where the forecaster number/id/name
# can be changed.
# This is called from the popFcstr popup menu
#
def editFcstr(self):
self.numVar=tkinter.StringVar()
self.numVar.set(self.editFnum)
self.idVar=tkinter.StringVar()
self.idVar.set(self.editFID)
self.nameVar=tkinter.StringVar()
self.nameVar.set(self.editFname)
ChangeCancelDialog(self.__VU, self.numVar,self.idVar,self.nameVar,
parent=self)
self.updateFcstrButtons()
return
#------------------------------------------------------------------
# deleteFcstr - delete a forecaster from the list of forecasters
# (give them a chance to back out of it first).
# Any grids currently attributed to this number
# will be changed into the 'unknown' forecaster
#
def deleteFcstr(self):
#
# Don't let them delete the 'unknown' forecaster
#
num=int(self.editFnum)
name=self.editFname
if num==0:
tkinter.messagebox.showerror("Error","You cannot delete the Unknown user",
parent=self)
return
#
# see how many grids this number is attributed to
#
msgWindow=messageWindow("Checking on forecaster %2.2d"%num,self)
try:
self.totalgrids=0
for parm in self.__VU.getVerParms():
datatype=self.__VU.getVerParmType(parm)
if not self.__VU.checkFile(parm,"Official",modify=0,datatype=datatype):
continue
fnc=self.__VU.fncFcstr[:,:]
numrec=add.reduce(add.reduce(equal(fnc,num)))
self.totalgrids+=numrec
self.__VU.closeFcstFile()
except:
pass
msgWindow.destroy()
#
# Give them a chance to back out of it.
#
if self.totalgrids>0:
text= "There are %d archived grids made by "%self.totalgrids
text+="forecaster number %d : %s.\n"%(num,name)
text+="\n"
text+="Are you sure you want to delete %s and "%name
text+="associate all those grids with the Unknown "
text+="forecaster?"
else:
text= "Are you sure you want to delete forecaster "
text+="number %d : %s ?"%(num,name)
ynDiag=tkinter.messagebox.askyesno("Are you sure?",text,
parent=self,default=tkinter.messagebox.NO)
if not ynDiag:
return
#
# setup a message window because this may take a while...
#
text="Deleting Forecaster #%d"%num
msgWindow=messageWindow(text,self)
try:
if self.totalgrids>0:
for parm in self.__VU.getVerParms():
#print "deleting #%d from %s"%(num,parm)
datatype=self.__VU.getVerParmType(parm)
if not self.__VU.checkFile(parm,"Official",modify=1,datatype=datatype):
#print "Could not open %s file for Official"%parm
continue
fnc=self.__VU.fncFcstr[:,:]
involved=logical_or.reduce(equal(fnc,num),1)
recsUsed=compress(involved,self.__VU.fncRecs)
for i in range(recsUsed.shape[0]):
rec=recsUsed[i]
fcstrs=fnc[rec,:]
#print " record %d has %s"%(rec,fcstrs)
numfcstrs=add.reduce(greater_equal(fcstrs,0))
if numfcstrs==1:
fcstrs[equal(fcstrs,num)] = 0
else:
fcstrs[equal(fcstrs,num)] = -127
#print " changed to %s"%fcstrs
fnc[rec,:]=fcstrs
self.__VU.fncFcstr[:,:]=fnc[:,:].astype(int8)
self.__VU.closeFcstFile()
numstr="%2.2d"%num
Names=self.__VU.getFcstrNames()
IDs=self.__VU.getFcstrIDs()
del Names[numstr]
del IDs[numstr]
self.__VU.setFcstrs(Names,IDs)
self.__VU.saveFcstrNums()
except:
tkinter.messagebox.showerror("Error","Could not delete forecaster #%d"%num,
parent=self)
msgWindow.destroy()
#
# re-draw list of forecaster buttons
#
self.updateFcstrButtons()
return
#------------------------------------------------------------------
# listFcstr - post the dialog where we display all the forecast
# made for this forecaster.
# Called by the PopFcstr popup menu
#
def listFcstr(self):
ListDialog(self.__VU,self.editFnum,self.editFID,
self.editFname,parent=self)
return
#------------------------------------------------------------------
# addFcstr - post the dialog where we can add a forecaster.
# called by the popFcstr popup menu
#
def addFcstr(self):
self.numVar=tkinter.StringVar()
self.numVar.set("")
self.idVar=tkinter.StringVar()
self.idVar.set("")
self.nameVar=tkinter.StringVar()
self.nameVar.set("")
AddCancelDialog(self.__VU, self.numVar,self.idVar,self.nameVar,
parent=self)
self.updateFcstrButtons()
return
#------------------------------------------------------------------
# drawParmNames - clear the parm name list - and draw text with new
# names
#
def drawParmNames(self):
self.cLab.delete(tkinter.ALL)
#
# Fill in parameter names
#
for i in range(len(self.parmList)):
parm=self.parmList[i]
yrow=i*(self.rowH)+self.yoff
self.cLab.create_text(5,yrow+3,anchor=tkinter.NW,fill="white",
font=self.cfb,text=parm)
return
#------------------------------------------------------------------
# scrollBothX - horizontally scrolls timebar and grid canvas -
# unless all of the X-scrollregion is already visible
#
def scrollBothX(self,*args):
sr=self.cGrd.cget('scrollregion').split()
sw=int(sr[2])-int(sr[0])
wd=self.cGrd.winfo_width()
if wd>=sw: # abort scross/moves if all of xscrollregion already visible
return None
self.cTim.xview(*args)
self.cGrd.xview(*args)
return None
#------------------------------------------------------------------
# scrollBothY - vertically scrolls parm lables and grid canvas -
# unless all of the Y-scrollregion is already visible
#
def scrollBothY(self,*args):
sr=self.cGrd.cget('scrollregion').split()
sh=int(sr[3])-int(sr[1])
hg=self.cGrd.winfo_height()
if hg>=sh: # abort scrolls/moves if all of yscrollregion already visible
return None
self.cLab.yview(*args)
self.cGrd.yview(*args)
return None
#------------------------------------------------------------------
# buttonstart - button 1 is pushed down. Store current location in
# xx,yy and store the starting location in xcstart,ycstart
# setup to call 'scrtest' (to test for auto-scrolling)
# if button is still down in a few milliseconds
#
def buttonstart(self,event):
self.xx=event.x
self.yy=event.y
self.xcstart=self.cGrd.canvasx(self.xx)
self.ycstart=self.cGrd.canvasy(self.yy)
self.screvent=self.cGrd.after(200,self.scrtest)
#
# If any grid boxes are 'selected' now - turn them off and
# set their color back to their original color
#
selItems=self.cGrd.find_withtag("selected")
if selItems is not None:
for item in selItems:
tags=self.cGrd.gettags(item)
for tag in tags:
if tag[0:4]=="col:":
oldcolor=tag[4:]
self.cGrd.itemconfigure(item,fill=oldcolor,outline=oldcolor,stipple="")
self.cGrd.dtag(tkinter.ALL,"selected")
#
return "break"
#------------------------------------------------------------------
# drag - button is held down while moving. Get new location in xx,yy
# and convert to new canvas location in xcnow,ycnow. Draw
# selection box from xcstart,ycstart to xcnow,ycnow.
#
def drag(self,event):
self.xx=event.x
self.yy=event.y
self.xcnow=self.cGrd.canvasx(self.xx)
self.ycnow=self.cGrd.canvasy(self.yy)
self.cGrd.delete('areasel')
self.cGrd.create_rectangle(self.xcstart,self.ycstart,
self.xcnow,self.ycnow,
outline="cyan",tags='areasel')
#
# Get selected grids, and any item inside the selection box
#
selItems=self.cGrd.find_withtag("selected")
inItems=self.cGrd.find_overlapping(self.xcstart,self.ycstart,
self.xcnow,self.ycnow)
#
# Check for grid items inside the selection box that are NOT
# currently in the selected list. For these - set them to
# selected and set their color to the highlight color
#
if inItems is not None:
for item in inItems:
tags=self.cGrd.gettags(item)
if "grid" in tags:
if item not in selItems:
newtags=list(tags)
newtags.append("selected")
self.cGrd.itemconfigure(item,fill="yellow",outline="yellow",stipple="gray12")
self.cGrd.itemconfigure(item,tags=tuple(newtags))
#
# Check currently selected items...and if no longer in the
# selection box, then turn their color back to their original color
#
if selItems is not None:
for item in selItems:
if item not in inItems:
tags=self.cGrd.gettags(item)
if "grid" in tags:
for tag in tags:
if tag[0:4]=="col:":
oldcolor=tag[4:]
self.cGrd.itemconfigure(item,fill=oldcolor,outline=oldcolor,stipple="")
self.cGrd.dtag(item,"selected")
#
# Finally check for status of 'set selected' button
#
self.checkSetButton()
self.cGrd.update_idletasks()
return "break"
#------------------------------------------------------------------
# scrtest - while button is down but not moving - check to see if
# pointer is in the auto-scrolling zone (within scrbuffer
# of edge of canvas) and scroll if so. If we scroll -
# then update the selection box.
#
def scrtest(self):
hg=self.cGrd.winfo_height()
wd=self.cGrd.winfo_width()
scrollflag=0
if self.xx<self.scrbuffer:
self.scrollBothX('scroll','-1','units')
scrollflag=1
if self.xx>(wd-self.scrbuffer):
self.scrollBothX('scroll','1','units')
scrollflag=1
if self.yy<self.scrbuffer:
self.scrollBothY('scroll','-1','units')
scrollflag=1
if self.yy>(hg-self.scrbuffer):
self.scrollBothY('scroll','1','units')
scrollflag=1
#
# If we scrolled - update the area that is highlighted
#
if scrollflag==1:
self.xcnow=self.cGrd.canvasx(self.xx)
self.ycnow=self.cGrd.canvasy(self.yy)
self.cGrd.delete('areasel')
self.cGrd.create_rectangle(self.xcstart,self.ycstart,
self.xcnow,self.ycnow,
fill='',outline="cyan",tags='areasel')
self.cGrd.update_idletasks()
#
# Check again for scrolling in a few milliseconds
#
self.screvent=self.cGrd.after(50,self.scrtest)
#------------------------------------------------------------------
# buttonstop - button 1 is released - save final position in xcnow,
# ycnow. Remove the selection box.
#
def buttonstop(self,event):
if self.screvent is not None:
self.cGrd.after_cancel(self.screvent)
self.screvent=None
self.xx=event.x
self.yy=event.y
self.xcnow=self.cGrd.canvasx(self.xx)
self.ycnow=self.cGrd.canvasy(self.yy)
self.cGrd.delete('areasel')
#
# Get selected grids, and any item inside the selection box
#
selItems=self.cGrd.find_withtag("selected")
inItems=self.cGrd.find_overlapping(self.xcstart,self.ycstart,
self.xcnow,self.ycnow)
#
# Check for grid items inside the selection box that are NOT
# currently in the selected list. For these - set them to
# selected and set their color to the highlight color
#
if inItems is not None:
for item in inItems:
tags=self.cGrd.gettags(item)
if "grid" in tags:
if item not in selItems:
newtags=list(tags)
newtags.append("selected")
self.cGrd.itemconfigure(item,fill="yellow",outline="yellow",stipple="gray12")
self.cGrd.itemconfigure(item,tags=tuple(newtags))
#
# Check currently selected items...and if no longer in the
# selection box, then turn their color back to their original color
#
if selItems is not None:
for item in selItems:
if item not in inItems:
tags=self.cGrd.gettags(item)
if "grid" in tags:
for tag in tags:
if tag[0:4]=="col:":
oldcolor=tag[4:]
self.cGrd.itemconfigure(item,fill=oldcolor,outline=oldcolor,stipple="")
self.cGrd.dtag(item,"selected")
self.checkSetButton()
self.cGrd.update_idletasks()
return "break"
#------------------------------------------------------------------
# checkSetButton - check to see if the "Set Forecasters for Selected
# Grids" button can be enabled. There have to be
# some selected grids - AND - there have to be
# some selected forecasters
#
def checkSetButton(self):
someFcstrs=0
for i in range(len(self.fcstrNums)):
if self.fcbstates[i].get()>0:
someFcstrs=1
break
#
if someFcstrs==1:
selItems=self.cGrd.find_withtag("selected")
if selItems is not None:
if len(selItems)>0:
self.scb.configure(state=tkinter.NORMAL)
return
#
self.scb.configure(state=tkinter.DISABLED)
return
#------------------------------------------------------------------
#
# extract forecasters for this grid into the currently selected
# forecasters.
#
def extract(self,event):
curgrid=self.cGrd.find_withtag(tkinter.CURRENT)
grtags=self.cGrd.gettags(curgrid)
for tag in grtags:
if tag.find("rec")>-1:
self.gridRecord=int(tag[4:])
if tag.find("parm")>-1:
self.gridParm=tag[5:]
self.gridModel=self.ml.getCurrentSelection()
if self.__VU.checkFile(self.gridParm,self.gridModel,modify=0):
fcstrs=self.__VU.fncFcstr[self.gridRecord,:]
self.fcstrNums=self.__VU.getFcstrNums()
for i in range(len(self.fcstrNums)):
self.fcbstates[i].set(0)
for i in range(fcstrs.shape[0]):
fnum=fcstrs[i]
if fnum>0:
fnumstr="%2.2d"%fnum
if fnumstr in self.fcstrNums:
idx=self.fcstrNums.index(fnumstr)
self.fcbstates[idx].set(1)
self.checkSetButton()
#=====================================================================
#
# Custom comboList widget
#
# User sees currently selected entry from list of entries, and a
# pulldown button. When pulldown is activated the list is shown -
# with scrollbars (if needed) and the user can click on the entry
# desired. The callbackMethod is called when the user chooses an
# entry - and you can get the currentEntry with getCurrentEntry method
# and currentIndex with getCurrentIndex method.
#
class comboList(tkinter.Frame):
def __init__(self,defaultEntry,entryList,parent=None,callbackMethod=None,
width=0,height=5):
if defaultEntry not in entryList:
return
tkinter.Frame.__init__(self,parent)
self.__callbackMethod=callbackMethod
self.entries=[]
for entry in entryList:
self.entries.append(entry)
self.currentIndex=self.entries.index(defaultEntry)
self.currentSelection=defaultEntry
if width==0:
for entry in self.entries:
width=max(len(entry),width)
width+=1
#
# Make the popup chooser
#
self.opop=tkinter.Toplevel()
self.opop.withdraw()
of=tkinter.Frame(self.opop)
if len(self.entries)>height:
os=tkinter.Scrollbar(of,orient=tkinter.VERTICAL)
self.ol=tkinter.Listbox(of,width=width,height=height,
yscrollcommand=os.set,
selectmode=tkinter.SINGLE,
exportselection=0)
os.config(command=self.ol.yview)
os.pack(side=tkinter.RIGHT,fill=tkinter.Y)
else:
self.ol=tkinter.Listbox(of,width=width,height=height)
for entry in self.entries:
self.ol.insert(tkinter.END,entry)
self.ol.pack(side=tkinter.LEFT,fill=tkinter.BOTH,expand=1)
of.pack(side=tkinter.TOP)
self.ol.bind("<ButtonRelease-1>",self.removePopup)
self.opop.transient(parent)
self.opop.overrideredirect(1)
self.opop.update_idletasks()
popwidth=self.opop.winfo_reqwidth()
if (len(self.entries)<=height):
popwidth+=21
popheight=self.opop.winfo_reqheight()
hpl=popheight/height
#
# Make the display of current entry and pulldown button
#
self.cf=tkinter.Frame(parent,width=popwidth)
self.cl=tkinter.Listbox(self.cf,width=width,height=1,
selectmode=tkinter.SINGLE,
exportselection=0)
self.cl.insert(tkinter.END,defaultEntry)
self.cl.pack(side=tkinter.LEFT)
self.cl.update_idletasks()
cw=self.cl.winfo_reqwidth()
ch=self.cl.winfo_reqheight()
canw=popwidth-cw-6
canh=ch-6
bw=2 # border width
cc=tkinter.Canvas(self.cf,width=canw,height=canh,
relief=tkinter.RAISED,bd=bw)
tsize=min(canw,canh)
toffx=((canw-tsize)/2.0)+bw+bw
toffy=((canh-tsize)/2.0)+bw+bw
twid=tsize-bw-bw
x0=toffx
y0=toffy
x1=toffx+twid
y1=toffy
x2 = toffx + (twid // 2)
y2=toffy+twid
cp=cc.create_polygon(x0,y0,x1,y1,x2,y2,fill="black")
cc.pack(side=tkinter.LEFT)
#self.cf.pack(side=Tkinter.TOP)
self.cl.bind("<Button-1>",self.postPopup)
cc.bind("<Button-1>",self.postPopup)
return
def postPopup(self,event):
curgeom=self.cf.winfo_geometry()
(wh,rest)=curgeom.split("+",1)
(w,h)=wh.split("x",1)
iw=int(w)
ih=int(h)
x=self.cf.winfo_rootx()
y=self.cf.winfo_rooty()
newgeom="+%d+%d"%(x,y+ih)
self.opop.geometry(newgeom)
self.opop.deiconify()
self.ol.select_clear(0,tkinter.END)
self.ol.select_set(self.currentIndex)
self.ol.see(self.currentIndex)
popgeom=self.opop.geometry()
(wh,rest)=popgeom.split("+",1)
(iw,ih)=wh.split("x",1)
self.iw=int(iw)
self.ih=int(ih)
self.opop.grab_set_global() # once you get here - you MUST choose
#self.opop.grab_set()
self.opop.focus_set()
self.opop.bind("<FocusOut>",self.closePopup)
self.opop.bind("<Button-1>",self.popClick)
return "break"
#------------------------------------------------------------------
# popClick - Test if they are clicking in the list - if so - they
# might release inside the list - and that will get
# captured in removePopup. If not - then close the
# popup without choosing
#
def popClick(self,event):
x=event.x
y=event.y
if ((x>self.iw)or(x<1)):
return self.closePopup(event)
if ((y>self.ih)or(y<1)):
return self.closePopup(event)
return "break"
#------------------------------------------------------------------
# removePopup - called when they choose one in the list
#
def removePopup(self,event):
selectIndex=int(self.ol.nearest(event.y))
self.currentIndex=selectIndex
newEntry=self.entries[selectIndex]
self.currentSelection=newEntry
self.opop.grab_release()
self.cl.delete(0)
self.cl.insert(0,newEntry)
self.cf.focus_set()
self.opop.withdraw()
self.__callbackMethod()
return
#------------------------------------------------------------------
# closePopup - called when they dont pick from the list - but
# need to close the popup
#
def closePopup(self,event):
#self.cf.focus_set()
self.opop.grab_release()
self.opop.unbind("<Button-1>")
self.opop.withdraw()
return
#------------------------------------------------------------------
# getCurrentSelection - tells you the currently selected text
#
def getCurrentSelection(self):
return self.currentSelection
#------------------------------------------------------------------
# getCurrentIndex - tells you the index of the currently selected
# text
#
def getCurrentIndex(self):
return self.currentIndex
#------------------------------------------------------------------
# delIndex - delete the specified index from the entries
#
def delIndex(self,index):
if ((index<0)or(index>=len(self.entries))):
return
curSel=self.currentSelection
self.ol.delete(index)
del self.entries[index]
if curSel in self.entries:
self.currentIndex=self.entries.index[curSel]
else:
self.currentIndex=min(self.currentIndex,len(self.entries)-1)
self.currentSelection=self.entries[self.currentIndex]
self.cl.delete(0,tkinter.END)
self.cl.insert(tkinter.END,self.currentSelection)
return
#------------------------------------------------------------------
# setCurrentIndex - set the selected entry to the specified index
# in entries
#
def setCurrentIndex(self,index):
if ((index<0)or(index>=len(self.entries))):
return
self.currentIndex=index
self.currentSelection=self.entries[index]
self.cl.delete(0,tkinter.END)
self.cl.insert(tkinter.END,self.currentSelection)
return
#------------------------------------------------------------------
# setCurrentSelection - set the selected entry to the specified
# entry
#
def setCurrentSelection(self,selection):
if selection not in self.entries:
return
index=self.entries.index[selection]
self.currentIndex=index
self.currentSelection=self.entries[index]
self.cl.delete(0,tkinter.END)
self.cl.insert(tkinter.END,self.currentSelection)
return
#------------------------------------------------------------------
# delValue - delete the specified entry
#
def delValue(self,value):
if value in self.entries:
indexdel=self.entries.index(value)
self.delIndex(indexdel)
return
#------------------------------------------------------------------
# newEntries - replace all the entries with a new list of entries.
# If the currently selected entry is in the new
# list of entries - then select it in the new list
# otherwise select the first entry
#
def newEntries(self,newList,newDef):
if len(newList)>0:
self.ol.delete(0,tkinter.END)
self.entries=[]
for entry in newList:
self.entries.append(entry)
self.ol.insert(tkinter.END,entry)
if newDef in newList:
self.setCurrentIndex(newList.index(newDef))
else:
self.setCurrentIndex(0)
return
#=====================================================================
# Create a basic 'message window' indicating that something
# is happening. Must be careful to destroy this...because there
# is no way for the user to destroy this if something goes wrong.
#
def messageWindow(message,parent=None):
if parent is None:
return
pwid=parent.winfo_width()
phgt=parent.winfo_height()
px=parent.winfo_rootx()
py=parent.winfo_rooty()
msgWindow=tkinter.Toplevel(master=parent)
msgWindow.resizable(0,0)
msgWindow.transient()
msgWindow.overrideredirect(1)
msgLab=tkinter.Label(master=msgWindow,text=message,
relief=tkinter.RIDGE,height=5,width=29,borderwidth=4)
msgLab.pack(side=tkinter.TOP)
msgWindow.update_idletasks()
wid=msgWindow.winfo_width()
hgt=msgWindow.winfo_height()
nx=int(px+(pwid/2.0)-(wid/2.0))
ny=int(py+(phgt/2.0)-(hgt/2.0))
msgWindow.geometry("%dx%d+%d+%d"%(wid,hgt,nx,ny))
msgWindow.update_idletasks()
return msgWindow
#=====================================================================
#
# stuff to support a callback with a pre-known variable
#
def GenericCallback(callback, *firstArgs, **firstKWArgs):
if firstKWArgs:
return GC(callback, *firstArgs, **firstKWArgs)
else:
return GCNoKWArgs(callback, *firstArgs)
#=====================================================================
#
# Classes for callbacks
#
class GC:
def __init__(self,callback,*firstArgs, **firstKWArgs):
self.__callback=callback
self.__firstArgs=firstArgs
self.__firstKWArgs=firstKWArgs
def __call__(self, *lastArgs, **kwArgs):
if kwArgs:
netKWArgs=self.__firstKWArgs.copy()
netKWArgs.update(self.__kwArgs)
else:
netKWArgs=self.__firstKWArgs
return self.__callback (*(self.__firstArgs+lastArgs),**netKWArgs)
class GCNoKWArgs:
def __init__(self, callback, *firstArgs):
self.__callback=callback
self.__firstArgs=firstArgs
def __call__(self, *args, **kwArgs):
return self.__callback (*(self.__firstArgs+args),**kwArgs)