#copyright ReportLab Europe Ltd. 2001 - 2017
#see license.txt for license details
'''The GUI editor main script'''
from __future__ import print_function
__version__ = '0.99'
try:
	import tkinter, tkinter.filedialog as tkFileDialog, tkinter.messagebox as tkMessageBox, tkinter.font as tkFont
except ImportError:
	import Tkinter as tkinter, tkFileDialog, tkMessageBox, tkFont
import sys, os, re, copy
import traceback
from reportlab.lib.utils import rl_exec, asNative, isNative, isNonPrimitiveInstance
from rlextra.graphics.guiedit import mutils
from rlextra.graphics.guiedit.guidialogs import AttrListBox, NameDialog, EditListDialog, upColor, RL_ScrolledText, RL_MenuBar, PMDisplay, GoStartDialog, EditTextDialog
from time import time
from reportlab.lib.utils import getStringIO
from codeop import compile_command

_MAIN_METHODS={'go':'()','save':"(formats=['pdf'],outDir='.',fnRoot=None)"}

# Application information
title = 'ReportLab Drawing Editor'
import Pmw
Pmw.aboutversion(__version__)
Pmw.aboutcopyright('Copyright ReportLab Europe Ltd. 2001-2017\nAll rights reserved')
Pmw.aboutcontact('''Contact: ReportLab Support
phone +44-208-545-1570
email: support@reportlab.com
'''
)

root = None
config=None
_MAGIC='#Autogenerated by ReportLab guiedit do not edit'
_escLF=re.compile(r'\\\n',re.MULTILINE)	#re to find escaped line ends
_classNamePat=re.compile(r'^class\s+([^\(\s]+)\s*\(\s*(?:_DrawingEditorMixin\s*,)?\s*([^\)\s]+)\s*\)\s*:')
_startStringPat=re.compile(r'''^\s*[uU]?[rR]?['"]''')
#this horror comes from
#http://code.activestate.com/recipes/475109-regular-expression-for-python-string-literals/
_stringLiteralPat = re.compile(
		r"""\s*[uU]?[rR]?(?:'''(?:[^']|\\'|'{1,2}(?!'))*'''|'(?:[^']|\\')*'(?!')|"""+
		r'''"""(?:[^"]|\\"|"{1,2}(?!"))*"""|"(?:[^"]\\")*"(?!"))\s*$'''
		)

_excNamePat=re.compile(r"^\s*(?:global\s*|)name '(?P<name>[a-zA-Z_]\w*)' is not defined$")
_initPat=re.compile(r"^\tdef __init__\(self,width=([^,]+),height=([^,]+),\*args,\*\*kw\):$")
_mixedPat=re.compile(r"^(?:\t+ +| +\t+)",re.MULTILINE)
_spacePat=re.compile(r"^ +",re.MULTILINE)
_tabPat=re.compile(r"^(\t+)",re.MULTILINE)
_getContentsPat=re.compile(r"^\tdef getContents\(self\):$")
_bnPat=re.compile(r"_[0-9]+$")
_ifmainPat = re.compile('^if\\s+(__name__\\s*==\\s*([\'\"])__main__[\"\']|([\'\"])__main__[\'\"]\\s*==\\s*__name__)\s*:')
_geom_re=re.compile(r'^(\d+)x(\d+)([+-]\d+)([+-]\d+)',re.I)
_testAttrFilter_re=re.compile(r'^\s+self\s*\.\s*test\s*=\s*\d+\s*$')
_returnPat=re.compile(r'^\t\treturn\s+')
editor=None
_known=None
_shapes={'Circle': '(20,20,10)',
 		'Ellipse': '(0,0,5,10)',
		'Group': '()',
		'Line': '(0,0,10,10)',
		'Path': '()',
		'PolyLine': '()',
		'Polygon': '()',
		'Rect':'(10,10,30,40)',
		'String':'(10,10,\'text\')',
		'Wedge':'(20,20,10,85)',
		}

def _hasattr(obj,name):
	try:
		eval('obj.'+name,locals())
		return 1
	except:
		return 0

def _save(obj,rootName,f):
	for k,v in obj.__dict__.items():
		if hasattr(v,'__dict__'):
			_save(v,rootName+k+'.',f)
		else:
			f.write('%s = %s\n' % (rootName+k,repr(v)))

class SubConfig:
	def __init__(self,**kw):
		self.__dict__.update(kw)

class Config:
	def __init__(self,path,**kw):
		self._path = path
		self.__dict__.update(kw)
		self.restore()

	def restore(self,path=None):
		if path is None: path = self._path
		if os.path.isfile(path):
			for l in open(path,'r').readlines():
				i = l.find('=')
				if i>=0:
					name = l[:i].strip()
					if _hasattr(self,name):
						try:
							old = self.getDeepAttr(name)
							rl_exec('self.'+l)
							if type(old) is type({}):
								new = self.getDeepAttr(name)
								if type(new) is type({}):
									for k in old.keys():
										if k not in new: new[k] = old[k]
						except:
							traceback.print_exc()
							print('failed to restore config.%s'%name, file=sys.stderr)

	def getDeepAttr(self,name):
		P = name.split('.')
		o = self
		for p in P:
			o = getattr(o,p.strip(),None)
			if o is None: break
		return o


	def save(self,path=None):
		opath = self._path
		del self._path
		if path is None: path = opath
		_save(self,'',open(path,'w'))
		self._path = opath

def _collectGeom(widget,name):
	if config:
		config.popupGeom[name] = widget.winfo_geometry()
		#if config.debug: writeLog('debug',"%s %s %s %s" % (widget, name, widget.geometry(), widget.winfo_geometry()))

def _savedGeom(name,pos=None):
	geom = None
	if config:
		if name in config.popupGeom:
			geom = config.popupGeom[name]
	if pos:
		if not geom: return pos
		m = _geom_re.match(geom)
		if m:
			geom = "%sx%s%s" % (m.group(1),m.group(2), pos)
		else:
			geom = pos
	return geom

def _popup(d,cbn,pos=None):
	d['deactivatecommand']=lambda d=d, cbn=cbn:_collectGeom(d,cbn)
	try:
		geom = _savedGeom(cbn,pos)
		if geom:
			r = d.activate(geometry=geom)
		else:
			r = d.activate()
	except:
		exceptionLog()
		r = None
	return r

def getBasesStr(C):
	B = []
	for b in C.__bases__:
		B.append(b.__name__)
	return '('+(','.join(B))+')'

def obj2str(obj):
	"""Used to format object values for the attribute list"""
	if isinstance(obj,(int,float)): return str(obj)
	if isNonPrimitiveInstance(obj):
		if _known:
			i = id(obj)
			if i in _known.varsI:
				return _known.varsI[i][0][0]

		if hasattr(obj,'__repr__'):
			return obj.__repr__()
		elif hasattr(obj,'__str__'):
			return obj.__str__()
		else:
			C = obj.__class__
			n = C.__name__
			if n=='TypedPropertyCollection' and hasattr(obj,'_value'):
				C = obj._value.__class__
				return '%s(%s%s)' % (n,C.__name__,getBasesStr(C))
			else:
				return '%s%s' % (n,getBasesStr(C))
	return repr(obj)

def _checkName(n):
	try:
		rl_exec(n.strip() + '=None')
		return 1
	except:
		return 0

def _findLHS(e):
	j = 0
	while 1:
		i = e.find('=',j)
		if i<0: return None
		lhs = e[:i].strip()
		try:
			compile_command(lhs,'<test>','eval')
			return lhs
		except:
			j = i+1

def _stripSub(e):
	'''remove a leading []. and return the LHS part'''
	j = 0
	while 1:
		i = e.find('].',j)
		if i<0: return None
		j = i+2
		lhs = _findLHS(e[j:])
		if lhs: return e[1:i], lhs

def normalizedReadLines(f):
	'''reads from f and returns a list of unescaped lines (without LFs)'''
	if hasattr(f,'read'): text = f.read()
	else: text = open(f,'r').read()
	text = _escLF.sub('',text)
	#autoconvert leading spaces into tabs
	if _mixedPat.search(text):
		raise ValueError('file contains mixed tabs and spaces')
	L = text.split('\n')
	if _spacePat.search(text):
		writeLog('warn','Normalizing spaces to tabs' )
		for i,l in enumerate(L):
			l = l.rstrip()
			if l.startswith(' '):
				n = len(l)
				l = l.lstrip(' ')
				n -= len(l)
				if n%4:
					raise ValueError('line%3d: "%s"\n\tnot tabbable with tab=4' % (i+1,L[i]))
				l = '\t'*(n/4) + l
			L[i] = l
		tab = 0
	else:
		tab = 1
	return tab, L

def _tab2space(m):
 	return (4*(m.end()-m.start()))*' '

def nullMethod(*args,**kw):
	pass

def _adjustDimension(w,bb,A,i,d):
	a = None
	for s in A:
		if hasattr(w,s):
			a = s
			break
	s = bb[i+2]-bb[i]
	if a is None or s<=d: return bb,None
	x0 = getattr(w,a)
	class f:
		cache = {x0: bb}
		def __call__(self,x):
			if x in self.cache:
				bb = self.cache[x]
			else:
				setattr(w,a,x)
				bb = self.cache[x] = w.getBounds()
			return bb[i+2]-bb[i]
	f = f()
	from rlextra.utils.newtonraphson import solve
	x1 = max(x0 - (s - d),int(x0/10),1)
	xs = solve(f,d,x0=x0,precision=1.0/d,x1=x1,maxIterations=10,failHard=0)
	bb = f.cache[xs]
	if int(xs)==xs: xs = int(xs)
	return bb, ('%s = %s' % (a,xs))

def _adjustPosition(w,bb,A,bbf,d):
	a = None
	for s in A:
		if hasattr(w,s):
			a = s
			break
	x0 = getattr(w,a)
	s = bbf(bb)
	if a is None or abs(s-d)<1: return bb,None
	class f:
		cache = {x0: bb}
		def __call__(self,x):
			if x in self.cache:
				bb = self.cache[x]
			else:
				setattr(w,a,x)
				bb = self.cache[x] = w.getBounds()
			return bbf(bb)
	f = f()
	from rlextra.utils.newtonraphson import solve
	x1 = x0 - (s - d)
	xs = solve(f,d,x0=x0,precision=1.0/max(d,1),x1=x1,maxIterations=10,failHard=0)
	bb = f.cache[xs]
	if int(xs)==xs: xs = int(xs)
	return bb, ('%s = %s' % (a,xs))

class _fakeIO:
	def __init__(self,kind):
		self.kind=kind
		self.line=[]
	def write(self,msg):
		while msg:
			x = msg.find('\n')
			if x>=0:
				writeLog(self.kind,''.join(self.line)+msg[:x],flush=1)
				self.line = []
				msg = msg[x+1:]
			else:
				self.line.append(msg)
				break

sys.stdout = _fakeIO_stdout = _fakeIO('info')
sys.stderr = _fakeIO_stderr = _fakeIO('debug')
_initLogText=[]
def writeLog(kind,message,flush=0):
	if editor and editor.log:
		editor.log.write(message,kind=kind,flush=flush)
	else:
		_initLogText.append((kind,message))
		if config:
			n = len(_initLogText)-config.log.maxlines
			if n>0:	del _initLogText[:n]

def exceptionLog(txt=None):
	try:
		S = getStringIO()
		if txt:
			x,v,tb = sys.exc_info()
			n,l,f,t = traceback.extract_tb(tb)[-1]
			del tb
			T = txt.split('\n')
			l -= 1
			txt = (n=='<string>' and l>=0 and l<len(T)) and T[l]+'\n' or ''
			writeLog('warn',"%s%s" % (txt, v))
		traceback.print_exc(None,S)
		writeLog('debug',S.getvalue(),flush=1)
		if not editor or not editor.log:
			pass
		if root and config.errorBells: root.bell()
		S.close()
	except:
		pass

def autoCd(nwd):
	if config.autoCd:
		cwd = os.getcwd()
		try:
			os.chdir(nwd)
		except:
			try:
				os.chdir(cwd)
			except:
				pass

_closeActions = []
def clearTodoList(L=_closeActions):
	while L:
		f = L.pop()
		try:
			f[0](*f[1:])
		except:
			#exceptionLog('error performing close action %s' % repr(f))
			pass

def getWD(nwd):
	wd = cwd = os.getcwd()
	try:
		os.chdir(nwd)
		wd = os.getcwd()
	except:
		os.chdir(cwd)
	return wd

_initFontPaths=None
def addSearchPaths(wd):
	from reportlab.rl_config import T1SearchPath,TTFSearchPath,CMapSearchPath
	global _initFontPaths
	if not _initFontPaths:
		_initFontPaths=dict(T1=T1SearchPath[:],TTF=TTFSearchPath[:],CMap=CMapSearchPath[:])
	else:
		T1SearchPath[:] = _initFontPaths['T1']
		TTFSearchPath[:] = _initFontPaths['TTF']
		CMapSearchPath[:] = _initFontPaths['CMap']
	D=[wd]
	d = os.path.dirname(wd)
	if d: D.append(d)
	for d in config.extraFontPaths:
		D.append(d)
	D.reverse()
	for p in (T1SearchPath,TTFSearchPath,CMapSearchPath):
		for d in D:
			if d not in p:
				p.insert(0,d)
				_closeActions.append((p.remove,d))

class Project:
	def __init__(self,notify=nullMethod):
		self.notify = notify
		self.clear()
		self._badModules=[]

	def clear(self):
		self._modified = 0
		self.className = self.path = self.baseName = self.sample = None
		self.docStrings = []
		self.edits = []
		self.dedits = []
		self.imports = []
		self.froms = {}
		self.medits = []
		self._sys_argv = None
		self._txt = None
		self.notify(self,'clear')
		self.goValues = {}
		self.tab = 1

	def _addBadModule(self,moduleName):
		if moduleName not in self._badModules:
			self._badModules.append(moduleName)

	def _checkBadModule(self):
		if _known:
			_known.reloadModule(self._badModules)
			self._badModules = []

	def _getArgDefault(self,moduleName,baseName,method='__init__',**kw):
		try:
			D = mutils.getModuleClassMethodDefaults(moduleName,baseName,'__init__',
				**kw
				)
			for k,v in kw.items():
				D.setdefault(k,v)
			return D
		except:
			self._addBadModule(moduleName)
			writeLog('warn',"Can't determine class __init__ args: %s %s"% sys.exc_info()[:2])
			self.clear
			return kw

	def new(self,className,baseName,moduleName,baseClass):
		self.clear()
		self._checkBadModule()
		self.className = className
		self.baseName = baseName
		self.addFrom(moduleName,baseName)
		from reportlab.graphics.shapes import _DrawingEditorMixin
		self.dedsc = issubclass(baseClass,_DrawingEditorMixin)
		self._baseWidth = config.drawingWidth
		if self._baseWidth in ('*','auto'):
			self._baseWidth = self._getArgDefault(moduleName,baseName,width=400)['width']
		self._baseHeight = config.drawingHeight
		if self._baseHeight in ('*','auto'):
			self._baseHeight = self._getArgDefault(moduleName,baseName,height=200)['height']
		try:
			self.notify(self,'redraw')
		except:
			t, m = sys.exc_info()[:2]
			writeLog('warn',"Error in initial draw: %s %s" % (t,m))
			self.clear()
			self._addBadModule(moduleName)
			L = traceback.extract_tb(sys.exc_info()[2])
			L.reverse()
			for tb in L:
				n = mutils._normpath(tb[0])	# the filename
				if n=='<string>': break
				if _known.checkPath(n):
					#we know the file probably
					for m in _known.modules:
						if mutils._normpath(m.__file__)==n:
							self._addBadModule(m)
							break
			raise

	def save(self):
		if self._modified:
			if not self.path: self.saveAs()
			else: self.writeout(self.path)

	def saveAs(self):
		fn=self.path and os.path.basename(self.path) or ''
		iDir = self.path and os.path.dirname(self.path) or config.prevDirs.saveAs or ''
		path = tkFileDialog.asksaveasfilename(initialfile=fn,initialdir=iDir or None,filetypes=[('Python files','*.py')])
		if path=='': return
		if path[-3:]!='.py' and os.path.splitext(os.path.basename(path))[-1]=='':
			path += '.py'
		cwd = os.getcwd()
		if not os.path.isabs(path): path = os.path.join(cwd,path)
		nwd = os.path.dirname(path)
		config.prevDirs.saveAs = nwd
		autoCd(nwd)
		self.path = path
		self.writeout(path)
		self.notify(self,'redraw')

	def exportAs(self):
		try:
			D = self._getSample()
		except:
			return
		fn = self.path and os.path.splitext(os.path.basename(self.path))[0] or ''
		iDir = self.path and os.path.dirname(self.path) or config.prevDirs.export or ''
		path = tkFileDialog.asksaveasfilename(initialdir=iDir or None,initialfile=fn,defaultextension='pdf',filetypes=[
			('BMP','*.bmp'),
			('EPS','*.eps'),
			('GIF','*.gif'),
			('JPEG','*.jpg'),
			('PDF','*.pdf'),
			('PNG','*.png'),
			('PS','*.ps'),
			('Python','*.py'),
			('SVG','*.svg'),
			('TIFF','*.tif'),
			])
		if path=='': return
		if not isNative(path): path = asNative(path)
		if not os.path.isabs(path): path = os.path.join(os.getcwd(),path)
		config.prevDirs.export = os.path.dirname(path)
		try:
			ext = os.path.splitext(path)[1][1:]
			if not ext:
				tkMessageBox.showerror('Export',"Export Failed!\nPlease add an extension!")
				return
			D.save(fnRoot=path,formats=[ext],verbose=0)
		except:
			tkMessageBox.showerror('Export',"Export Failed")
			exceptionLog()

	def open(self,path=None):
		if not self._checkModified('Open'): return
		if not path:
			path = tkFileDialog.askopenfilename(initialdir=config.prevDirs.open or '',
					filetypes=[('Python files','*.py')])
		if not path: return
		self.clear()
		cwd = os.getcwd()
		try:
			tab, text = normalizedReadLines(path)
			if text[0][:len(_MAGIC)]!=_MAGIC:
				writeLog('warn',"This file wasn't created by ReportLab guiedit")
			if not os.path.isabs(path): path = os.path.join(cwd,path)
			self.path = path
			nwd = os.path.dirname(path)
			autoCd(nwd)
			addSearchPaths(getWD(nwd))
			self.findImports(text)
			self.findNamesAndEdits(text)
			self.notify(self,'redraw')
			config.prevDirs.open = nwd
			self.tab = tab
		except:
			os.chdir(cwd)
			exceptionLog(self._txt)
			self.clear()
			clearTodoList()

	def writeout(self,path):
		t = self.buildText(excludeMatcher=self._writeFilter)
		if not self.tab:
			writeLog('warn','Converting tabs to spaces' )
			t = _tabPat.sub(_tab2space,t)
		_f = open(path,'w')
		_f.write(t)
		_f.close()
		self._modified = 0
		self.notify(self,'modulecheck')

	def _checkModified(self,title):
		return (not self._modified) or tkMessageBox.askyesno(title,"Ignore unsaved changes?")

	def addImport(self,I):
		imports = self.imports
		if isinstance(I,str): I = [I]
		for i in I:
			if i and i not in imports: imports.append(i)

	def addFrom(self,n,I):
		froms = self.froms
		if isinstance(I,str): I = [I]
		if n not in froms: froms[n] = I
		else:
			F = froms[n]
			for i in I:
				if i and i not in F: F.append(i)

	def delFroms(self,addedFroms):
		'''remove all froms matching pairs (I,name)'''
		froms = self.froms
		imports = self.imports
		for I,name in addedFroms:
			try:
				if name:
					froms[I].remove(name)
					if froms[I]==[]: del froms[I]
				else:
					imports.remove(I)
			except:
				pass

	def findImports(self,text):
		'''scan a bunch of lines looking for import and from name import statements'''
		for t in text:
			s = t.split()
			if len(s)<=1: continue
			if s[0]=='import':
				self.addImport((''.join(s[1:])).split(','))
			elif s[0]=='from':
				assert s[2]=='import', "Corrupt from statement in python source\n'%s'" % t
				self.addFrom(s[1],(''.join(s[3:])).split(','))

	def findNamesAndEdits(self,text):
		state = 0
		n = len(text)
		x = 0
		while x<n:
			t = text[x]
			x += 1
			if not t.strip(): continue
			if state==0 and t[:5]=='class':
				m = _classNamePat.match(t)
				if not m:
					raise ValueError('Can\'t find the class name')
				g = m.groups()
				self.className = g[0]
				self.baseName = g[1]
				self.dedsc = '_DrawingEditorMixin,' not in t
				state = 1
			elif state==1:
				if _startStringPat.match(t):
					if _stringLiteralPat.match(t):
						self.docStrings = [t]
						t = text[x]
						x += 1
					else:
						s = t
						while x<n:
							s += '\n'+text[x]
							x += 1
							if _stringLiteralPat.match(s):
								self.docStrings = s.split('\n')
								t = text[x]
								x += 1
								break
						else:
							raise ValueError('could not complete match of class doc string')
				m = _initPat.match(t)
				if m:
					self._baseWidth = m.group(1)
					self._baseHeight = m.group(2)
					state = 2
					m = ('\t\tapply(%s.__init__,(self,width,height)+args,kw)' % self.baseName,'\t\t%s.__init__(self,width,height,*args,**kw)' % self.baseName)
				else:
					raise ValueError('__init__ part of file not matched\n%r' % t)
			elif state==2:
				if t in m:
					state = 3
					edits = self.edits
				else:
					raise ValueError('after def __init__ unexpected "%s"' % t)
			elif state in [3,6]:
				if t[:2]=='\t\t':
					if state!=6 or not rcpat.match(t):
						edits.append(t.strip())
				else:
					if _ifmainPat.match(t):
						state = 4
						m = 0
						edits = self.medits
					elif state==3 and _getContentsPat.match(t):
						state = 6
						rcpat=re.compile(r'^\t\treturn\s+(?:apply\(\s*%s\s*.\s*getContents\s*,\s*\(\s*self\s*,\s*\)\s*\)|%s.getContents\s*\(\s*self\s*\))\s*$'%(self.baseName,self.baseName))
						edits = self.dedits
					else:
						raise ValueError('Corrupt edit "%s"' % t)
			elif state==4:
				i = t[0]=='\t'
				if i:
					t = t[1:]
					if t[0] in [' ','\t']: i = 0
				if not i:
					raise ValueError('Indentation error in __main__ section "%s"' % t)
				if t[0]!='#': m = m + 1
				edits.append(t.strip())

		if state not in [3,4,6]:
			raise ValueError('File corrupted')
		if state == 4 and m==0:
			raise ValueError('Zero length __main__ section')

	def has_method(self,name):
		for k, v in self.froms.items():
			if self.baseName in v:
				return mutils.getModuleClassMethod(k,self.baseName,name) is not None

	def _writeFilter(self,text):
		if config.testAttrFilter and _testAttrFilter_re.match(text):
			from rlextra.graphics.guiedit.datacharts import DataAwareDrawing
			if issubclass(self.sample.__class__,DataAwareDrawing): return 1
		return 0

	def buildText(self,annotate=None,excludeMatcher=None):
		self._txt = None
		L = []
		if excludeMatcher:
			def aL(x):
				if not excludeMatcher(x): L.append(x)
		else:
			aL = L.append
		aL('%s'%_MAGIC)
		if self.imports: aL('import %s' % ', '.join(self.imports))
		if not self.dedsc:
			self.addFrom('reportlab.graphics.shapes','_DrawingEditorMixin')
		for n, i in self.froms.items():
			aL('from %s import %s'% (n,', '.join(i)))
		aL('')
		aL(
					('class %s(%s):' if self.dedsc else 'class %s(_DrawingEditorMixin,%s):'
						) % (self.className,self.baseName) 
				)
		if annotate is not None:
			n = len(L)
			j1 = 6+len(self.className)
			j2 = j1 + (1 if self.dedsc else len('(_DrawingEditorMixin,'))
			annotate.append(('classname',("%d.%d" % (n,6), "%d.%d" % (n,j1))))
			annotate.append(('basename',("%d.%d" % (n,j2), "%d.%d" % (n,j2+len(self.baseName)))))

		if self.docStrings:
			for x in self.docStrings:
				aL(x)
				if annotate is not None:
					n = len(L)
					y = x.strip()
					j1 = len(x)-len(y)
					j2 = j1+len(y)
					annotate.append(('docstring',("%d.%d" % (n,j1), "%d.%d" % (n,j2))))

		aL('\tdef __init__(self,width=%s,height=%s,*args,**kw):' % (self._baseWidth,self._baseHeight))
		aL('\t\t%s.__init__(self,width,height,*args,**kw)' % self.baseName)
		for e in self.edits:
			if not e.strip(): continue
			aL('\t\t%s' % e )
			if annotate is not None:
				n = len(L)
				annotate.append(('edit',("%d.%d"%(n,2), "%d.end"%n)))
		dedits = self.dedits
		if dedits:
			aL('')
			aL('\tdef getContents(self):')
			for e in dedits:
				if not e.strip(): continue
				aL('\t\t%s' % e )
				if annotate is not None:
					n = len(L)
					annotate.append(('dedit',("%d.%d"%(n,2), "%d.end"%n)))
			if not _returnPat.match(L[-1]):
				aL('\t\treturn %s.getContents(self)' % self.baseName)
		if not self.medits:
			for n, e in _MAIN_METHODS.items():
				if self.has_method(n):
					_meditsClassName = self.className
					self.medits = ['%s().%s%s' % (self.className, n, e)]
					self._modified = 1
					break
		else:
			pat = re.compile(r'\b(\w+)\(\)\.(?:%s)\b' % '|'.join(_MAIN_METHODS.keys()), re.M)
			m = pat.findall('\n'.join(self.medits))
			_meditsClassName = m and m[0] or self.className

		if self.medits:
			aL('')
			aL('if __name__=="__main__": #NORUNTESTS')
			if _meditsClassName!=self.className:
				pat = re.compile(r'\b%s\b' % _meditsClassName)
				self.medits = [pat.sub(self.className,e) for e in self.medits]
			for e in self.medits:
				aL('\t%s' % e)
				if annotate is not None:
					n = len(L)
					annotate.append(('medit',("%d.%d"%(n,1), "%d.end"%n)))
		self._txt = '\n'.join(L)
		return self._txt

	def _getSample(self):
		txt = self.buildText()
		try:
			if self.path:
				import __main__
				__main__._guiedit_sample_path = self.path
				cwd = os.getcwd()
				D = os.path.normpath(os.path.dirname(self.path))
				xD = D not in sys.path
				if xD: sys.path.insert(0,D)
				os.chdir(D)
			if self._sys_argv:
				oargv = sys.argv
				sys.argv = self._sys_argv
			_NS={}
			if sys.stdout!=_fakeIO_stdout:
				sys.stdout = _fakeIO_stdout
				sys.stderr = _fakeIO_stderr
			rl_exec("%s\n_x=%s()\n" % (txt,self.className),_NS)
			if sys.stdout!=_fakeIO_stdout:
				sys.stdout = _fakeIO_stdout
				sys.stderr = _fakeIO_stderr
			_x = _NS['_x']
		finally:
			if self.path:
				del __main__._guiedit_sample_path
				os.chdir(cwd)
				if xD: sys.path.remove(D)
			if self._sys_argv:
				sys.argv = oargv
		if self.path:
			_x.__class__.__module__=os.path.splitext(os.path.basename(self.path))[0]
		dtc = getattr(self.sample,'_drawTimeCollector',None)
		if dtc:
			dtc.disabled = True
		return _x

	def getSample(self):
		self.sample = self._getSample()

def _getClassList(filt):
	C = list(filter(filt,_known.classObjs))
	r = [(x.__name__,x) for x in C]
	r.sort(key=lambda x:(x[0],id(x[1])))
	d = {}
	for n,c in r:
		d[n] = n in d
	N = []
	C = []
	for n,c in r:
		if d[n]: n = c.__module__+'.'+n
		N.append(n)
		C.append(c)
	return N, C

class GUIEditor:
	def __init__(self, top):
		self._attrStack = []
		# Create the Balloon.
		class GUIEditorBalloon(Pmw.Balloon):
			def _showBalloon(self, widget, help, isItem):
				Pmw.Balloon._showBalloon(self, widget, help, isItem)
				self.after(config.root.balloonDelay,self._leave,None)

		balloon = self._balloon = GUIEditorBalloon(top)

		self.project = Project(self.redraw)
		self._state = self.log = self.main = None
		self._editMode = 0

		menu = RL_MenuBar(top,
				hull_relief = 'raised',
				hull_borderwidth = 1,
				balloon = balloon)
		self.menu = menu

		# Add some buttons to the MenuBar.
		menu.addmenu('File', 'File related Actions')
		menu.addmenuitem('File', 'command', 'Close project',
				command = self.closeProject,
				label = 'Close')
		menu.addmenuitem('File', 'command', 'Create new project',
				command = self.newProject,
				label = 'New')
		menu.addmenuitem('File', 'command', 'Open existing project',
				command = self.projectOpen,
				label = 'Open')
		menu.addmenuitem('File', 'command', 'Save project',
				command = self.projectSave,
				label = 'Save')
		menu.addmenuitem('File', 'command', 'Save under new name',
				command = self.projectSaveAs,
				label = 'SaveAs')
		menu.addmenuitem('File', 'command', 'Export under new name',
				command = self.project.exportAs,
				label = 'Export')
		menu.addmenuitem('File', 'separator')
		menu.addmenuitem('File', 'command', 'Exit the application',
				command = self.exit,
				label = 'Exit')
		self._setupRecentFiles()
		self._setupRunGO()

		menu.addmenu('Options', 'Set user preferences')
		menu.addFlipButton('Options', 'Edit getContents on/off',
				label = 'Edit getContents',
				value = self._editMode,
				command = self._editModeToggle,
				)
		menu.addFlipButton('Options', 'Private Attributes on/off',
				label = 'Private Attributes',
				value = config.privateAttrs,
				command = self._privateAttrsToggle,
				)
		menu.addFlipButton('Options', 'Advanced Attributes on/off',
				label = 'Advanced Attributes',
				value = config.advancedAttrs,
				command = self._advancedAttrsToggle,
				)
		menu.addFlipButton('Options', 'test attr. filter on/off',
				label = 'Filter test Attr',
				value = config.testAttrFilter,
				command = self._testAttrFilterToggle,
				)
		menu.addmenuitem('Options', 'separator')
		menu.addFlipButton('Options', 'Error Bells on/off',
				label = 'Error Bells',
				value = config.errorBells,
				command = self._errorBellsToggle,
				)
		menu.addFlipButton('Options', 'Simplify on Save on/off',
				label = 'Save on Simplify',
				value = config.simplifyOnSave,
				command = self._simplifyOnSaveToggle,
				)
		menu.addFlipButton('Options', 'Debug on/off',
				label = 'DEBUG',
				value = config.debug,
				command = self._debugToggle,
				)
		menu.addFlipButton('Options', 'Button3 Binding',
				label = 'Button3',
				value = config.button3,
				command = self._button3,
				)
		menu.addFlipButton('Options', 'Tooltips on/off',
				label = 'Tooltips',
				value = config.root.balloon,
				command = self._balloonToggle,
				)
		menu.addFlipButton('Options', 'Auto CD on/off',
				label = 'Auto CD',
				value = config.autoCd,
				command = self._autoCdToggle,
				)
		menu.addmenuitem('Options', 'separator')
		menu.addmenuitem('Options', 'command', 'Drawing Size',
				command = self._drawingSize,
				label = 'Drawing Size %s x %s' % (config.drawingWidth, config.drawingHeight))

		menu.addcascademenu('Options', 'Auto Position', 'Auto Position',
				traverseSpec = 'z', tearoff = 0)
		for opt in ('off', 'c', 'n','ne','e','se','s','sw','w','nw'):
			menu.addmenuitem('Auto Position', 'command', 'Set Auto Positoning to '+opt,
					command = lambda opt=opt: self._widgetAutoPosition(opt),
					label = opt,
					foreground='darkred')
		self._widgetAutoPosition(config.widgetAutoPosition)
		menu.addFlipButton('Options', 'Auto Size',
				label = 'Auto Size',
				value = config.widgetAutoSize,
				command = self._widgetAutoSize,
				)

		menu.addmenuitem('Options', 'separator')
		menu.addmenuitem('Options', 'command', 'Searched Packages',
				command = self.editSearch,
				label = 'Searched Packages')
		menu.addmenuitem('Options', 'command', 'Package Exclusion Patterns',
				command = self.editExclude,
				label = 'Package Exclusions')
		menu.addmenuitem('Options', 'command', 'Python Path',
				command = self.editPythonPath,
				label = 'Python Path')
		menu.addmenuitem('Options', 'command', 'Extra Font Paths',
				command = self.editExtraFontPaths,
				label = 'Extra Font Paths')

		menu.addmenu('Actions', 'Editor Actions')
		menu.addmenuitem('Actions', 'command', 'Add Widget',
				command = self.addWidget,
				label = 'Add Widget')
		menu.addmenuitem('Actions', 'command', 'Edit Doc String',
				command = self.editDocString,
				label = 'Edit Doc String')
		menu.addmenuitem('Actions', 'command', 'Reload Modules',
				command = self.reloadModules,
				label = 'Reload Modules')
		menu.addmenuitem('Actions', 'command', 'Set Sample sys.argv',
				command = self._set_sample_sys_argv,
				label = 'Set Sample sys.argv')

		menu.addmenu('Windows', 'Window Actions')
		menu.addFlipButton('Windows', 'Log on/off',
				label = 'Log Window',
				value = config.log.on,
				command = self._logToggle,
				)
		menu.addFlipButton('Windows', 'Src on/off',
				label = 'Src Window',
				value = config.src.on,
				command = self._srcToggle,
				)
		menu.addFlipButton('Windows', 'Attributes on/off',
				label = 'Attributes Window',
				value = config.attrList.on,
				command = self._attrListToggle,
				)
		menu.addFlipButton('Windows', 'Attr. Help on/off',
				label = 'Attr. Help Window',
				value = config.attrHelp.on,
				command = self._attrHelpToggle,
				)
		menu.addmenuitem('Windows', 'separator')
		menu.addmenuitem('Windows', 'command', 'Clear Log Window',
				command = self.clearLog,
				label = 'Clear Log Window')
		menu.addmenuitem('Windows', 'separator')
		menu.addmenuitem('Windows', 'command', 'Zoom Set',
				command = self.zoomSet,
				label = 'Zoom Set')

		menu.addmenu('Help', 'User manuals')#, side = 'right')

		self._about = Pmw.AboutDialog(top, applicationname = title)
		self._about.withdraw()
		astr = 'About '+title
		menu.addmenuitem('Help', 'command', astr, command=self._about.show, label=astr)

		alm = self.attrListMenu = tkinter.Menu(top,tearoff=0)
		alm.add_command(label="Up",command=self.attrListContract,foreground=upColor)
		alm.bind('<Leave>',self._attrListMenuLeave)
		alm.bind('<Enter>',self._attrListMenuEnter)

		self.project.clear()
		top.configure(menu=menu)
		top.protocol("WM_DELETE_WINDOW",self.exit)

	def _attrListMenuLeave(self,e):
		self._almDelayedUnpost = root.after(1000,self._attrListMenuUnpost)

	def _attrListMenuUnpost(self,*args,**kwds):
		self._almDelayedUnpost = None
		self.attrListMenu.unpost()

	def _attrListMenuEnter(self,e):
		if getattr(self,'_almDelayedUnpost',None):
			try:
				root.after_cancel(self._almDelayedUnpost)
			except:
				pass
			self._almDelayedUnpost = None

	def attrListPopup(self,event):
		from reportlab.graphics.widgetbase import TypedPropertyCollection
		alm = self.attrListMenu
		n = alm.index('end')
		if n>=1: alm.delete(1,'end')
		alm.entryconfigure(0,state=self._attrStack==[] and 'disabled' or 'normal')
		try:
			self._popup_event = event
			al = self.attrList
			lb = al._AttrListBox__listbox
			al.selectitem(lb.nearest(event.y))
			name = self.getFullObjectName()
			aname = al._lastsetentry[1]
			unsub = aname[0]!='['
			lhs = _findLHS(name+(unsub and '.' or '')+aname)
			if lhs!=None: lhs = lhs[5:]
			if not unsub:
				sname, aname = _stripSub(aname)
				sname = '[%s].%s' % (sname,aname)
			else:
				sname = aname = _findLHS(aname)
			obj = self.getSampleAttr(self.attrRootName)
			if isinstance(obj,TypedPropertyCollection):
				obj = getattr(obj,'_value',obj)
			am = getattr(obj,'_attrMap',None)
			inMap = am and aname in am or 0
			if inMap:
				from reportlab.lib.validators import isBoolean, OneOf
				from rlextra.graphics.guiedit.guidialogs import _addFlipButton
				ame = am[aname]
				desc = getattr(ame,'desc')
				if isinstance(desc, str):
					desc = desc.strip()
				if not desc: 
					desc='No description is available for this item, please refer to the Diagra manual'
				f = tkFont.Font(alm,alm.cget('font'))
				s = int(f.cget('size'))
				if s>=10: f.configure(size=s-2)
				for l in desc.split('\n'):
					alm.add_command(label=l,foreground='darkgreen',font=f)
				validate = ame.validate
				if validate is isBoolean:
					alm.add_separator()
					value = self.getSampleAttr(lhs)
					#var = Tkinter.IntVar()
					#var.set(value)
					#alm.add_checkbutton(label=' '+sname,variable=var,
					#	indicatoron=1,foreground=(value and 'darkgreen' or 'darkred'),
					#	command=lambda v=var,n=sname,cmd=self.attrChange: cmd(new='%s=%d'%(n,v.get())))
					_addFlipButton(alm,label=sname,value=value, flip=['is off', 'is on'],
						command=lambda v,n=sname,cmd=self.attrChange: cmd(new='%s=%d'%(n,v)))
					writeLog('debug',str(alm.configure()))
					writeLog('debug',str(alm.entryconfigure(0)))
				elif isinstance(validate,OneOf):
					value = self.getSampleAttr(lhs)
					alm.add_separator()
					for s in validate._enum:
						alm.add_command(label=s,foreground=(value==s and 'darkgreen' or 'darkred'),
							command=lambda v=s,n=sname,cmd=self.attrChange: cmd(new='%s=%s'%(n,repr(v))))

			alm.post(event.x_root, event.y_root)
		finally:
			del self._popup_event

	def saveConfig(self):
		config.root.geometry = root.winfo_geometry()
		self.pack_forget()
		config.save()

	def pwForget(self, pw, name):
		if pw:
			for i in pw.panes():
				w=getattr(self,i)
				getattr(config,i).size=pw._size[i]
				setattr(self,i,None)
				if isinstance(w,Pmw.PanedWidget):
					self.pwForget(w,i)
				w.pack_forget()
				try:
					w.destroy()
				except:
					pass
			pw.pack_forget()
			setattr(self,name,None)
			pw.destroy()

	def pack_forget(self):
		self.menu.pack_forget()
		self.pwForget(self.main,'main')

	def pack(self):
		main = self.main = Pmw.PanedWidget(root)
		if self._state=='splash':
			self.splash = tkinter.Label(main.add('splash',size=config.splash.size),
										text = 'ReportLab GUI Drawing Editor',
										background = 'black', foreground = 'white',
										padx = 30, pady = 30)
		else:
			pmda = self.pmda = Pmw.PanedWidget(main.add('pmda',size=config.pmda.size),orient='horizontal')
			self.pmd = PMDisplay(pmda.add('pmd',size=config.pmd.size),
				labelpos = 'n',
				label_text = None,
				usehullsize = 0,
				hull_width = 200,
				hull_height = 200,
				vertflex='fixed',
				horizflex='fixed',
				)

			if self.project.className:
				if config.attrList.on:
					n = int(config.attrList.on)+int(config.attrHelp.on)
					if n==2:
						pmdah = self.pmdah = Pmw.PanedWidget(pmda.add('pmdah',size=config.pmdah.size),orient='vertical')
					else:
						pmdah = self.pmdah = pmda
					self.attrList = attrList = AttrListBox(pmdah.add('attrList',size=config.attrList.size),
							labelpos = 'n',
							label_text='Attributes',
							)
					attrList.configure(
						dblclickcommand=self.attrListExpand,
						dncommand=self.attrListExpand,
						upcommand=self.attrListContract,
						)
					ef = attrList._entryfield
					balloon = self._balloon
					balloon.bind(ef,'Make changes here\nand press return')
					self._button3(config.button3,1)
					attrList._entryfield.configure(command=self.attrChange)
					w = attrList.component('label')
					w.bind('<Double-1>',self.attrListContract)
					w.configure(foreground=upColor)
					balloon.bind(w,'Double-click for\nparent attributes')
					balloon.bind(attrList._ub,'Double-click for\nparent attributes')
					if config.attrHelp.on:
						self.attrHelp = RL_ScrolledText(pmdah.add('attrHelp',size=config.attrHelp.size),
							colors=config.attrHelp.colors,
							maxlines = config.attrHelp.maxlines,
							labelpos = 'n', label_text = 'Attribute Help',
							autoclear=1,
							)
					if pmdah is not pmda:
						for i in self.pmdah.panes():
							getattr(self,i).pack(padx=1,pady=2,expand=1,fill='both')

				if config.src.on:
					self.src = RL_ScrolledText(main.add('src',size=config.src.size),
								autoclear=1, colors=config.src.colors,
								labelpos = 'n', label_text = 'Source')
			if pmda is not main:
				for i in self.pmda.panes():
					getattr(self,i).pack(padx=1,pady=2,expand=1,fill='both')
		if config.log.on:
			self.log = RL_ScrolledText(main.add('log',size=config.log.size),
				initText = _initLogText,
				colors=config.log.colors,
				maxlines = config.log.maxlines,
				labelpos = 'n', label_text = 'Log')
		for i in self.main.panes():
			getattr(self,i).pack(padx=1,pady=2,expand=1,fill='both')
		#self.menu.pack(fill = 'x')
		main.pack(pady=2,fill='both',expand=1)

	def getPaneInfo(self):
		'''return [(W,paneName)....] for our paned widgets'''
		GP =lambda W: [(W,x) for x in W.panes()]
		panes = GP(self.main)
		if self.pmda is not self.main:
			panes += GP(self.pmda)
			if self.pmdah is not self.pmda:
				panes += GP(self.pmdah)
		return panes

	def setMain(self,state):
		if self._state==state: return
		self.pack_forget()
		self._state = state
		self.pack()

	def closeProject(self):
		if self.project._checkModified('Close'):
			self.project.clear()
			self._setupRunGO()
		clearTodoList()

	def newProject(self):
		if not self.project._checkModified('New'): return
		self.findObjects()
		from reportlab.graphics.shapes import Drawing
		N, C = _getClassList(lambda c,D=Drawing: issubclass(c,D))

		d = NameDialog(
					title = 'New Project Parameters',
					buttons = ('OK', 'Cancel'),
					defaultbutton = 'OK',
					combobox_labelpos = 'n',
					label_text = 'Base Class Name?',
					namelabel = 'New Class Name',
					scrolledlist_items = N,
					)
		r = _popup(d,'newProject')
		if r != 'OK': return
		baseName = d.get()
		if not baseName in N:
			tkMessageBox.showerror('New',"Base Class must be in list")
			return
		className = d._nameField.get()
		if not className:
			i = _bnPat.search(baseName)
			base = i and baseName[:i.start()] or baseName
			for i in range(1000000):
				className = '%s_%03d' % (base, i)
				if className not in N: break
		if className and '.' in className:
			className = className.split('.')[-1]
		base = C[N.index(baseName)]
		baseName = base.__name__
		moduleName = base.__module__
		for n in (baseName, className):
			if not n or not _checkName(n):
				tkMessageBox.showerror('New',"Invalid class name '%s'"%n)
				return
		self.project.new(className,baseName,moduleName,baseClass=base)
		self._setupRunGO()

	def projectOpen(self,path=None):
		self.project.open(path=path)
		self._setupRecentFiles()
		self._setupRunGO()

	def projectSave(self):
		if config.simplifyOnSave: self._simplifyProperties()
		self.project.save()
		self._setupRecentFiles()
		self._setupRunGO()

	def projectSaveAs(self):
		if config.simplifyOnSave: self._simplifyProperties()
		self.project.saveAs()
		self._setupRecentFiles()
		self._setupRunGO()

	def exit(self):
		if self.project._checkModified('Exit'):
			root.wm_protocol("WM_DELETE_WINDOW", None)
			root.update()
			try:
				self.saveConfig()
			except:
				traceback.print_exc()
			root.destroy()

	def redraw(self,project=None,action='redraw'):
		if	hasattr(self,'main'):
			if project is None: project = self.project
			if action=='clear':
				self.setMain('splash')
				self.attrRootName = ''
				self._editMode = 0
			elif action=='redraw':
				if not self.project.className:
					return self.setMain('splash')
				src = getattr(self,'src',None)
				if src:
					src._storedViews = [src.xview()[0],src.yview()[0]]
				self.setMain('display')
				if not self.project.className: return
				self.newDrawing()
				if config.attrList.on: self.newAttrList()
				if not config.src.on: return
				src = self.src
				A = []
				src.write(self.project.buildText(annotate=A),'info')
				k = getattr(src,'_storedViews',None)
				if k:
					src.xview_moveto(k[0])
					src.yview_moveto(k[1])
					delattr(src,'_storedViews')
				for k, rg in A:
					src.tag_remove('info',rg[0],rg[1:])
					src.tag_add(k,rg[0],rg[1:])
				B3 = config.button3 and '<Button-3>' or '<Shift-Button-1>'
				src.tag_bind('basename', B3, lambda event,self=self: self._changeClassName(event,0))
				src.tag_bind('classname', B3, self._changeClassName)
				src.tag_bind('docstring', B3, lambda e,self=self: self.editDocString())
				src.tag_bind('edit', B3, self._changeEdit)
				src.tag_bind('dedit', B3, lambda x,self=self: self._changeEdit(x,mode='dedit'))
				src.tag_bind('medit', B3, lambda x,self=self: self._changeEdit(x,mode='medit'))
			elif action=='modulecheck':
				if _known.checkPath(self.project.path):
					self.findObjects(force=1)

	def _set_sample_sys_argv(self):
		p = self.project
		old0 = p._sys_argv
		old = old0 or sys.argv
		d = Pmw.TextDialog(None,
				title = 'Sample sys.argv',
				label_text = 'Sample sys.argv elements one per line',
				scrolledtext_labelpos = 'n',
				defaultbutton=0,
				buttons = ('OK', 'Cancel'),
				scrolledtext_text_width= min(120,max(max(list(map(len,old))),30)),
				)
		d.unbind('<Return>')
		d.insert('end','\n'.join(old))
		r=_popup(d,'__set_sample_sys_argv')
		if r!='OK': return
		new = [_f for _f in d.get().split('\n') if _f]
		if len(new)<1 or new==sys.argv: new = None
		if new==old0: return
		try:
			p._sys_argv = new
			if p.className: self.redraw()
			return
		except:
			p._sys_argv = old0
			self._showerror('Alter Sample sys.argv', "Can't change sample.sys.argv:\n%s" % new)
			if p.className: self.redraw()

	def _drawingSize(self):
		d = Pmw.PromptDialog(None,
				title = 'Change Drawing Size',
				label_text = 'geometry:',
				entryfield_labelpos = 'w',
				defaultbutton = 0,
				buttons = ('OK', 'Cancel'),
				entryfield_entry_width=20,
				)
		drawingWidth = config.drawingWidth
		drawingHeight = config.drawingHeight
		d.setentry('%s x %s' % (drawingWidth,drawingHeight))
		def makeInt(x):
			x = x.strip()
			try:
				x = int(x)
			except:
				if x in ('*','auto'): return x
				raise ValueError('Dimension must be integer not "%s"' % x)
			if x<=0: raise ValueError('Negative sizes are not allowed!')
			return x
		try:
			if _popup(d,'_drawingSize')!='OK': return
			wh = d.get().lower().replace('x',' ')
			width, height = list(map(makeInt,[_f for _f in wh.split() if _f]))
			wh = " %s x %s" % (width,height)
			#writeLog('info',"New drawing size"+wh)
			config.drawingWidth = width
			config.drawingHeight = height
			if self.project.className:
				self.project._baseWidth = width
				self.project._baseHeight = height
				self.redraw()
			m = self.menu.component('Options')
			i = m.index('Drawing Size*')
			m.entryconfigure(i,label='Drawing Size'+wh)
		except:
			exceptionLog()

	def _changeEdit(self,event,mode='edit'):
		p = self.project
		e = getattr(p,mode+'s')
		src = self.src
		x = event.x
		i = int(float(src.index("@%d,%d" % (x,event.y))))-int(float(src.index(mode+".first")))
		if i < 0:
			i = 0
		elif i>=len(e):
			i = len(e)-1
		old = e[i]
		d = Pmw.PromptDialog(None,
				title = 'Alter%s Edit' % (mode=='dedit' and ' Dynamic ' or (mode=='medit' and ' __main__' or '')),
				label_text = 'edit:',
				entryfield_labelpos = 'w',
				defaultbutton = 0,
				buttons = ('OK', 'Remove', 'Cancel'),
				entryfield_entry_width=min(120,len(old)),
				)
		#d.overrideredirect(1)
		d.setentry(old)
		while 1:
			r = _popup(d,'_changeEdit',pos="+%d+%d" % (event.x_root-x, event.y_root+20))
			if r not in ('OK','Remove'): return
			new = d.get()
			r = r=='Remove'
			if r:
				del e[i]
			else:
				addedFroms = []
			while 1:
				try:
					if not r: e[i] = new
					self.redraw()
					self.project._modified = 1
					return
				except:
					if r:
						exceptionLog()
						e.insert(i,old)
						t, m = sys.exc_info()[:2]
						O = self.getFullObjectName().split('.')
						if (t==AttributeError and str(m).find(repr(O[-1]))>=0) \
							or (t==IndexError and 'contents' in O):
								writeLog('info',"Are you deleting what's being inspected?")
						self.redraw()
					else:
						e[i] = old
						if r:
							m = "Can't delete %s:\n%s" % (mode,e[i])
						else:
							m = "Can't change %s:\n%s" % (mode,new)
						if self._exceptionFixup(addedFroms,'Alter Edit', m): continue
					break

	def _simplifyProperties(self):
		try:
			p = self.project
			im0 = self.getSampleBmp()
			nDel = 0
			for mode in ('edit','dedit'):
				doHdr = 1
				good = bad = 0
				e = getattr(p,mode+'s',None)
				E = [l for l in e]
				if not e: continue
				ac = mutils.AssignChecker(e)
				aclines = ac.lines
				for i,j in ac.repeatedAssigns:
					acli = aclines[i]
					lhs = acli.LHS[j]
					if doHdr:
						writeLog('info','\nSimplifying %ss' % mode)
						doHdr = 0
					msg = "%s[%d]: %s [%d:%d] " % (mode,i,lhs,acli.assigns[j][1],acli.assigns[j][2])
					oei = e[i]
					try:
						acli.delete(j)
						e[i] = acli.render()
						OK = im0==self.getSampleBmp()
						msg += (OK and 'removed' or 'not removed(changed outcome)')
					except:
						OK = False
						msg += 'not removed(caused exception)'
					if not OK:
						bad += 1
						acli.restore(j)
						e[i] = oei
					else:
						good += 1
					writeLog(OK and 'info' or 'warn',msg,flush=1)
				D = [i for i, ei in enumerate(e) if E[i]!=ei and not ei]
				D.sort()
				D.reverse()
				for i in D:
					del e[i]
				if not doHdr:
					writeLog('info','Assignments: %d removed, %d not removed %d lines totally removed'%(good,bad,len(D)))
				nDel += len(D)
			self.redraw()
			if nDel:
				writeLog('info','project modified')
				p._modified=1
		except:
			exceptionLog('Error during simplify command')

	def _changeClassName(self,event,c=1):
		if c:
			aName, bName = 'className', ''
		else:
			addedFroms = []
			aName, bName = 'baseName', 'Base '
		title = bName + 'Class Name'
		p = self.project
		old = getattr(p,aName)
		d = Pmw.PromptDialog(None,
				title = title,
				label_text = title+':',
				entryfield_labelpos = 'w',
				defaultbutton = 0,
				buttons = ('OK', 'Cancel'),
				)
		d.setentry(old)
		#d.overrideredirect(1)
		r= _popup(d,'_changeClassName', pos="+%d+%d" % (event.x_root-event.x, event.y_root+20))
		if r!='OK': return
		new = d.get()
		while 1:
			try:
				setattr(p,aName,new)
				self.redraw()
				self.project._modified = 1
				return
			except:
				setattr(p,aName,old)
				if c or not self._exceptionFixup(addedFroms,
					title, "Could not rename %sclass %s to class %s" % (bName, old,new)): break

	def _newDrawing(self):
		project = self.project
		self.pmd.newDrawing(config.pmd.zoom, project.sample,
			'File: %s, class %s(...,%s)' % (project.path or '<unknown>', project.className,project.baseName))

	def newDrawing(self):
		self.project.getSample()
		self._newDrawing()

	def getSampleBmp(self):
		self.project.getSample()
		from reportlab.graphics import renderPM
		return renderPM.drawToString(self.project.sample,'bmp')

	def getFullObjectName(self,arn=None):
		if not arn: arn = self.attrRootName
		if arn !='':
			arn = 'self.'+arn
		else:
			arn = 'self'
		return arn

	def newAttrList(self,active=None):
		attrList = self.attrList
		arn = self.attrRootName
		obj = self.getSampleAttr(arn)
		text = "%s Attrs "% self.getFullObjectName(arn)
		newText = attrList['label_text']!=text
		if active is None:
			if newText:
				active = 0
			else:
				n = attrList.curselection()
				if len(n):
					active = int(n[0])
				else:
					active = attrList._lastsetentry[0]
				try:
					active = attrList._list.get(0,'end')[active].split('=')[0].strip()
				except:
					active = 0

		attrList['label_text'] = text
		A = mutils.getAttributes(obj,config.privateAttrs,config.advancedAttrs)
		names = [x[0] for x in A]
		if not isinstance(active,int):
			if active in names:
				active = names.index(active)
			else:
				active = 0
		n = max(list(map(len,names)))
		attrList._n = n
		attrList.setlist(list(map(lambda x,fmt="%-"+str(n)+"s = %s": fmt % (x[0],obj2str(x[1])), A)))
		attrList.selectitem(active)

	def attrListExpand(self,S):
		attrList = self.attrList
		if S:
			n = attrList._n
			if S[n] == '=':
				S = [S[:n],S[n+1]]
			else:
				S = S.split('=')
			S = [s.strip() for s in S]
			s = S[0]
			a = self.attrRootName
			if a: a = a+(s[0]!='[' and '.' or '')+s
			else: a=s
			try:
				obj = self.getSampleAttr(a)
			except:
				writeLog('debug','getSampleAttr(%s) failed'%repr(a))
				exceptionLog()
				return
			if hasattr(obj,'__class__'):
				self._attrStack.append((self.attrRootName,int(attrList.curselection()[0])))
				self.attrRootName = a
				try:
					self.newAttrList()
				except:
					self.attrListContract(None)
					writeLog('warn','failed to expand '+a)
					return

	def attrListContract(self,S=None):
		stack = self._attrStack
		attrList = self.attrList
		if stack==[]:
			writeLog('warn','Can\'t contract!')
			if not len(attrList.curselection()):
				attrList.selectitem(0)
			return
		self.attrRootName, active = stack.pop()
		self.newAttrList(active)

	def attrChange(self, new=None, name=None):
		'''this is called when the entry field is changed'''
		attrList = self.attrList
		if not new:
			new = attrList._entryfield.get().strip()
		active, current = attrList._lastsetentry
		addedFroms = []
		project = self.project
		if self._editMode:
			edits = project.dedits
		else:
			edits = project.edits
		if new != current:
			while 1:
				try:
					name = name or self.getFullObjectName()
					e = name+(new[0]!='[' and '.' or '')+new
					if edits:
						en = _findLHS(e)
						if en:
							eh = edits[-1]
							if en==_findLHS(eh):
								en = eh
								del edits[-1]
							else:
								en = None
					else:
						en = None
					edits.append(e)
					self.redraw()
					project._modified = 1
					return 1
				except:
					del edits[-1]
					if en: edits.append(en)
					if not self._exceptionFixup(addedFroms,'Atribute Change','Couldn\'t set %s' % e): break
		return 0

	def _exceptionFixup(self,addedFroms, title=None, msg=None):
		'''Return 1 if we can possibly fix up and exception by adding a new import'''
		ex_type, ex_value = sys.exc_info()[:2]
		if ex_type==NameError:
			m = _excNamePat.match(str(ex_value))
			if m:
				m = m.group('name')
				K = self.findObjects(m)
				if len(K)==1:
					if list(K.keys())[0]=='':
						# it's a pure module
						I,m = m,''
						self.project.addImport(I)
					else:
						I = list(K.values())[0][0].__name__
						self.project.addFrom(I,m)
					addedFroms.append((I,m))
					return 1
		self.project.delFroms(addedFroms)
		return self._showerror(title,msg)

	def _getWidgetSample(self,widgetdefn):
		imports = []
		froms = {}
		while 1:
			C = []
			if imports: C.append('import ' + ','.join(imports))
			for n, i in froms.items():
				C.append('from %s import %s'% (n,','.join(i)))
			code = 'def gW():\n\t%s\n\treturn %s' % ('\n\t'.join(C),widgetdefn)
			try:
				_NS = {}
				rl_exec(code, _NS)
				gW = _NS['gW']
				return gW()
			except:
				#writeLog('warn', code)
				#exceptionLog()
				ex_type, ex_value = sys.exc_info()[:2]
				if ex_type==NameError:
					m = _excNamePat.match(str(ex_value))
					if m:
						m = m.group('name')
						K = self.findObjects(m)
						if len(K)==1:
							if list(K.keys())[0]=='':
								# it's a pure module
								if i not in imports: imports.append(i)
							else:
								I = list(K.values())[0][0].__name__
								froms.setdefault(I,[]).append(m)
							continue
				return None

	def _showerror(self,title,msg,sample=1):
		exceptionLog(sample and self.project._txt or None)
		self.redraw(action='redraw')
		if title and msg:
			tkMessageBox.showerror(title, msg )
			writeLog('warn', msg)
		return 0

	def getSampleAttr(self,name):
		if name and name[0]!='.': name = '.'+name
		return eval('self.project.sample'+name)

	def findObjects(self,name=None,force=0):
		global _known
		if force or not _known:
			t0 = time()
			writeLog('info','making list of known objects','flush')
			pmwMods = mutils._checkPmw()
			_known = mutils.KnownNames((config.package.search+['!'+p for p in config.pythonpath],
				config.package.exclude+['*.guiedit.guiedit','*.guidialogs']
				))
			mutils._uncheckPmw(pmwMods)
			writeLog('info','finished list of _known objects %.2f"'%(time()-t0))
			writeLog('info','#M=%d #F=%d #C=%d #VN=%d #VI=%d'%(len(_known.modules),len(list(_known.funcs.keys())),
						len(list(_known.classes.keys())),len(list(_known.varsN.keys())),len(list(_known.varsI.keys()))))
		if name: return _known.findName(name)

	def findObjectsFirst(self):
		self._mN = [x for x in list(sys.modules.keys()) if sys.modules[x]]
		self.findObjects()

	def clearLog(self):
		if self.log: self.log.clear()

	def zoomSet(self):
		d = Pmw.PromptDialog(None,
				title = 'Image Zoom',
				label_text = 'Zoom:',
				entryfield_labelpos = 'w',
				defaultbutton = 0,
				buttons = ('OK', 'Cancel'),
				)
		s = "%.2f" % (100*config.pmd.zoom)
		if s[-1]=='0': s=s[:-1]
		if s[-1]=='0': s=s[:-1]
		if s[-1]=='.': s=s[:-1]
		s0=s = s+'%'
		d.setentry(s)
		r=_popup(d,'zoomSet')
		if r!='OK': return
		r0 = r = d.get().strip()
		if r[-1]=='%': r = r[:-1]
		try:
			z = float(r)/100.
			if z<=0.001 or z > 1600: raise ValueError("Invalid Zoom")
		except:
			writeLog('warn','Cannot make a zoom out of %s' % r0)
			return
		r = config.pmd.zoom - z
		if r<0: r = -r
		if r>0.01:
			writeLog('info', "Zoom changed from %s to %s" % (s0,r0))
			config.pmd.zoom = z
			if self.project.className:
				self.redraw()

	def editSearch(self):
		'''Allow user to change the searched packages'''
		d = EditListDialog(
					title = 'Packages to Search',
					buttons = ('OK', 'Cancel'),
					defaultbutton = 'OK',
					combobox_labelpos = 'n',
					label_text = 'Packages',
					scrolledlist_items = config.package.search,
					balloon=self._balloon,
					)
		r=_popup(d,'editSearch')
		if r != 'OK': return
		L = list(d.getList())
		if L!=config.package.search:
			config.package.search = L
			self.findObjects(force=1)

	def editPythonPath(self):
		'''Allow user to change the standard python path'''
		d = EditListDialog(
					title = 'Additional Python Path',
					buttons = ('OK', 'Cancel'),
					defaultbutton = 'OK',
					combobox_labelpos = 'n',
					label_text = 'Python Path',
					scrolledlist_items = config.pythonpath,
					balloon=self._balloon,
					)
		r=_popup(d,'editPythonPath')
		if r != 'OK': return
		L = list(d.getList())
		if L!=config.pythonpath:
			config.pythonpath = L
			_setupPythonPath()
			self.findObjects(force=1)

	def editExclude(self):
		'''Allow user to change the searched packages'''
		d = EditListDialog(
					title = 'Package Exclusion Patterns',
					buttons = ('OK', 'Cancel'),
					defaultbutton = 'OK',
					combobox_labelpos = 'n',
					label_text = 'Exclusion Patterns',
					scrolledlist_items = config.package.exclude,
					balloon=self._balloon,
					)
		r=_popup(d,'editExclude')
		if r != 'OK': return
		L = list(d.getList())
		if L!=config.package.exclude:
			config.package.exclude = L
			self.findObjects(force=1)

	def editExtraFontPaths(self):
		'''Allow user to prefix the reportlab font paths'''
		d = EditListDialog(
					title = 'Extra Font Paths',
					buttons = ('OK', 'Cancel'),
					defaultbutton = 'OK',
					combobox_labelpos = 'n',
					label_text = 'Font Path',
					scrolledlist_items = config.extraFontPaths,
					balloon=self._balloon,
					)
		r=_popup(d,'editExtraFontPaths')
		if r != 'OK': return
		L = list(d.getList())
		if L!=config.extraFontPaths:
			config.extraFontPaths = L
			_setupPythonPath()
			if self.project.className:
				cwd = os.getcwd()
				try:
					nwd = os.path.dirname(self.project.path)
					autoCd(nwd)
					addSearchPaths(getWD(nwd))
				finally:
					os.chdir(cwd)

	def reloadModules(self):
		'''Reload most modules'''
		project = self.project
		redraw = self._state=='display' and project.sample is not None
		B = self._mN+['rlextra.graphics.guiedit.mutils','copy','tempfile','zipfile']
		if redraw: project.sample = None
		M = list(filter(lambda m, B=B: m not in B and hasattr(sys.modules[m],'__file__') and sys.modules[m].__file__[-4:].lower()!='.pyd',list(sys.modules.keys())))
		try:
			_known.reloadModule(M)
		except:
			exceptionLog()
		if redraw: self.redraw()

	def addWidget(self):
		N = self._state=='display' and self.project.sample is not None
		if not N: return
		self.findObjects()
		from reportlab.graphics.widgetbase import Widget
		from reportlab.graphics.shapes import Shape
		_sk = list(_shapes.keys())
		N, C = _getClassList(lambda c, draw=Widget.draw, S=Shape, W=Widget, _sk=_sk: \
									(issubclass(c,W) and c.draw!=draw) or \
										(issubclass(c,S) and  c.__name__ in _sk))
		for i in range(len(N)):
			n = N[i]
			if issubclass(C[i],Shape) and n in _sk:
				N[i] = n+_shapes[n]
		d = NameDialog(
					title = 'Add New Widget',
					buttons = ('OK', 'Cancel'),
					defaultbutton = 'OK',
					combobox_labelpos = 'n',
					label_text = 'Widgets?',
					namelabel = 'New Widget Name',
					scrolledlist_items = N,
					)
		r=_popup(d,'addWidget')
		if r != 'OK': return
		wName = d.get()
		if wName[-1]!=')':
			wName = wName + '()'
		aName = d._nameField.get() or None
		objName = self.getFullObjectName()
		r = self.attrChange(new="_add(%s,%s,name=%s,validate=None,desc=None)" % (objName, wName, repr(aName)),
						name='self')
		wAP = config.widgetAutoPosition
		if r and objName=='self' and (wAP!='off' or config.widgetAutoSize):
			w = self._getWidgetSample(wName)
			if not w:
				writeLog('error', "Cannot get widget sample '%s' for auto sizing/positioning" % wName)
				return
			try:
				bb = w.getBounds()
			except:
				writeLog('error','widget = %s could not get bounds for auto sizing/positioning'+str(w))
				exceptionLog()
				return
			C = []
			if not aName:
				aName = 'contents[%d]' % (len(self.getSampleAttr('contents'))-1)
			pfx = aName+'.'
			width = self.getSampleAttr('width')
			height = self.getSampleAttr('height')
			if config.widgetAutoSize:
				C.append((_adjustDimension,('width','size'),0,width))
				C.append((_adjustDimension,('height','size'),1,height))
			if wAP!='off':
				if wAP.endswith('w'):
					xf = lambda bb: bb[0]
					xt = 0
				elif wAP.endswith('e'):
					xf = lambda bb: bb[2]
					xt = width
				else:
					xf = lambda bb: (bb[0]+bb[2])*0.5
					xt = width*0.5
				if wAP.startswith('s'):
					yf = lambda bb: bb[1]
					yt = 0
				elif wAP.startswith('n'):
					yf = lambda bb: bb[3]
					yt = height
				else:
					yf = lambda bb: (bb[1]+bb[3])*0.5
					yt = height*0.5
				C.append((_adjustPosition,['x'],xf,xt))
				C.append((_adjustPosition,['y'],yf,yt))
			for c in C:
				try:
					bb, s = c[0](*((w,bb)+c[1:]))
					if s:
						r = self.attrChange(new=pfx+s, name='self')
				except:
					exceptionLog()

	def editDocString(self):
		if self._state!='display' or self.project.sample is None: return
		docStrings = self.project.docStrings
		if not docStrings:
			initialText=''
			nt = 1
		else:
			nt = min([len(s)-len(s.lstrip('\t')) for s in docStrings])
			initialText = '\n'.join([s[nt:] for s in docStrings] if nt else docStrings)
		try:
			d = EditTextDialog(
						initialText = initialText,
						title = 'Edit Doc String',
						buttons = ('OK', 'Cancel'),
						defaultbutton = None,
						#label_text = 'Widgets?',
						)
			r=_popup(d,'editTextString')
			if r != 'OK': return
			nt = nt*'\t'
			r = d.get()
			ds = [nt+s for s in r.split('\n')] if r.strip() else []
			for x in (-1,0):	#remove empty trailing/leading lines
				while ds and not ds[x].strip():
					del ds[x]
			self.project.docStrings = ds
			try:
				self.redraw()
			except:
				self.project.docStrings = docStrings
				exceptionLog()
				self.redraw()
		except:
			exceptionLog()

	def _setupRunGO(self):
		menu = self.menu
		smenu= menu.component('File')
		def _dynamicMenu(needed,text,label,command):
			try:
				j = smenu.index(label)
			except:
				j = -1
			if needed:
				i = smenu.index('Exit')
				if j<0:
					menu.insertmenuitem('File',i,'separator')
					menu.insertmenuitem('File',i,'command',text,label=label,command=command)
			elif j>=0:
				menu.deletemenuitems('File',j-1,j)

		_dynamicMenu(self.project.sample,'Simplify Properties','Simplify Properties',self._simplifyProperties)
		from rlextra.graphics.guiedit.datacharts import DataAwareDrawing
		_dynamicMenu(self.project.sample and isinstance(self.project.sample,DataAwareDrawing),
				'Run DataAwareChart','GO',self.runGO)

	def _setupRecentFiles(self,newpath=None):
		RFL = config.recent_files
		newpath = newpath or self.project.path
		if newpath:
			if newpath in RFL:
				if RFL[0]==newpath: return
				RFL.remove(newpath)
			RFL.insert(0,newpath)
		RFL=RFL[:5]
		menu = self.menu
		i0 = menu.component('File').index('Exit')+1
		ie = menu.component('File').index('end')
		if i0<=ie:
			menu.deletemenuitems('File',i0,ie)
		if not RFL: return
		menu.addmenuitem('File', 'separator')
		for i,fn in enumerate(RFL):
			if os.path.isfile(fn):
				menu.addmenuitem('File', 'command', 'Open '+fn,
					command = lambda self=self, path=fn: self.projectOpen(path),
					label = '%d: %s'%(i+1,os.path.basename(fn)))

	def _srcToggle(self,v):
		config.src.on = v
		if self.project.className:
			self._state = None
			self.redraw()

	def _attrHelpToggle(self,v):
		config.attrHelp.on = v
		if self.project.className:
			self._state = None
			self.redraw()

	def _attrListToggle(self,v):
		config.attrList.on = v
		if self.project.className:
			self._state = None
			self.redraw()

	def _logToggle(self,v):
		config.log.on = v
		self._state = None
		self.redraw()
		writeLog('info','Logging turned %s' % (v and 'on' or 'off'))
		if v:
			if self.project.className:
				al = self.attrList
				if al: al._logAttrInfo()

	def _editModeToggle(self,v):
		self._editMode = v
		writeLog('info','Edit getContents turned %s' % (v and 'on' or 'off'))

	def _testAttrFilterToggle(self,v):
		config.testAttrFilter = v
		writeLog('info','testAttrFilter turned %s' % (v and 'on' or 'off'))

	def _errorBellsToggle(self,v):
		config.errorBells = v
		writeLog('info','Error Bells turned %s' % (v and 'on' or 'off'))

	def _widgetAutoSize(self,v):
		config.widgetAutoSize = v
		writeLog('info','First Widget Auto Size turned %s' % (v and 'on' or 'off'))

	def _widgetAutoPosition(self,v):
		m = self.menu.component('Options')
		i = m.index('Auto Position*')
		m.entryconfigure(i,label='Auto Position '+v)
		cm = self.menu.component('Auto Position')
		for t,c in [(config.widgetAutoPosition,'darkred'),(v,'darkgreen')]:
			cm.entryconfigure(cm.index(t),foreground=c)
		config.widgetAutoPosition = v
		#writeLog('info','Widget Auto Position %s' % v)

	def _simplifyOnSaveToggle(self,v):
		config.simplifyOnSave = v
		writeLog('info','Simplify on Save turned %s' % (v and 'on' or 'off'))

	def _debugToggle(self,v):
		config.debug = v
		writeLog('info','DEBUG turned %s' % (v and 'on' or 'off'))

	def _privateAttrsToggle(self,v):
		config.privateAttrs = v
		if self._state=='display' and self.project.sample is not None: self.newAttrList()
		writeLog('info','privateAttrs turned %s' % (v and 'on' or 'off'))

	def _advancedAttrsToggle(self,v):
		config.advancedAttrs = v
		if self._state=='display' and self.project.sample is not None: self.newAttrList()
		writeLog('info','Advanced Attributes turned %s' % (v and 'on' or 'off'))

	def _balloonToggle(self,v):
		config.root.balloon = v
		self._balloon.configure(state=v and 'balloon' or 'none')
		writeLog('info','Tooltips turned %s' % (v and 'on' or 'off'))

	def _autoCdToggle(self,v):
		config.autoCd = v
		writeLog('info','Auto CD turned %s' % (v and 'on' or 'off'))

	def _button3(self,v,which=3):
		config.button3 = v

		B1 = '<Shift-Button-1>'
		B3 = '<Button-3>'
		if not v:
			B1, B3 = B3, B1

		if 1 & which:
			al = getattr(self,'attrList',None)
			if al:
				lb = al._AttrListBox__listbox
				self._balloon.bind(lb,'Double click to expand\nUse %s for info' % (v and 'right' or 'shift'))
				lb.bind(B1,None)
				lb.bind(B3,self.attrListPopup)
		if (2 & which) and self.project.className:
			self._state = None
			self.redraw()

	def runGO(self):
		cwd = os.getcwd()
		project = self.project
		try:
			sample = project.sample
			dataSource = sample.dataSource
			sample._getAndApplyConfig()
			goValues = project.goValues
			isCSV = hasattr(dataSource,'headerRows')
			if isCSV:
				idbA = 'filename'
			else:
				idbA = 'name'
			writeLog('warn',"type(dataSource)=%s isCSV=%d" % (type(dataSource),isCSV))
			iformats = ' '.join(getattr(sample,'formats',[]))
			kwd = dict(
						icwd=goValues.setdefault('icwd',os.path.dirname(project.path or cwd)),
						isql=goValues.setdefault('isql',getattr(dataSource,'sql','')),
						idb=goValues.setdefault('idb',getattr(dataSource,idbA,'')),
						imaxcharts=goValues.setdefault('imaxcharts',''),
						ishow=goValues.setdefault('ishow',0),
						iformats=goValues.setdefault('iformats',iformats),
						ipattern=goValues.setdefault('ipattern',sample.fileNamePattern),
						ioutdir=goValues.setdefault('ioutdir',sample.outDir),
						balloon=self._balloon,
						isCSV = isCSV,
						)
			if not isCSV:
				kwd.update(
					dict(
						idriver=GoStartDialog.DRIVERS.index(goValues.setdefault('idriver',getattr(dataSource,'driver',''))),
						ihost=goValues.setdefault('ihost',getattr(dataSource,'host','')),
						iuser=goValues.setdefault('iuser',getattr(dataSource,'user','')),
						ipasswd=goValues.setdefault('ipasswd',getattr(dataSource,'password','')),
						)
					)
			d = GoStartDialog(**kwd)
			r = _popup(d,'gostartdialog')
			if r!='OK': return
		except:
			exceptionLog('Error during GO command')
			os.chdir(cwd)
			project.getSample()
			return
		maxlines = config.log.maxlines
		SON=dict(src=config.src.on,
				log=config.log.on,
				attrList=config.attrList.on,
				attrHelp=config.attrHelp.on,
				)
		SIZES = [w._size[n] for w,n in self.getPaneInfo()]
		try:
			try:
				ishow = goValues['ishow'] = d._showVar.get()
				SELF = self
				config.src.on = 0
				config.log.on = 1
				config.attrList.on = 0
				config.attrHelp.on = 0
				self._state = None
				self.redraw()
				self.pmd.update()
				self.log.maxlines = None
				sample = project.sample
				dataSource = sample.dataSource
				icwd = sample._override_CWD = goValues['icwd'] = d._cwd.getvalue()
				os.chdir(icwd)
				dataSource.sql = isql = goValues['isql'] = d._sql.getvalue()
				idb = goValues['idb'] = d._db.getvalue()
				setattr(dataSource,idbA,idb)
				if not isCSV:
					dataSource.driver = idriver = goValues['idriver'] = d._driver.getvalue()
					dataSource.host = ihost = goValues['ihost'] = d._host.getvalue()
					dataSource.user = iuser = goValues['iuser'] = d._user.getvalue()
					dataSource.password = ipasswd = goValues['ipasswd'] = d._passwd.getvalue()
				sample.fileNamePattern = ipattern = goValues['ipattern'] = d._pattern.getvalue()
				sample.outDir = ioutdir = goValues['ioutdir'] = d._outdir.getvalue()
				sample.formats = iformats = [_f for _f in d._formats.getvalue().split() if _f]
				goValues['iformats'] = ' '.join(iformats)
				imaxcharts = d._maxcharts.getvalue()
				sample._override_maxCharts = imaxcharts = goValues['imaxcharts'] = imaxcharts and int(imaxcharts) or 0
				sample._override_verbose = 1
				sample._override_FN = project.path and os.path.basename(project.path) or 'UNKNOWN'
				dbVars = not isCSV and ('idriver', 'ihost', 'iuser', 'ipasswd') or ()
				for var in ('icwd', 'isql', 'idb')+dbVars+(
							'ishow', 'iformats', 'imaxcharts', 'ipattern','ioutdir'):
					v=locals()[var]
					if var=='ipasswd': v=len(v)*'*'
					print('%s=%r' % (var[1:],v))
				if ishow:
					klass = sample.__class__
					class ShowWrap(klass):
						_n = 0
						def _saveHook(self,**kwds):
							self._n += 1
							print('drawing charts for data set', self._n)
							klass._saveHook(self,**kwds)
							self._state = None
							SELF._newDrawing()
							SELF.pmd.update()
					sample.__class__ = ShowWrap
					sample.__class__.__name__ = klass.__name__
				sample.go()
			except:
				exceptionLog('Error during GO command')
		finally:
			os.chdir(cwd)
			for k in SON:
				getattr(config,k).on = SON[k]
			config.log.maxlines = maxlines
			project.getSample()
			self._state = None
			self.redraw()
			PI=self.getPaneInfo()
			for s,(w,n) in zip(SIZES,PI):
				w.configurepane(n,size=s)
			s=None
			for w,n in PI:
				if w is not s:
					s = w
					w.updatelayout()
			self.pmd.update()

def _getConfig(fn=None):
	global config
	if not config:
		if fn is None:
			from reportlab.lib.utils import isCompactDistro
			if not isCompactDistro():
				D = os.path.dirname(sys.argv[0])
			else:
				D = os.environ.get('HOME',os.getcwd())
			fc = sys.platform=='win32' and '_' or '.'
			fn = os.path.join(D,'%s%s.ini' % (fc,os.path.splitext(os.path.basename(sys.argv[0]))[0]))
		config = Config(fn,
					package = SubConfig(
						search = [	'reportlab.lib','reportlab','rlextra'],
						exclude = [	'reportlab.demo.*','reportlab.test*',
									'reportlab.doc*','reportlab.utils.*',
									'reportlab.lib.graphdocpy',
									'reportlab.lib.docpy', 'reportlab.tools*',
									],
						),
					main=SubConfig(
						size=400,
						),
					log = SubConfig(
						on=1,
						maxlines=200,
						colors = {
								'info': {'foreground':'blue'},
								'debug': {'foreground':'red'},
								'warn': {'foreground':'orange','background':'grey50'},
								'error': {'foreground':'red','background':'grey50'},
								'security': {'foreground':'blue','background':'red'},
								'temporary': {'foreground': 'darkgreen'},
								},
						size = 100,
						max = 0.25,
						min = 0,
						),
					root = SubConfig(
						geometry = '400x500+100+100',	#width X height +x+y
						balloonDelay = 800,				# milliseconds
						balloon=1,
						bg_color=None, # use '#d4d0c8' for windows style
						),
					pmd = SubConfig(
						on=1,
						size = 200,
						min = 100,
						max = 1.0,
						zoom = 1.0,
						),
					pmda = SubConfig(
						on=1,
						size = 200,
						min = 100,
						max = 1.0,
						zoom = 1.0,
						),
					pmdah = SubConfig(
						on=1,
						size = 200,
						min = 100,
						max = 1.0,
						zoom = 1.0,
						),
					splash = SubConfig(
						on=1,
						size = 200,
						min = 0,
						max = 1.0,
						zoom = 1.0,
						),
					attrList = SubConfig(
						on=1,
						size = 200,
						min = 100,
						max = 1.0,
						),
					attrHelp = SubConfig(
						maxlines=200,
						colors = {
								'info': {'foreground':'blue'},
								'debug': {'foreground':'red'},
								'warn': {'foreground':'orange','background':'grey50'},
								'error': {'foreground':'red','background':'grey50'},
								'security': {'foreground':'blue','background':'red'},
								'temporary': {'foreground': 'darkgreen'},
								},
						on=1,
						size = 200,
						min = 100,
						max = 1.0,
						),
					src = SubConfig(
						on = 1,
						size = 200,
						min = 100,
						max = 1.0,
						colors ={
								'info': {'foreground':'blue'},
								'classname': {'foreground': 'red'},
								'basename': {'foreground': '#ff00ff'},
								'edit': {'foreground': 'darkred'},
								'dedit': {'foreground': '#804000'},
								'medit': {'foreground': '#808000'},
								'docstring': {'foreground':'#008f00'},
								},
						),
					prevDirs = SubConfig(
							saveAs = None,
							export = None,
							open = None,
							),
					popupGeom = {},
					button3 = 1,
					debug=0,
					simplifyOnSave=1,
					testAttrFilter=1,
					privateAttrs=0,
					errorBells=0,
					widgetAutoSize=0,
					widgetAutoPosition='off',
					advancedAttrs=0,
					drawingWidth=400,
					drawingHeight=200,
					recent_files=[],
					autoCd=0,
					pythonpath=[],
					extraFontPaths=[],
				)
	return config

_basePythonPath=sys.path[:]
def _setupPythonPath():
	sys.path[:] = config.pythonpath+_basePythonPath

def _setPalette(root):
	bg_color = config.root.bg_color
	if config.root.bg_color:
		root.tk_setPalette(bg_color)

######################################################################
# Create demo in root window for testing.
def mainApp():
	#sys.stdout = sys.stdin = open(os.path.join(os.path.dirname(sys.argv[0]),'_guiedit.out'),'w')
	global root, editor, config
	writeLog('info','Configuration Starting')
	try:
		fn = [x for x in sys.argv[1:] if x.startswith('--config=')]
		if fn:
			map(sys.argv.remove,fn)
			fn = fn[-1][9:] or None
		else:
			fn = None
	except:
		fn = None
	try:
		_getConfig(fn)
		_setupPythonPath()
		root = tkinter.Tk()
		Pmw.initialise(root, fontScheme = None ) #Pmw.initialise(root, fontScheme = 'pmw1')
		root.title(title)
		root.geometry(config.root.geometry)
		editor = GUIEditor(root)
		_setPalette(root)
	except:
		if not root: root = tkinter.Tk()
		root.withdraw()
		S = getStringIO()
		traceback.print_exc(None,S)
		tkMessageBox.showerror('Guieditor start up error',S.getvalue())
		traceback.print_exc(file=sys.__stderr__)
		return

	root.after(500,editor.findObjectsFirst)
	writeLog('info','Configuration Finished')
	if len(sys.argv)!=1: editor.projectOpen(sys.argv[1])
	root.focus_set()
	root.mainloop()
	
if __name__ == '__main__':
	mainApp()
