#!/usr/bin/python2 #guinter.py - a DEMpy/PullSDTS module - 5 Jan 02 #Update at http://www.3dartist.com/WP/pullsdts/ #Created by Bill Allen """ This module is common to both DEMpy and PullSDTS. It contains functions and classes for working with the Python Tkinter graphical user interface. ------ Index, functions: fontTuple rgb2hex saveAsFile Index, classes: buttClass - special wrapper for buttons to encompass all their aspects colorClass - all the named colors used in both DEMpy & PullSDTS demPreviewClass - to display PPM images & more helpLabelClass - a way to place helpful tips into a dialog as a [?] box initsClass - mostly but not all GUI, needs to have a derived class progressFrameClass - temporary frame for showing action progress rawSaveDialogClass - interface for saving an 8- or 16-bit RAW file sortBarClass - for sorting TAR content file list tipClass - for tip window to appear when cursor enters a button, etc. """ #--- user-set constant --- tipDelay = 700 #tip window delay for Mac only taskBarHt = 56 #taskbar height #--- modules & constants --- import ConfigParser, os, string, sys, time, tkFileDialog, tkMessageBox import Tkinter as tk from string import ljust, rjust thisDir = os.path.split(sys.argv[0])[0] if not thisDir in sys.path: sys.path.append(thisDir) import pulldem, sdts from pulldem import fixPath #--- functions --- def fontTuple(tup,family,size,style): #need because can't directly assign f = tup[0] # values within a Python tuple sz = tup[1] if family != '': f = family if size > 0: sz = size if style == '' and len(tup) == 2: #no new or old font style return f,sz if style != '': st = style else: st = tup[2] return f,sz,st #end def fontTuple def rgb2hex(r,g,b): #turn decimal RGB values into hexadecimal return '#%02X%02X%02X'%(r,g,b) def saveAsFile(master,txt,dir,fn,fileType): if dir == '': n = tkFileDialog.asksaveasfilename(title=txt,initialfile=fn, filetypes=fileType,parent=master.frame) else: #stating the parent forces it to wait for this dialog to complete n = tkFileDialog.asksaveasfilename(title=txt,initialfile=fn, filetypes=fileType,parent=master.frame,initialdir=dir) if n: n = os.path.split(n) if fixPath(n[0]) == master.ini.tmpDir: msg = master.ini.tmpDir+', the folder for '+master.title \ +' temporary files, is not a good place to put your files,' \ +' as some may be deleted without warning.' if os.name == 'mac': msg = msg + '\n\n' + 'Please choose another folder.' else: L = pulldem.text2list(msg,30) msg = '' for i in range(len(L)): msg = msg + L[i] + '\n' msg = msg + '\nPlease choose another folder.' tkMessageBox.showwarning('Not a good idea...',msg, parent=master.frame) return None,'' master.ini.outDir = fixPath(n[0]) #with these 2 lines, every calling master.frame.update_idletasks() # master needs an ini inits object return n # & a Tk frame type of object else: return None,'' #end def saveAsFile #--- classes --- class buttClass: def __init__(self,caller,callerParent,type,lab,stat): """ Other params here could include event bindings (including hot keys with underscore location), Python name, focus, and tab order. Also font specs, and button size, propagation, and expand/fill. """ color = colorClass() self.frameParent = caller #immediate parent widget self.callerParent = callerParent #the toplevel frame self.type = type #can be: toggle, text, togoicon, image, radio, check self.label = lab #can be text or image, or list of either self.bindings = [] #holds keyboard shortcut bindings self.status = stat #can be: normal, disabled, None self.tip = '' #holds short help (tip) if type == 'toggle': #2 or more text labels self.tog = 0 self.butt = tk.Button(self.frameParent,text=lab[0]) elif type == 'text' or not type: self.butt = tk.Button(self.frameParent,text=lab) elif type == 'togicon': #2 or more icons self.tog = 0 self.butt = tk.Button(self.frameParent,image=lab[0]) elif type == 'image': self.butt = tk.Button(self.frameParent,image=lab) elif type == 'check': self.butt = tk.Checkbutton(self.frameParent,text=lab,var=None) if os.name == 'mac': self.butt.config(highlightbackground=color.winGray) if type == 'check': self.butt.config(bg=color.winGray) self.butt.config(state=stat) def cfg(self,tip='',uline=-1,altkey='',stat=''): if tip: self.butt.bind('',self.showTip) self.tip = tip if stat: #only needed for "action" buttons self.state(stat) # & getting here can cue other steps self.label = self.butt.cget('text') if os.name != 'mac': #Alt-keys don't work on Mac if altkey != '': b1 = '' b2 = '' self.bindings.append(b1) self.bindings.append(b2) self.callerParent.frame.bind(b1,self.flex) self.callerParent.frame.bind(b2,self.flex) if uline > -1 and self.type in ['check','text'] \ and uline < len(self.label): #what about toggled text? self.butt.config(underline=uline) def bright(self): self.butt.config(fg=rgb2hex(180,0,0)) def state(self,stat): self.butt.config(state=stat) self.status = stat def flex(self,event): #this is how Alt-keys are invoked if self.type == 'check': # & how they are made to depress self.butt.invoke() else: self.butt.tkButtonDown() self.butt.tkButtonUp() self.butt.tkButtonInvoke() def showTip(self,event): if self.callerParent.showTips.get() and self.tip: b = self.butt tip = tipClass(b,self.tip, b.winfo_rootx()+b.winfo_width(), b.winfo_rooty()+b.winfo_height()) #end class buttClass class colorClass: def __init__(self): self.black = rgb2hex(0,0,0) self.brown = rgb2hex(133,79,12) self.cream = rgb2hex(255,255,223) self.offwhite = rgb2hex(253,252,240) #self.red = rgb2hex(255,0,0) self.tan = rgb2hex(253,219,97) self.white = rgb2hex(255,255,255) if os.name == 'mac': self.winGray = rgb2hex(204,204,204) #end class colorClass class demPreviewClass: """ Puts up a new transient frame with an 8-bit DEM grayscale view and related info and controls. """ def __init__(self,parent,sdts,title=''): color = colorClass() parent.frame.update_idletasks() #update the calling window self.parent = parent self.sdts = sdts frame = self.frame = tk.Toplevel(parent.frame,bd=1,bg=color.brown) if title: frame.title(title) else: frame.title('DEM 8-bit grayscale view') self.parUndock = None #store parent XY to undock it if parent.dock.get(): self.dock() else: frame.geometry('440x520+10+10') frame.resizable(0,0) frame.protocol('WM_DELETE_WINDOW',self.quit) #close button frame.transient(self.parent.frame) #keep off task bar self.gamma = parent.gamma #get current gamma self.image = None self.scale = tk.DoubleVar() self.scale.set(0.0) self.ini = parent.ini #needed for saveAsFile to work self.showTips = parent.showTips #needed for buttClass to work grp = self.imgGrp = tk.Frame(frame,bd=0,bg=color.brown) grp.pack(side='top',fill='both',expand=1,padx=0,pady=0) #--- slider group grp = self.sliderGrp = tk.Frame(self.imgGrp,bd=0,bg=color.brown) grp.pack(anchor='ne',side='right',padx=0,pady=0) slide = self.slider = tk.Scale(grp,length=300,bd=0,digits=1, variable=self.scale,bg=color.brown, fg=color.white,orient='horizontal', sliderlength=2, highlightbackground=color.brown) slide.pack(anchor='sw',side='bottom',padx=10,pady=0) #finish up frame.update_idletasks() #end def __init__ def dock(self): self.frame.withdraw() #hide this briefly self.frame.update_idletasks() pframe = self.parent.frame xPar = pframe.winfo_x() #parent left X y = pframe.winfo_y() #parent top Y if os.name == 'mac': self.span = 452 #distance needed to move the main window aside else: self.span = 446 if xPar >= self.span: x = xPar - self.span else: self.parUndock = (xPar,y) #to move back later x = 0 pframe.geometry('+'+str(self.span)+'+'+str(y)) pframe.update_idletasks() #move the parent self.frame.geometry('440x520+'+str(x)+'+'+str(y)) self.frame.deiconify() #redisplay in place self.frame.update_idletasks() def gifSave(self): s = self.frame.title() #make up a GIF name by removing i = s.find(',') # comma & what follows if i == -1: s = s[0:i] L = s.split() #remove white spaces s = '' for i in range(len(L)): s = s + L[i] pn = saveAsFile(self,'Save to GIF',self.ini.outDir, s+'.gif',[('GIF file','*.gif'),('All Files','*.*')]) if pn[0]: self.image.write(fixPath(pn[0],pn[1])) def progress(self,v): self.scale.set(v) self.frame.update_idletasks() def progressLabel(self,txt): self.slider.config(label=txt) def quit(self): if self.parUndock: self.parent.frame.geometry('+'+str(self.parUndock[0])+'+' +str(self.parUndock[1])) self.parent.frame.update_idletasks() self.parent.gamma = self.gamma #preserve the gamma self.parent.frame.focus_set() #grab_set is NOT returned to parent self.frame.destroy() def rawSave(self): raw = rawSaveDialogClass(self) def setGamma(self,event): self.gamma = self.scale.get() self.image.config(gamma=self.gamma) self.frame.update_idletasks() def setImage(self,fn): color = colorClass() img = self.image = tk.PhotoImage(file=fn,gamma=self.gamma) if img.height() > 500: img = self.image = img.subsample(3) self.frame.title(self.frame.title()+' (33% size)') lbl = tk.Label(self.imgGrp,bd=3,bg=color.white,relief='flat', width=img.width(),height=img.height(),image=img) lbl.pack(anchor='nw',side='left',padx=1,pady=1) #--- create button group grp = self.buttGrp = tk.Frame(self.frame,bd=1,relief='flat',height=28, bg=color.brown) grp.pack_propagate(0) grp.pack(side='top',fill='x') if os.name == 'mac': filler = tk.Label(grp,width=1,bg=color.brown) #window handle room filler.pack(side='right',padx=3) #--- Close button b = self.bQuit = buttClass(grp,self,'text','Close','normal') b.butt.config(command=self.quit) b.cfg(tip='Close the DEM window',uline=0,altkey='c') b.butt.pack(side='right',padx=1,pady=1,fill='y') #--- GIF save button b = self.bSaveGif = buttClass(grp,self,'text','Save GIF','normal') b.butt.config(command=self.gifSave) b.cfg(tip='Save DEM preview to 254-gray GIF\n' +'(not affected by Viewing Gamma)',uline=5,altkey='g') b.butt.pack(side='right',padx=1,pady=1,fill='y') #--- RAW save button b = self.bSaveRaw = buttClass(grp,self,'text','Save RAW','normal') b.butt.config(command=self.rawSave) b.cfg(tip='Export DEM as 8- or 16-bit RAW file (2D raster\n' +'format, not affected by Viewing Gamma)',uline=7,altkey='w') b.butt.pack(side='right',padx=1,pady=1,fill='y') #--- deal with slider group self.slider.pack_forget() #so we can insert elements above it self.scale.set(self.gamma) self.slider.config(label='',length=400,width=6,from_=0.1,to=4.2, resolution=0.01,showvalue=1, sliderlength=12,digits=3,orient='vertical', command=self.setGamma) self.slider.pack_propagate(0) self.slider.pack(anchor='nw',side='bottom',padx=0,pady=0) sliderLbl = tk.Label(self.sliderGrp,text='Viewing\nGamma', fg=color.white,bg=color.brown, font=self.ini.fontSmall) sliderLbl.pack(anchor='nw',side='bottom',padx=0,pady=0) #--- deal with Mac GUI issues if os.name == 'mac': b.butt.config(highlightbackground=color.brown) self.bQuit.butt.config(highlightbackground=color.brown) self.bSaveGif.butt.config(highlightbackground=color.brown) self.bSaveRaw.butt.config(highlightbackground=color.brown) #end class demPreviewClass class helpLabelClass: def __init__(self,parent,callerParent,tip,lado='right',anch='w',bgr=None): lbl = self.lHelp = tk.Label(parent,bd=1,relief='solid',text='?') if bgr: lbl.config(bg=bgr) lbl.pack(expand=0,side=lado,anchor=anch,padx=2) lbl.bind('',self.showTip) self.callerParent = callerParent self.tip = tip def showTip(self,event): if self.callerParent.showTips.get() and self.tip: b = self.lHelp tip = tipClass(b,self.tip, b.winfo_rootx()+b.winfo_width(), b.winfo_rooty()+b.winfo_height()) #end class helpLabelClass class initsClass: def __init__(self): #if the script doesn't know where files were last opened/saved then, # instead of assigning those folders to where the script is located, # blank the folder names in case Tkinter may remember self.startup = os.path.split(sys.argv[0])[0] self.inDir = '' #where to start looking for file to open self.outDir = '' #where to put extracted files self.tmpDir = '' #temp folder self.width = '' #top frame width, set in derived class self.height = '' #top frame height, set in derived class def topGeo(self,master): #main window configuration sw = int(self.width) #all of this is to make sure that the sh = int(self.height) # top level frame appears fully on screen ww = master.frame.winfo_screenwidth() wh = master.frame.winfo_screenheight() if int(self.xLoc) + sw > ww: self.xLoc = str(ww-(sw+10)) #10 just for breathing room if int(self.yLoc) + sh > wh: self.yLoc = str(wh-(sh+taskBarHt)) return self.width + 'x' + self.height + '+' \ + self.xLoc + '+' + self.yLoc def reload(self): if os.path.exists(self.iniFile): try: f = open(self.iniFile,'r') except IOError: return cf = ConfigParser.ConfigParser() cf.readfp(f) f.close() if cf.has_option('main','xloc'): self.xLoc = str(cf.get('main','xloc')) if cf.has_option('main','yloc'): self.yLoc = str(cf.get('main','yloc')) if cf.has_option('main','tips'): try: self.tips = cf.getboolean('main','tips') except ValueError: pass if cf.has_option('main','dock'): try: self.dock = cf.getboolean('main','dock') except ValueError: pass if cf.has_option('main','endian'): v = cf.get('main','endian') if v in ['big','little']: self.endian = v if cf.has_option('main','gamma'): self.gamma = cf.getfloat('main','gamma') if cf.has_option('folders','temp'): self.tmpDir = cf.get('folders','temp') if self.tmpDir: #don't accept a bad path if not os.path.exists(self.tmpDir): self.tmpDir = '' if cf.has_option('folders','inlast'): self.inDir = cf.get('folders','inlast') if self.inDir: if not os.path.exists(self.inDir): self.inDir = '' if cf.has_option('folders','outlast'): self.outDir = cf.get('folders','outlast') if self.outDir: if not os.path.exists(self.outDir): self.outDir = '' if cf.has_option('fonts','boldsize'): self.fontBold = fontTuple(self.fontBold,'', cf.getint('fonts','boldsize'),'') if cf.has_option('fonts','listsize'): self.fontList = fontTuple(self.fontList,'', cf.getint('fonts','listsize'),'') if cf.has_option('fonts','textsize'): self.fontText = fontTuple(self.fontText,'', cf.getint('fonts','textsize'),'') if cf.has_option('RAW','unit'): v = cf.get('RAW','unit') if v in ['dec','ft','m']: #if no valid unit then self.rawUnit = v # don't load min/max range if cf.has_option('RAW','rangemin'): self.rawMin = cf.getint('RAW','rangemin') if cf.has_option('RAW','rangemax'): self.rawMax = cf.getint('RAW','rangemax') if cf.has_option('RAW','distribution'): v = cf.get('RAW','distribution') if v in ['exact','range','self']: self.rawDist = v if cf.has_option('RAW','bitcount'): v = cf.getint('RAW','bitcount') if v in [8,16]: self.rawBits = v if cf.has_option('RAW','fillvalue'): try: self.rawFill = cf.getboolean('RAW','fillvalue') except ValueError: pass def save(self,master): #write settings to .ini file cf = ConfigParser.ConfigParser() cf.add_section('main') #main window & general settings cf.set('main','xloc',master.frame.winfo_x()) cf.set('main','yloc',master.frame.winfo_y()) cf.set('main','tips',master.showTips.get()) cf.set('main','dock',master.dock.get()) cf.set('main','endian',self.endian) cf.set('main','gamma',master.gamma) cf.add_section('folders') #folders to remember cf.set('folders','temp',self.tmpDir) cf.set('folders','inlast',self.inDir) cf.set('folders','outlast',self.outDir) cf.add_section('fonts') #font sizes cf.set('fonts','boldsize',str(self.fontBold[1])) cf.set('fonts','listsize',str(self.fontList[1])) cf.set('fonts','textsize',str(self.fontText[1])) cf.add_section('RAW') #RAW files cf.set('RAW','unit',self.rawUnit) cf.set('RAW','distribution',self.rawDist) cf.set('RAW','rangemin',self.rawMin) cf.set('RAW','rangemax',self.rawMax) cf.set('RAW','bitcount',self.rawBits) cf.set('RAW','fillvalue',self.rawFill) f = open(self.iniFile,'w') cf.write(f) f.flush() f.close() def setDefault(self): self.xLoc = '20' #upper-left corner screen position self.yLoc = '20' # note these are numeric string values self.tips = 1 #show tips for buttons self.dock = 1 #dock demPreview to mainWindow self.endian = sys.byteorder #target platform endianess: little, big self.gamma = 1.0 #viewing gamma self.rawBits = 16 #bit size for RAW files, 8 or 16 self.rawDist = 'self' #how to redistribute DEM values self.rawFill = 0 #black (0) or white (1) self.rawMin = -282 #min. elevation range, in ConUS = -282 self.rawMax = 14494 #max. elevation range, in ConUS = 14494 self.rawUnit = 'ft' #units, 'ft', 'dec', or 'm' if os.name == 'mac': self.fontBold = ('Helvetica',12,'bold') self.fontList = ('Courier',13) self.fontSmall = ('Helvetica',9) #fixed size, for small buttons self.fontText = ('Times',10) else: self.fontBold = ('Helvetica',8,'bold') self.fontList = ('Courier',8) self.fontSmall = ('Helvetica',7) self.fontText = ('Times',9) #end class initsClass class progressFrameClass: def __init__(self,parFrame,voh='horizontal'): self.parentFrame = parFrame frame = self.frame = tk.Frame(parFrame,bd=0) scale = self.scale = tk.DoubleVar() scale.set(0.0) slide = self.slider = tk.Scale(frame,bd=0,digits=1,variable=scale, orient=voh,sliderlength=2, length=parFrame.winfo_width()-20) slide.pack() def title(self,t): self.slider.config(label=t) def progress(self,v): self.scale.set(v) self.frame.update_idletasks() def withdraw(self): self.frame.withdraw() #end class progressFrameClass class rawSaveDialogClass: def __init__(self,parent): x = parent.frame.winfo_rootx() + 27 y = parent.frame.winfo_rooty() + 274 if os.name == 'mac': color = colorClass() wg = color.winGray del color wxh = '406x214' x = x - 22 y = y + 22 else: wg = None wxh = '400x214' xy = '+' + str(x) + '+' + str(y) self.parent = parent parent.frame.update_idletasks() frame = self.frame = tk.Toplevel(parent.frame,bd=1) frame.title('RAW File Save Settings') frame.geometry(wxh+xy) frame.resizable(0,0) frame.protocol('WM_DELETE_WINDOW',self.quit) #close button frame.transient(parent.frame) #keep off task bar nullGray = self.nullGray = rgb2hex(230,230,230) self.rangeDefaultDict = {'ftMin':-282,'ftMax':14494, 'mMin':-86,'mMax':4418, 'decMin':-860,'decMax':44178} self.ini = parent.ini #need for getting/putting RAW inits self.showTips = parent.showTips #needed for buttClass to work frame.bind('',self.saveSelf) frame.bind('',self.quit) #--- Mac GUI issues --- if wg: frame.config(bd=2,bg=wg,relief='raised',highlightbackground=wg) grp = tk.Frame(frame,bd=0,height=21) grp.pack_propagate(0) #need frame to constrain label size grp.pack(side='top',fill='x',expand=1) lbl = tk.Label(grp,text='RAW File Save Settings', font=self.ini.fontBold,bg=wg) lbl.pack(fill='x',expand=1) #--- dialog variables --- bc = self.bitCnt = tk.IntVar() #8 or 16 bits bc.set(self.ini.rawBits) bo = self.byteOrder = tk.StringVar() if self.ini.endian == 'little': bo.set('<') else: bo.set('>') doHead = self.doHeader = tk.IntVar() doHead.set(0) #don't output a header fv = self.fillValue = tk.IntVar() #is fill value black or white? fv.set(self.ini.rawFill) header = self.header = tk.StringVar() header.set('') #what the header would be rng = self.range = tk.StringVar() if self.ini.rawDist == 'exact': rng.set('x') elif self.ini.rawDist == 'range': rng.set('r') else: #'self' rng.set('=') rMax = self.rangeMax = tk.IntVar() #elevation range maximum rMax.set(self.ini.rawMax) rMaxStr = self.rMaxStr = tk.StringVar() rMaxStr.set(str(self.ini.rawMax)) rMin = self.rangeMin = tk.IntVar() #elevation range minimum rMin.set(self.ini.rawMin) rMinStr = self.rMinStr = tk.StringVar() rMinStr.set(str(self.ini.rawMin)) unit = self.unit = tk.StringVar() unit.set(self.ini.rawUnit[0]) #units f/d/m, default to feet self.unitHold = unit.get() #hold for recalcs #--- units group --- grp = tk.Frame(frame,bd=0,height=17,bg=wg) grp.pack_propagate(0) grp.pack(side='top',fill='x',pady=4) lbl = tk.Label(grp,text='Units:',font=self.ini.fontBold,bg=wg) lbl.pack(side='left') for t,v in [('Feet','f'),('Decimeters','d'),('Meters, Whole','m')]: b = tk.Radiobutton(grp,variable=unit,text=t,value=v,bg=wg, command=self.updateUnit) b.pack(side='left',padx=3,pady=0) if self.showTips.get(): hlp = helpLabelClass(grp,self,' applies to setting range\n' +'units and doing exact-unit output.\n' +'Important for matching output\n' +'when tiling multiple quads.',bgr=wg) #--- divider --- grp = tk.Frame(frame,bd=1,relief='groove',height=2,bg=wg) grp.pack(side='top',fill='x',pady=0) #--- endian group --- grp = tk.Frame(frame,bd=0,height=17,bg=wg) grp.pack(side='top',fill='x',pady=0) lbl = tk.Label(grp,text='Byte Order:',font=self.ini.fontBold,bg=wg) lbl.pack(side='left') b = tk.Radiobutton(grp,variable=bo,text='"Mac" (Motorola/Sun)', bg=wg,value='>',command=self.update) b.pack(side='left',padx=3,pady=0) b = tk.Radiobutton(grp,variable=bo,text='"PC" (Intel)',value='<', bg=wg,command=self.update) b.pack(side='left',padx=3,pady=0) #--- divider --- grp = tk.Frame(frame,bd=1,relief='groove',height=2,bg=wg) grp.pack(side='top',fill='x',pady=0) #--- header group grp = tk.Frame(frame,bd=0,height=17,bg=wg) grp.pack(side='top',fill='x',pady=0) lbl = tk.Label(grp,text='Header:',font=self.ini.fontBold,bg=wg) lbl.pack(side='left') for t,v in [('No Header',0),('Include 128-Byte Header',1)]: b = tk.Radiobutton(grp,variable=doHead,text=t,value=v,bg=wg, command=self.updateHeader) b.pack(side='left',padx=3,pady=0) if self.showTips.get(): hlp = helpLabelClass(grp,self,'
Including a header puts\n' +'key info and an optional short\n' +'comment into 128 bytes added to\n' +'the beginning of the RAW file.', bgr=wg) #--- header comment group --- grp = self.commentGrp = tk.Frame(frame,bd=0,height=17,bg=wg) grp.pack(side='top',fill='x',pady=0) ew = self.ewComment = tk.Entry(grp,textvariable=header,width=39) ew.pack(side='right') lbl = tk.Label(grp,text='Header Comment:',bg=wg) lbl.pack(side='right') #--- divider --- grp = tk.Frame(frame,bd=1,relief='groove',height=2,bg=wg) grp.pack(side='top',fill='x',pady=0) #--- gray redistribution group --- grp = tk.Frame(frame,bd=0,height=17,bg=wg) grp.pack(side='top',fill='x',pady=2) grp.pack_propagate(0) lbl = tk.Label(grp,text='Grays:',font=self.ini.fontBold,bg=wg) lbl.pack(side='left') for t,v in [('Exact','x'),('Equalize to Self','='), ('Equalize to Range','r')]: b = tk.Radiobutton(grp,variable=rng,text=t,value=v,bg=wg, command=self.updateGrays) b.pack(side='left',padx=3,pady=0) #--- gray range group --- grp = self.grayRangeGrp = tk.Frame(frame,bd=0,height=17,bg=wg) grp.pack(side='top',fill='x',pady=0) if self.showTips.get(): hlp = helpLabelClass(grp,self, ' lets you save the DEM data as\n' +'original exact values or redistributed\n' +'(equalized) to a maximum range according\n' +'to the quad\'s own extents or a user-set\n' +'range of extents. (Only Exact and Range\n' +'results can be tiled in an image editor.)', bgr=wg) #--- ConUS default range button --- box = tk.Frame(grp,bd=0,width=46,height=17,bg=wg) box.pack_propagate(0) #force smaller button size box.pack(side='right',padx=4) b = self.bConus = buttClass(box,self,'text','ConUS','normal') b.butt.config(command=self.rangeDefault,font=self.ini.fontSmall) b.cfg(tip='Reset to continental\nU.S. elevation range') b.butt.pack() #--- range entry widgets lbl = self.lblRange = tk.Label(grp,bg=wg) t = unit.get() if t == 'f': t = 'ft' elif t == 'd': t = 'dec' lbl.config(text=t+'. ') lbl.pack(side='right') ew = self.ewRangeMax = tk.Entry(grp,textvariable=rMaxStr,width=10) ew.pack(side='right') lbl = tk.Label(grp,text='to',bg=wg) lbl.pack(side='right') ew = self.ewRangeMin = tk.Entry(grp,textvariable=rMinStr,width=10) ew.pack(side='right') lbl = tk.Label(grp,text='Range:',justify='right',bg=wg) lbl.pack(side='right') #--- divider --- grp = tk.Frame(frame,bd=1,relief='groove',height=2,bg=wg) grp.pack(side='top',fill='x',pady=0) #--- other group --- grp = tk.Frame(frame,bd=0,height=17,bg=wg) grp.pack_propagate(0) grp.pack(side='top',fill='x',pady=3) lbl = tk.Label(grp,text='Bit Depth:',font=self.ini.fontBold,bg=wg) lbl.pack(side='left') for t,v in [('16',16),('8',8)]: b = tk.Radiobutton(grp,variable=bc,text=t,value=v,bg=wg, command=self.update) b.pack(side='left',padx=3,pady=0) lbl = tk.Label(grp,text=' Fill:',font=self.ini.fontBold,bg=wg) lbl.pack(side='left') for t,v in [('Black',0),('White',1)]: b = tk.Radiobutton(grp,variable=fv,text=t,value=v,bg=wg, command=self.update) b.pack(side='left',padx=3,pady=0) if self.showTips.get(): hlp = helpLabelClass(grp,self,' 16-bit data is usually\n' +'preferred if the target application can\n' +'handle it.\n' +'-'*24+'\n' +' Black for fill areas (edge slivers)\n' +'is best for heightfields. White may be\n' +'useful for 2D graphics work. Void will\n' +'be the opposite of fill.',bgr=wg) #--- divider --- grp = tk.Frame(frame,bd=1,relief='groove',height=2,bg=wg) grp.pack(side='top',fill='x',pady=0) #--- button group --- grp = self.buttGrp = tk.Frame(self.frame,bd=0,relief='flat',height=28, bg=wg) grp.pack_propagate(0) grp.pack(anchor='se',side='bottom',fill='x',pady=2) """ if os.name == 'mac': #make room for Mac window handle filler = tk.Label(grp,width=1,bg=wg) filler.pack(side='right',padx=3) """ #--- quit button --- b = self.bQuit = buttClass(grp,self,'text','Cancel','normal') b.butt.config(command=self.quit,highlightbackground=wg) b.cfg(tip='Don\'t save RAW file',uline=0,altkey='c') b.butt.pack(side='right',padx=1,pady=1,fill='y') #--- save button --- b = self.bSave = buttClass(grp,self,'text','SAVE','normal') b.butt.config(command=self.saveSelf,highlightbackground=wg) b.cfg(tip='Save RAW\n2D raster file',uline=0,altkey='s') b.butt.pack(side='right',padx=1,pady=1,fill='y') #--- finish up --- if not doHead.get(): self.ewComment.config(state='disabled',bg=nullGray) if self.range.get() != 'r': self.ewRangeMin.config(state='disabled',bg=nullGray) self.ewRangeMax.config(state='disabled',bg=nullGray) self.bConus.butt.config(state='disabled') frame.focus_set() frame.grab_set() parent.frame.wait_window(self.frame) #disable parent frame #end def __init__ def quit(self,event=None): self.parent.frame.focus_set() self.parent.frame.grab_set() self.frame.destroy() def rangeDefault(self): if self.unit.get() == 'f': min = self.rangeDefaultDict['ftMin'] max = self.rangeDefaultDict['ftMax'] elif self.unit.get() == 'd': min = self.rangeDefaultDict['decMin'] max = self.rangeDefaultDict['decMax'] else: min = self.rangeDefaultDict['mMin'] max = self.rangeDefaultDict['mMax'] self.updateUnit(min,max) def saveSelf(self,event=None): k = self.parent.sdts.keyInfo s = k.name #make up a file name by removing i = s.find(',') # comma & what follows if i > -1: s = s[0:i] L = s.split() #remove white spaces s = '' for i in range(len(L)): s = s + L[i] if self.doHeader.get(): #create complete header h = self.byteOrder.get() \ + str(k.dimX) + 'x' + str(k.dimY) + '\\' \ + k.name + '\\' + self.header.get() i = len(h) if i < 127: h = ljust(h,127) elif i > 127: h = h[0:127] h = h + '\0' #add terminator as 128th character else: #no header, put key info into file name h = '' s = s + '_' + str(k.dimX) + 'x' + str(k.dimY) + '_' if self.byteOrder.get() == '>': s = s + 'Mac' else: s = s + 'PC' s = s + '.raw' pn = saveAsFile(self,'Save as RAW',self.ini.outDir, s,[('RAW file','*.raw'),('All Files','*.*')]) if pn[0]: xfer = self.parent.sdts if self.range.get() in ['r','x']: #want only for exact & range unitFactor = pulldem.convertUnits(1.0,k.unitZ,self.unit.get()) else: unitFactor = 1.0 """ print '\n',self.range.get(),k.unitZ,self.unit.get(),unitFactor #**DEBUG print '\t range:',self.ewRangeMin.get(),'-',self.ewRangeMax.get() print '\tkeyInfo:',k.elevDict['zMin'],'-',k.elevDict['zMax'] """ if self.range.get() == '=': #equalize r1 = k.elevDict['zMin'] r2 = k.elevDict['zMax'] elif self.range.get() == 'r': #set to range #pick up current range settings from string entry field #remove any commas & fail gracefully if entry is non-numeric try: s = string.replace(self.ewRangeMin.get(),',','') r1 = int(s) self.rangeMin.set(r1) except ValueError: #invalid numeric input r1 = self.rangeMin.get() # so revert self.ewRangeMin.config(textvariable=str(r1)) try: s = string.replace(self.ewRangeMax.get(),',','') r2 = int(s) if r2 < r1: #safety r2 = self.rangeMax.get() if r2 < r1: r2 = r1 + 1 self.rangeMax.set(r2) except ValueError: r2 = self.rangeMax.get() self.ewRangeMax.config(textvariable=str(r2)) else: r1 = r2 = None #save exact values self.buttGrp.pack_forget() progress = progressFrameClass(self.frame) progress.slider.config(bg='green') progress.frame.pack(anchor='ne',side='bottom',fill='x') self.frame.update_idletasks() if self.bitCnt.get() == 16: sdts.dem2raw(fixPath(pn[0],pn[1]), xfer.ddf[xfer.ddfDict['CEL0']][1], self.byteOrder.get(),h, k.dimY,k.dimX,unitFactor, k.elevDict['fill'],k.elevDict['void'], self.range.get(),r1,r2, self.fillValue.get(),progress) else: sdts.dem2raw8(fixPath(pn[0],pn[1]), xfer.ddf[xfer.ddfDict['CEL0']][1], self.byteOrder.get(),h, k.dimY,k.dimX,unitFactor, k.elevDict['zMin'],k.elevDict['zMax'], k.elevDict['fill'],k.elevDict['void'], self.range.get(),r1,r2, self.fillValue.get(),progress) lookup = {'=':'self','<':'little','>':'big', 'd':'dec','f':'ft','m':'m','r':'range', 'x':'exact'} #used instead of if/then structures if self.range.get() == 'r': self.ini.rawDist = 'range' self.ini.rawMax = self.rangeMax.get() self.ini.rawMin = self.rangeMin.get() else: self.ini.rawDist = lookup[self.range.get()] self.ini.rawBits = self.bitCnt.get() self.ini.rawFill = self.fillValue.get() self.ini.rawUnit = lookup[self.unit.get()] self.ini.endian = lookup[self.byteOrder.get()] self.quit() #quit upon successful completion def update(self,event=None): self.frame.update_idletasks() def updateGrays(self): if self.range.get() == 'r': self.ewRangeMin.config(state='normal',bg='white') self.ewRangeMax.config(state='normal',bg='white') self.bConus.butt.config(state='normal') else: self.ewRangeMin.config(state='disabled',bg=self.nullGray) self.ewRangeMax.config(state='disabled',bg=self.nullGray) self.bConus.butt.config(state='disabled') self.update() def updateHeader(self): if self.doHeader.get(): self.ewComment.config(state='normal',bg='white') else: self.ewComment.config(state='disabled',bg=self.nullGray) self.update() def updateUnit(self,min=None,max=None): """ Reset the range numbers label. Recalc range numbers for new unit if not resetting to default. """ self.lblRange.config(text= {'d':'dec','f':'ft','m':'m'}[self.unit.get()]+'. ') #reset label if not min: #true if not set by rangeDefault min = pulldem.convertUnits(self.rangeMin.get(),self.unitHold, self.unit.get()) max = pulldem.convertUnits(self.rangeMax.get(),self.unitHold, self.unit.get()) self.unitHold = self.unit.get() #reset self.rangeMin.set(min) self.rMinStr.set(str(min)) self.rangeMax.set(max) self.rMaxStr.set(str(max)) self.update() #end class rawSaveDialogClass class sortBarClass: """ This class acts as 1) a frame with buttons for sorting the TAR file contents list as well as 2) holding the master copy of the list and the functions for sorting and producing that list. """ def __init__(self,master,parent,w,h,fnt,visible=1): #master is the calling frame, parent is the calling class color = colorClass() self.list = [] #where the current master list resides self.listFlag = [0,0,0,0,1] #reverse flags: name/size/type/time/order self.parent = parent # which are reset by self.unhide self.visible = 1 #visibility of sort bar buttons self.frame = tk.Frame(master,width=w,height=h,bd=1) #bd needed for Mac if os.name == 'mac': self.frame.config(bg=color.winGray, highlightbackground=color.winGray) self.but1 = tk.Button(self.frame,text='Name',font=fnt, command=self.sortName) self.but1.pack(side='left',fill='x',expand=1) self.but2 = tk.Button(self.frame,text='Type',font=fnt, command=self.sortType) self.but2.pack(side='left') self.but3 = tk.Button(self.frame,text='Size',font=fnt, command=self.sortSize) self.but3.pack(side='left') self.but4 = tk.Button(self.frame,text='Date/Time',font=fnt, command=self.sortTime) self.but4.pack(side='left',fill='x',expand=1) self.but5 = tk.Button(self.frame,text='File #',font=fnt, command=self.sortOrig) self.but5.pack(side='left') if os.name == 'mac': self.but1.config(bd=0,highlightbackground=color.winGray) self.but2.config(bd=0,highlightbackground=color.winGray) self.but3.config(bd=0,highlightbackground=color.winGray) self.but4.config(bd=0,highlightbackground=color.winGray) self.but5.config(bd=0,highlightbackground=color.winGray) self.frame.pack_propagate(0) self.frame.pack(anchor='w',fill=None,expand=0) self.tip = 'Sort by file attribute' self.frame.bind('',self.showTip) #end def __init__ def hide(self): #hide the sort buttons frame self.but1.pack_forget() self.but2.pack_forget() self.but3.pack_forget() self.but4.pack_forget() self.but5.pack_forget() self.visible = 0 def load(self,fo): x,y = os.path.split(fo.fname) self.listHeader = ['FILE NAME: '+y,'IN FOLDER: '+x] x = os.stat(fo.fname) self.listHeader.append('FILE DATE: ' \ +pulldem.fixDateTuple(time.ctime(x[8]))) self.listHeader.append('FILE SIZE: '+pulldem.fixSize(x[6])) x = fo.dirLenMax self.timeStr = x['time'] #19 for display, but other for sorting w = self.colWidth = [x['name'],x['size'],19,x['order']] self.width = w[0] + w[1] + w[2] + w[3] + 6 if fo.sdtsAttr: self.listHeader.append('NOTE: Appears to be a USGS SDTS transfer.') self.listHeader.append('-'*self.width) #create horizontal rule x = self.list = [] y = [] for i in range(len(fo.dir)): e = [] # e[0]=sorder text, e[1]=flag, e[2]=listing text, e[3]=tar.dir e.append(rjust(str(fo.dir[i].order),w[3])) #just for starters e.append(0) e.append(ljust(fo.dir[i].name,w[0]) +' '+rjust(str(fo.dir[i].size),w[1])+' ' +ljust(pulldem.fixDateTuple(time.ctime(fo.dir[i].time)),w[2]) +' '+rjust(str(fo.dir[i].order),w[3])) e.append(fo.dir[i]) x.append(e) y.append(e[2]) return self.listHeader + y #end def load def showTip(self,event): if self.parent.showTips.get() and self.visible and self.tip: f = self.frame tip = tipClass(f,self.tip, f.winfo_rootx()+f.winfo_width(), f.winfo_rooty()+f.winfo_height()) def unhide(self): #unhide the sort buttons self.listFlag = [0,0,0,0,1] #reset sort order-reversal flags self.but1.pack(side='left',fill='x',expand=1) self.but2.pack(side='left') self.but3.pack(side='left') self.but4.pack(side='left',fill='x',expand=1) self.but5.pack(side='left') self.visible = 1 #--- sorting functions (in relative order) --- def doSort(self,o): self.list.sort() if self.listFlag[o]: self.list.reverse() self.listFlag[o] = not self.listFlag[o] L = [] for i in range(len(self.list)): L.append(self.list[i][2]) self.parent.list.delete(0,'end') self.parent.listSet(self.listHeader+L) def sortName(self): for i in range(len(self.list)): self.list[i][0] = self.list[i][3].name self.doSort(0) def sortSize(self): for i in range(len(self.list)): self.list[i][0] = rjust(str(self.list[i][3].size),self.colWidth[1]) self.doSort(1) def sortType(self): #insert a leading temp tuple item for type for i in range(len(self.list)): t = ljust(self.list[i][3].type,4) p = self.list[i][3].name.find('.') if p > -1: t = t + self.list[i][3].name[0:p] else: t = t + self.list[i][3].name self.list[i][0] = t self.doSort(2) def sortTime(self): for i in range(len(self.list)): self.list[i][0] = rjust(str(self.list[i][3].time),self.timeStr) self.doSort(3) def sortOrig(self): for i in range(len(self.list)): self.list[i][0] = rjust(str(self.list[i][3].order),self.colWidth[3]) self.doSort(4) #end class sortBarClass class tipClass: """ This class creates a small transient Toplevel window just large enough to contain a widget tip, which is displayed butting against the widget's lower-right corner. If, once displayed, the initial tip window is found to go off the screen's right or bottom edge, then it is moved to a different appropriate corner (top, left, or top left). Since we don't know the size of the tip window until it displays, this off-screen detection and solution can't be calculated and acted on until it displays. By locally binding the mouse and left-mouse-click events for the button to this class's done function, and then entering a local wait loop*, the tip is left up until either event occurs, at which point the tip display is destroyed. A greater widget/tip overlap would be desirable, but more than one pixel overlap can cause problems (flickering and orphan tip windows). *Under Python 2.0 Tkinter, the Tkinter wait_variable() function doesn't work with Mac OS9.x, so a timed loop is used instead (doubled for special windows). """ def __init__(self,parent,tip,x,y): color = colorClass() frame = tk.Toplevel(parent,bd=1,bg=color.black) frame.geometry('+'+str(x-1)+'+'+str(y-1)) frame.transient() #keep from showing up in task list/bar frame.overrideredirect(1) #no window decorations (close box, etc) self.sign = tk.Label(frame,text=tip,bg=color.cream,justify='left') self.sign.pack() #put the text into a label frame.update_idletasks() #make sure it all gets displayed xFix = (frame.winfo_rootx()+frame.winfo_width()) \ - parent.winfo_screenwidth() yFix = (frame.winfo_rooty()+frame.winfo_height()) \ - (parent.winfo_screenheight()-taskBarHt) if xFix > 0 or yFix > 0: #tip goes off the screen one/both ways if xFix <= 0: xFix = frame.winfo_rootx() else: xFix = parent.winfo_rootx() - frame.winfo_width() + 1 if yFix <= 0: yFix = frame.winfo_rooty() else: yFix = parent.winfo_rooty() - frame.winfo_height() + 1 frame.geometry(newGeometry='+'+str(xFix)+'+'+str(yFix)) frame.update_idletasks() if os.name == 'mac': if tip[0] == '<': parent.after(2*tipDelay) else: parent.after(tipDelay) else: parent.bind('',self.done) parent.bind('