diff options
author | Karl Berry <karl@freefriends.org> | 2009-05-16 00:19:13 +0000 |
---|---|---|
committer | Karl Berry <karl@freefriends.org> | 2009-05-16 00:19:13 +0000 |
commit | bab45528d65eaafe68a705dbb2a57075c7b7cbd8 (patch) | |
tree | 10b4ae2b5195c8dede153ab89359ec00f55f325f /Build/source/utils/asymptote/GUI | |
parent | 8643d90372e9c31e0f461c15c596b60a545bd7d3 (diff) |
asymptote 1.72 sources (not integrated into build yet)
git-svn-id: svn://tug.org/texlive/trunk@13110 c570f23f-e606-0410-a88d-b1316a301751
Diffstat (limited to 'Build/source/utils/asymptote/GUI')
-rwxr-xr-x | Build/source/utils/asymptote/GUI/CubicBezier.py | 101 | ||||
-rwxr-xr-x | Build/source/utils/asymptote/GUI/UndoRedoStack.py | 117 | ||||
-rwxr-xr-x | Build/source/utils/asymptote/GUI/xasy.py | 35 | ||||
-rwxr-xr-x | Build/source/utils/asymptote/GUI/xasy2asy.py | 785 | ||||
-rwxr-xr-x | Build/source/utils/asymptote/GUI/xasyActions.py | 387 | ||||
-rwxr-xr-x | Build/source/utils/asymptote/GUI/xasyBezierEditor.py | 208 | ||||
-rwxr-xr-x | Build/source/utils/asymptote/GUI/xasyCodeEditor.py | 37 | ||||
-rwxr-xr-x | Build/source/utils/asymptote/GUI/xasyColorPicker.py | 248 | ||||
-rw-r--r-- | Build/source/utils/asymptote/GUI/xasyExample.asy | 63 | ||||
-rwxr-xr-x | Build/source/utils/asymptote/GUI/xasyFile.py | 315 | ||||
-rwxr-xr-x | Build/source/utils/asymptote/GUI/xasyGUIIcons.py | 92 | ||||
-rwxr-xr-x | Build/source/utils/asymptote/GUI/xasyMainWin.py | 1742 | ||||
-rwxr-xr-x | Build/source/utils/asymptote/GUI/xasyOptions.py | 126 | ||||
-rwxr-xr-x | Build/source/utils/asymptote/GUI/xasyOptionsDialog.py | 245 |
14 files changed, 4501 insertions, 0 deletions
diff --git a/Build/source/utils/asymptote/GUI/CubicBezier.py b/Build/source/utils/asymptote/GUI/CubicBezier.py new file mode 100755 index 00000000000..6455b700a79 --- /dev/null +++ b/Build/source/utils/asymptote/GUI/CubicBezier.py @@ -0,0 +1,101 @@ +#!/usr/bin/env python +########################################################################### +# +# Convert a Bezier curve to a polyline +# +# Once Tk supports "RawCurves" this will not be needed. +# +# +# Author: Orest Shardt +# Created: June 29, 2007 +# +########################################################################### +import math + +def norm(vector): + """Return the norm of a vector""" + return math.sqrt(vector[0]**2+vector[1]**2) + +def splitLine(end0,end1,t): + """Split a line at the distance t, with t in (0,1)""" + return (end0[0]+t*(end1[0]-end0[0]),end0[1]+t*(end1[1]-end0[1])) + +def splitBezier(node0,control0,control1,node1,t): + """Find the nodes and control points for the segments of a Bezier curve split at t""" + a = splitLine(node0,control0,t) + b = splitLine(control0,control1,t) + c = splitLine(control1,node1,t) + d = splitLine(a,b,t) + e = splitLine(b,c,t) + f = splitLine(d,e,t)#this is the point on the curve at t + return ([node0,a,d,f],[f,e,c,node1]) + +def BezierWidth(node0,control0,control1,node1): + """Compute the distance of the control points from the node-node axis""" + deltax = node1[0] - node0[0] + deltay = node1[1] - node0[1] + length = norm((deltax,deltay)) + if length == 0: + y1 = control0[1]-node0[1] + y2 = control1[1]-node0[1] + else: + cosine = deltax/length + sine = deltay/length + y1 = cosine*(control0[1]-node0[1])-sine*(control0[0]-node0[0]) + y2 = cosine*(control1[1]-node0[1])-sine*(control1[0]-node0[0]) + if y1*y2 >= 0: + #same sign + return max(abs(y1),abs(y2)) + else: + #opposite sign + return abs(y1)+abs(y2) + +#If the above algorithm fails, this one will work, but it is far from elegant +#def computeIntermediates(steps,node0,control0,control1,node1): + #pointList = [] + #for a in range(0,100,100/steps)+[100]: + #t = a/100.0 + #t1 = 1-t + #x = node0[0]*t1**3+3*control0[0]*t*t1**2+3*control1[0]*t**2*t1+node1[0]*t**3 + #y = node0[1]*t1**3+3*control0[1]*t*t1**2+3*control1[1]*t**2*t1+node1[1]*t**3 + #pointList.append((x,y)) + #return pointList +#def makeBezier(steps,node0,control0,control1,node1): + #if len(node0)!=2 or len(control0)!=2 or len(control1)!=2 or len(node1)!=2: + #return -1 + #else: + #return [node0]+computeIntermediates(steps,node0,control0,control1,node1)+[node1] + +def makeBezierIntermediates(node0,control0,control1,node1,epsilon): + """Find the points, excluding node0, to be used as the line segment endpoints""" + if(BezierWidth(node0,control0,control1,node1) <= epsilon): + return [node1] + else: + splitUp = splitBezier(node0,control0,control1,node1,0.5) + return makeBezierIntermediates(*splitUp[0]+[epsilon])+makeBezierIntermediates(*splitUp[1]+[epsilon]) + +def makeBezier(node0,control0,control1,node1,epsilon=1): + """Return the vertices to be used in the polyline representation of a Bezier curve""" + return [node0]+makeBezierIntermediates(node0,control0,control1,node1,epsilon) + +if __name__ == '__main__': + pointList = makeBezier((-80,0),(-150,40),(150,120),(80,0),0.5) + from timeit import Timer + t = Timer('makeBezier((-80,0),(-40,-40),(40,120),(80,0),1)','from __main__ import makeBezier') + print pointList + print len(pointList) + iterations = 1000 + time = t.timeit(iterations) + print "%d iterations took %f seconds (%f ms for each)."%(iterations,time,1000.0*time/iterations) + points = [] + for point in pointList: + points.append(point[0]) + points.append(-point[1]) + from Tkinter import * + root = Tk() + canv = Canvas(root,scrollregion=(-100,-100,100,100)) + canv.pack() + canv.create_line(points) + for point in pointList: + canv.create_oval(point[0],-point[1],point[0],-point[1],fill='red',outline='red') + root.mainloop() diff --git a/Build/source/utils/asymptote/GUI/UndoRedoStack.py b/Build/source/utils/asymptote/GUI/UndoRedoStack.py new file mode 100755 index 00000000000..f4a247a411d --- /dev/null +++ b/Build/source/utils/asymptote/GUI/UndoRedoStack.py @@ -0,0 +1,117 @@ +#!/usr/bin/env python +########################################################################### +# +# UndoRedoStack implements the usual undo/redo capabilities of a GUI +# +# Author: Orest Shardt +# Created: July 23, 2007 +# +########################################################################### + +class action: + def __init__(self,act,inv): + self.act = act + self.inv = inv + def undo(self): + #print "Undo:",self + self.inv() + def redo(self): + #print "Redo:",self + self.act() + def __str__(self): + return "A generic action" + +class beginActionGroup: + pass + +class endActionGroup: + pass + +class actionStack: + def __init__(self): + self.clear() + + def add(self,action): + self.undoStack.append(action) + #print "Added",action + self.redoStack = [] + + def undo(self): + if len(self.undoStack) > 0: + op = self.undoStack.pop() + if op is beginActionGroup: + level = 1 + self.redoStack.append(endActionGroup) + while level > 0: + op=self.undoStack.pop() + if op is endActionGroup: + level -= 1 + self.redoStack.append(beginActionGroup) + elif op is beginActionGroup: + level += 1 + self.redoStack.append(endActionGroup) + else: + op.undo() + self.redoStack.append(op) + elif op is endActionGroup: + raise Exception,"endActionGroup without previous beginActionGroup" + else: + self.redoStack.append(op) + op.undo() + #print "undid",op + else: + pass #print "nothing to undo" + + def redo(self): + if len(self.redoStack) > 0: + op = self.redoStack.pop() + if op is beginActionGroup: + level = 1 + self.undoStack.append(endActionGroup) + while level > 0: + op = self.redoStack.pop() + if op is endActionGroup: + level -= 1 + self.undoStack.append(beginActionGroup) + elif op is beginActionGroup: + level += 1 + self.undoStack.append(endActionGroup) + else: + op.redo() + self.undoStack.append(op) + elif op is endActionGroup: + raise Exception,"endActionGroup without previous beginActionGroup" + else: + self.undoStack.append(op) + op.redo() + #print "redid",op + else: + pass #print "nothing to redo" + + def setCommitLevel(self): + self.commitLevel = len(self.undoStack) + + def changesMade(self): + if len(self.undoStack) != self.commitLevel: + return True + else: + return False + + def clear(self): + self.redoStack = [] + self.undoStack = [] + self.commitLevel = 0 + +if __name__=='__main__': + import sys + def opq(): + print "action1" + def unopq(): + print "inverse1" + q = action(opq,unopq) + w = action(lambda:sys.stdout.write("action2\n"),lambda:sys.stdout.write("inverse2\n")) + e = action(lambda:sys.stdout.write("action3\n"),lambda:sys.stdout.write("inverse3\n")) + s = actionStack() + s.add(q) + s.add(w) + s.add(e) diff --git a/Build/source/utils/asymptote/GUI/xasy.py b/Build/source/utils/asymptote/GUI/xasy.py new file mode 100755 index 00000000000..ffca16a1e2d --- /dev/null +++ b/Build/source/utils/asymptote/GUI/xasy.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python +########################################################################### +# +# xasy implements a graphical interface for Asymptote. +# +# +# Author: Orest Shardt +# Created: June 29, 2007 +# +############################################################################ + +import getopt,sys,signal +from Tkinter import * +import xasyMainWin + +signal.signal(signal.SIGINT,signal.SIG_IGN) + +root = Tk() +mag = 1.0 +try: + opts,args = getopt.getopt(sys.argv[1:],"x:") + if(len(opts)>=1): + mag = float(opts[0][1]) +except: + print "Invalid arguments." + print "Usage: xasy.py [-x magnification] [filename]" + sys.exit(1) +if(mag <= 0.0): + print "Magnification must be positive." + sys.exit(1) +if(len(args)>=1): + app = xasyMainWin.xasyMainWin(root,args[0],mag) +else: + app = xasyMainWin.xasyMainWin(root,magnification=mag) +root.mainloop() diff --git a/Build/source/utils/asymptote/GUI/xasy2asy.py b/Build/source/utils/asymptote/GUI/xasy2asy.py new file mode 100755 index 00000000000..23bd5a67c38 --- /dev/null +++ b/Build/source/utils/asymptote/GUI/xasy2asy.py @@ -0,0 +1,785 @@ +#!/usr/bin/env python +########################################################################### +# +# xasy2asy provides a Python interface to Asymptote +# +# +# Author: Orest Shardt +# Created: June 29, 2007 +# +########################################################################### +import sys,os,signal,threading +from subprocess import * +from string import * +import xasyOptions +import Queue +from Tkinter import * +from tempfile import mkdtemp + +# PIL support is now mandatory due to rotations +try: + import ImageTk + import Image +except: + pass + +import CubicBezier + +class asyProcessFailure(Exception): + """asy could not be invoked to execute a command because the process has failed.""" + pass + +idCounter = 0; +randString = 'wGd3I26kOcu4ZI4arZZMqoJufO2h1QE2D728f1Lai3aqeTQC9' + +quickAsyFailed = True +global AsyTempDir + +console=None + +def startQuickAsy(): + global quickAsy + global quickAsyFailed + global AsyTempDir + if quickAsyRunning(): + return + try: + quickAsy.stdin.close() + quickAsy.wait() + except: + pass + try: + quickAsyFailed = False + if os.name == "nt": + AsyTempDir=mkdtemp(prefix="asy_", dir="./") + else: + AsyTempDir=mkdtemp(prefix="asy_")+os.sep + quickAsy = Popen([xasyOptions.options['asyPath']]+ + split("-noV -multiline -interactive -o"+AsyTempDir), + stdin=PIPE,stdout=PIPE,stderr=STDOUT) + quickAsy.stdin.write("settings.xformat;\n") + if quickAsy.returncode != None: + quickAsyFailed = True + except: + quickAsyFailed = True + +def getAsyTempDir(): + return AsyTempDir + +def quickAsyRunning(): + if quickAsyFailed or quickAsy.returncode != None: + return False + else: + return True + +def asyExecute(command): + if not quickAsyRunning(): + startQuickAsy() + syncQuickAsyOutput() + quickAsy.stdin.write(command) + +def closeConsole(event): + global console + console = None + +def consoleOutput(line): + global console + global ctl + if console == None: + ctl=Toplevel() + ctl.title("Asymptote Console") + ctl.bind("<Destroy>",closeConsole) + yscrollbar=Scrollbar(ctl) + yscrollbar.pack(side=RIGHT,fill=Y) + console=Text(ctl,yscrollcommand=yscrollbar.set) + console.pack() + yscrollbar.config(command=console.yview) + console.insert(END,line) + ctl.lift() + +def syncQuickAsyOutput(verbose=False,queue=None): + global idCounter + idStr = randString+"-id "+str(idCounter) + idCounter += 1 + quickAsy.stdin.write("\nwrite(\""+idStr+"\");\n") + quickAsy.stdin.flush() + line = quickAsy.stdout.readline() + while not line.endswith(idStr+'\n'): + if verbose: + queue.put(("OUTPUT",line)) + line = quickAsy.stdout.readline() + quickAsy.stdin.flush() + +class asyTransform: + """A python implementation of an asy transform""" + def __init__(self,initTuple,delete=False): + """Initialize the transform with a 6 entry tuple""" + if type(initTuple) == type((0,)) and len(initTuple) == 6: + self.t = initTuple + self.x,self.y,self.xx,self.xy,self.yx,self.yy = initTuple + self.deleted = delete + else: + raise Exception,"Illegal initializer for asyTransform" + + def getCode(self): + """Obtain the asy code that represents this transform""" + if self.deleted: + return str(self.t) + ", false" + else: + return str(self.t) + + def scale(self,s): + #return asyTransform((s*self.t[0],s*self.t[1],s*self.t[2],s*self.t[3],s*self.t[4],s*self.t[5])) + return asyTransform((0,0,s,0,0,s))*self + + def __str__(self): + """Equivalent functionality to getCode(). It allows the expression str(asyTransform) to be meaningful.""" + return self.getCode() + + def __mul__(self,other): + """Define multiplication of transforms as composition.""" + if type(other)==type((0,)): + if len(other) == 6: + return self*asyTransform(other) + elif len(other) == 2: + return ((self.t[0]+self.t[2]*other[0]+self.t[3]*other[1]),(self.t[1]+self.t[4]*other[0]+self.t[5]*other[1])) + else: + raise Exception, "Illegal multiplier of %s"%str(type(other)) + elif isinstance(other,asyTransform): + result = asyTransform((0,0,0,0,0,0)) + result.x = self.x+self.xx*other.x+self.xy*other.y + result.y = self.y+self.yx*other.x+self.yy*other.y + result.xx = self.xx*other.xx+self.xy*other.yx + result.xy = self.xx*other.xy+self.xy*other.yy + result.yx = self.yx*other.xx+self.yy*other.yx + result.yy = self.yx*other.xy+self.yy*other.yy + result.t = (result.x,result.y,result.xx,result.xy,result.yx,result.yy) + return result + else: + raise Exception, "Illegal multiplier of %s"%str(type(other)) + +def identity(): + return asyTransform((0,0,1,0,0,1)) + +class asyObj: + """A base class for asy objects: an item represented by asymptote code.""" + def __init__(self): + """Initialize the object""" + self.asyCode = "" + + def updateCode(self,mag=1.0): + """Update the object's code: should be overriden.""" + pass + + def getCode(self): + """Return the code describing the object""" + self.updateCode() + return self.asyCode + +class asyPen(asyObj): + """A python wrapper for an asymptote pen""" + def __init__(self,color=(0,0,0),width=0.5,options=""): + """Initialize the pen""" + asyObj.__init__(self) + self.options=options + self.width=width + self.setColor(color) + self.updateCode() + if options != "": + self.computeColor() + + def updateCode(self,mag=1.0): + """Generate the pen's code""" + self.asyCode = "rgb(%g,%g,%g)"%self.color+"+"+str(self.width) + if len(self.options) > 0: + self.asyCode += "+"+self.options + + def setWidth(self,newWidth): + """Set the pen's width""" + self.width=newWidth + self.updateCode() + + def setColor(self,color): + """Set the pen's color""" + if type(color) == type((1,)) and len(color) == 3: + self.color = color + else: + self.color = "(0,0,0)" + self.updateCode() + + def computeColor(self): + """Find out the color of an arbitrary asymptote pen.""" + syncQuickAsyOutput() + quickAsy.stdin.write("pen p="+self.getCode()+';\n') + quickAsy.stdin.write("write(\";\n\");write(colorspace(p));\n") + quickAsy.stdin.write("write(colors(p));\n") + quickAsy.stdin.flush() + testline = quickAsy.stdout.readline() + if testline.startswith("> -:"): + raise Exception,"Invalid pen options" + quickAsy.stdout.readline() + colorspace = quickAsy.stdout.readline() + if colorspace.find("cmyk") != -1: + lines = quickAsy.stdout.readline()+quickAsy.stdout.readline()+quickAsy.stdout.readline()+quickAsy.stdout.readline() + parts = lines.split() + c,m,y,k = eval(parts[2]),eval(parts[4]),eval(parts[6]),eval(parts[8]) + k = 1-k + r,g,b = ((1-c)*k,(1-m)*k,(1-y)*k) + elif colorspace.find("rgb") != -1: + lines = quickAsy.stdout.readline()+quickAsy.stdout.readline()+quickAsy.stdout.readline() + parts = lines.split() + r,g,b = eval(parts[2]),eval(parts[4]),eval(parts[6]) + elif colorspace.find("gray") != -1: + lines = quickAsy.stdout.readline() + parts = lines.split() + r = g = b = eval(parts[2]) + self.color = (r,g,b) + + def tkColor(self): + """Return the tk version of the pen's color""" + self.computeColor() + r,g,b = self.color + r,g,b = int(256*r),int(256*g),int(256*b) + if r == 256: + r = 255 + if g == 256: + g = 255 + if b == 256: + b = 255 + r,g,b = map(hex,(r,g,b)) + r,g,b = r[2:],g[2:],b[2:] + if len(r) < 2: + r += '0' + if len(g) < 2: + g += '0' + if len(b) < 2: + b += '0' + return'#'+r+g+b + +class asyPath(asyObj): + """A python wrapper for an asymptote path""" + def __init__(self): + """Initialize the path to be an empty path: a path with no nodes, control points, or links.""" + asyObj.__init__(self) + self.nodeSet = [] + self.linkSet = [] + self.controlSet = [] + self.computed = False + + def initFromNodeList(self,nodeSet,linkSet): + """Initialize the path from a set of nodes and link types, "--", "..", or "::" """ + if len(nodeSet)>0: + self.nodeSet = nodeSet[:] + self.linkSet = linkSet[:] + self.computed = False + + def initFromControls(self,nodeSet,controlSet): + """Initialize the path from nodes and control points""" + self.controlSet = controlSet[:] + self.nodeSet = nodeSet[:] + self.computed = True + + def makeNodeStr(self,node): + """Represent a node as a string""" + if node == 'cycle': + return node + else: + return "("+str(node[0])+","+str(node[1])+")" + + def updateCode(self,mag=1.0): + """Generate the code describing the path""" + if not self.computed: + count = 0 + #this string concatenation could be optimised + self.asyCode = self.makeNodeStr(self.nodeSet[0]) + for node in self.nodeSet[1:]: + self.asyCode += self.linkSet[count]+self.makeNodeStr(node) + count += 1 + else: + count = 0 + #this string concatenation could be optimised + self.asyCode = self.makeNodeStr(self.nodeSet[0]) + for node in self.nodeSet[1:]: + self.asyCode += "..controls" + self.asyCode += self.makeNodeStr(self.controlSet[count][0]) + self.asyCode += "and" + self.asyCode += self.makeNodeStr(self.controlSet[count][1]) + self.asyCode += ".." + self.makeNodeStr(node) + "\n" + count += 1 + + def getNode(self,index): + """Return the requested node""" + return self.nodeSet[index] + + def getLink(self,index): + """Return the requested link""" + return self.linkSet[index] + + def setNode(self,index,newNode): + """Set a node to a new position""" + self.nodeSet[index] = newNode + + def moveNode(self,index,offset): + """Translate a node""" + if self.nodeSet[index] != "cycle": + self.nodeSet[index] = (self.nodeSet[index][0]+offset[0],self.nodeSet[1]+offset[1]) + + def setLink(self,index,ltype): + """Change the specified link""" + self.linkSet[index] = ltype + + def addNode(self,point,ltype): + """Add a node to the end of a path""" + self.nodeSet.append(point) + if len(self.nodeSet) != 1: + self.linkSet.append(ltype) + if self.computed: + self.computeControls() + + def insertNode(self,index,point,ltype=".."): + """Insert a node, and its corresponding link, at the given index""" + self.nodeSet.insert(index,point) + self.linkSet.insert(index,ltype) + if self.computed: + self.computeControls() + + def setControl(self,index,position): + """Set a control point to a new position""" + self.controlSet[index] = position + + def moveControl(self,index,offset): + """Translate a control point""" + self.controlSet[index] = (self.controlSet[index][0]+offset[0],self.controlSet[index][1]+offset[1]) + + def computeControls(self): + """Evaluate the code of the path to obtain its control points""" + syncQuickAsyOutput() + quickAsy.stdin.write("path p="+self.getCode()+';\n') + quickAsy.stdin.write("write(length(p));\n") + quickAsy.stdin.write("write(unstraighten(p));write(\"\");\n") + quickAsy.stdin.flush() + lengthStr = quickAsy.stdout.readline() + pathSegments = eval(lengthStr.split()[-1]) + pathStrLines = [] + for i in range(pathSegments+1): + pathStrLines.append(quickAsy.stdout.readline()) + oneLiner = "".join(split(join(pathStrLines))) + oneLiner = oneLiner.replace(">","") + splitList = oneLiner.split("..") + nodes = [a for a in splitList if a.find("controls")==-1] + self.nodeSet = [] + for a in nodes: + if a == 'cycle': + self.nodeSet.append(a) + else: + self.nodeSet.append(eval(a)) + controls = [a.replace("controls","").split("and") for a in splitList if a.find("controls") != -1] + self.controlSet = [[eval(a[0]),eval(a[1])] for a in controls] + self.computed = True + +class asyLabel(asyObj): + """A python wrapper for an asy label""" + def __init__(self,text="",location=(0,0),pen=asyPen()): + """Initialize the label with the given test, location, and pen""" + asyObj.__init__(self) + self.text = text + self.location = location + self.pen = pen + + def updateCode(self,mag=1.0): + """Generate the code describing the label""" + self.asyCode = "Label(\""+self.text+"\","+str((self.location[0],self.location[1]))+","+self.pen.getCode()+",align=SE)" + + def setText(self,text): + """Set the label's text""" + self.text = text + self.updateCode() + + def setPen(self,pen): + """Set the label's pen""" + self.pen = pen + self.updateCode() + + def moveTo(self,newl): + """Translate the label's location""" + self.location = newl + +class asyImage: + """A structure containing an image and its format, bbox, and IDTag""" + def __init__(self,image,format,bbox): + self.image = image + self.format = format + self.bbox = bbox + self.IDTag = None + +class xasyItem: + """A base class for items in the xasy GUI""" + def __init__(self,canvas=None): + """Initialize the item to an empty item""" + self.transform = [identity()] + self.asyCode = "" + self.imageList = [] + self.IDTag = None + self.asyfied = False + self.onCanvas = canvas + + def updateCode(self,mag=1.0): + """Update the item's code: to be overriden""" + pass + + def getCode(self): + """Return the code describing the item""" + self.updateCode() + return self.asyCode + + def handleImageReception(self,file,format,bbox,count): + """Receive an image from an asy deconstruction. It replaces the default in asyProcess.""" + image = Image.open(file) + self.imageList.append(asyImage(image,format,bbox)) + if self.onCanvas != None: + self.imageList[-1].itk = ImageTk.PhotoImage(image) + self.imageList[-1].originalImage = image.copy() + self.imageList[-1].originalImage.theta = 0.0 + self.imageList[-1].originalImage.bbox = bbox + if count >= len(self.transform) or self.transform[count].deleted == False: + self.imageList[-1].IDTag = self.onCanvas.create_image(bbox[0],-bbox[3],anchor=NW,tags=("image"),image=self.imageList[-1].itk) + self.onCanvas.update() + + def asyfy(self,mag=1.0): + self.removeFromCanvas() + self.imageList = [] + self.imageHandleQueue = Queue.Queue() + worker = threading.Thread(target=self.asyfyThread,args=(mag,)) + worker.start() + item = self.imageHandleQueue.get() + if console != None: + console.delete(1.0,END) + while item != (None,) and item[0] != "ERROR": + if(item[0] == "OUTPUT"): + consoleOutput(item[1]) + else: + self.handleImageReception(*item) + try: + os.remove(item[0]) + except: + pass + item = self.imageHandleQueue.get() + #self.imageHandleQueue.task_done() + worker.join() + + def asyfyThread(self,mag=1.0): + """Convert the item to a list of images by deconstructing this item's code""" + quickAsy.stdin.write("\nreset;\n") + quickAsy.stdin.write("initXasyMode();\n") + quickAsy.stdin.write("atexit(null);\n") + syncQuickAsyOutput() + global console + for line in self.getCode().splitlines(): + quickAsy.stdin.write(line+"\n"); + quickAsy.stdin.flush() + syncQuickAsyOutput(verbose=True,queue=self.imageHandleQueue); + quickAsy.stdin.write("deconstruct(%f);\n"%mag) + quickAsy.stdin.flush() + format = split(quickAsy.stdout.readline())[1] + maxargs = int(split(quickAsy.stdout.readline())[0]) + boxes=[] + batch=0 + n=0 + text = quickAsy.stdout.readline() + template=AsyTempDir+"%d_%d.%s" + def render(): + for i in range(len(boxes)): + l,b,r,t = [float(a) for a in split(boxes[i])] + name=template%(batch,i+1,format) + self.imageHandleQueue.put((name,format,(l,b,r,t),i)) + while text != "Done\n" and text != "Error\n": + boxes.append(text) + text = quickAsy.stdout.readline() + n += 1 + if n >= maxargs: + render() + boxes=[] + batch += 1 + n=0 + if text == "Error\n": + self.imageHandleQueue.put(("ERROR",quickAsy.stdout.readline())) + else: + render() + self.imageHandleQueue.put((None,)) + self.asyfied = True + + def drawOnCanvas(self,canvas,mag,forceAddition=False): + pass + def removeFromCanvas(self): + pass + +class xasyDrawnItem(xasyItem): + """A base class for GUI items was drawn by the user. It combines a path, a pen, and a transform.""" + def __init__(self,path,pen = asyPen(),transform = identity()): + """Initialize the item with a path, pen, and transform""" + xasyItem.__init__(self) + self.path = path + self.pen = pen + self.transform = [transform] + + def appendPoint(self,point,link=None): + """Append a point to the path. If the path is cyclic, add this point before the 'cycle' node.""" + if self.path.nodeSet[-1] == 'cycle': + self.path.nodeSet[-1] = point + self.path.nodeSet.append('cycle') + else: + self.path.nodeSet.append(point) + self.path.computed = False + if len(self.path.nodeSet) > 1 and link != None: + self.path.linkSet.append(link) + + def clearTransform(self): + """Reset the item's transform""" + self.transform = [identity()] + + def removeLastPoint(self): + """Remove the last point in the path. If the path is cyclic, remove the node before the 'cycle' node.""" + if self.path.nodeSet[-1] == 'cycle': + del self.path.nodeSet[-2] + else: + del self.path.nodeSet[-1] + del self.path.linkSet[-1] + self.path.computed = False + + def setLastPoint(self,point): + """Modify the last point in the path. If the path is cyclic, modify the node before the 'cycle' node.""" + if self.path.nodeSet[-1] == 'cycle': + self.path.nodeSet[-2] = point + else: + self.path.nodeSet[-1] = point + self.path.computed = False + +class xasyShape(xasyDrawnItem): + """An outlined shape drawn on the GUI""" + def __init__(self,path,pen=asyPen(),transform=identity()): + """Initialize the shape with a path, pen, and transform""" + xasyDrawnItem.__init__(self,path,pen,transform) + + def updateCode(self,mag=1.0): + """Generate the code to describe this shape""" + self.asyCode = "xformStack.push("+self.transform[0].getCode()+");\n" + self.asyCode += "draw("+self.path.getCode()+","+self.pen.getCode()+");" + + def removeFromCanvas(self,canvas): + """Remove the shape's depiction from a tk canvas""" + if self.IDTag != None: + canvas.delete(self.IDTag) + + def drawOnCanvas(self,canvas,mag,asyFy=False,forceAddition=False): + """Add this shape to a tk canvas""" + if not asyFy: + if self.IDTag == None or forceAddition: + #add ourselves to the canvas + self.path.computeControls() + self.IDTag = canvas.create_line(0,0,0,0,tags=("drawn","xasyShape"),fill=self.pen.tkColor(),width=self.pen.width*mag) + self.drawOnCanvas(canvas,mag) + else: + self.path.computeControls() + pointSet = [] + previousNode = self.path.nodeSet[0] + nodeCount = 0 + if len(self.path.nodeSet) == 0: + pointSet = [0,0,0,0] + elif len(self.path.nodeSet) == 1: + if self.path.nodeSet[-1] != 'cycle': + p = self.transform[0]*(self.path.nodeSet[0][0],self.path.nodeSet[0][1]) + pointSet = [p[0],-p[1],p[0],-p[1],p[0],-p[1]] + else: + pointSet = [0,0,0,0] + else: + for node in self.path.nodeSet[1:]: + if node == 'cycle': + node = self.path.nodeSet[0] + transform = self.transform[0].scale(mag) + points = CubicBezier.makeBezier(transform*previousNode,transform*self.path.controlSet[nodeCount][0],transform*self.path.controlSet[nodeCount][1],transform*node) + for point in points: + pointSet += [point[0],-point[1]] + nodeCount += 1 + previousNode = node + canvas.coords(self.IDTag,*pointSet) + canvas.itemconfigure(self.IDTag,fill=self.pen.tkColor(),width=self.pen.width*mag) + else: + #first asyfy then add an image list + pass + + def __str__(self): + """Create a string describing this shape""" + return "xasyShape code:%s"%("\n\t".join(self.getCode().splitlines())) + +class xasyFilledShape(xasyShape): + """A filled shape drawn on the GUI""" + def __init__(self,path,pen=asyPen(),transform=identity()): + """Initialize this shape with a path, pen, and transform""" + if path.nodeSet[-1] != 'cycle': + raise Exception,"Filled paths must be cyclic" + xasyShape.__init__(self,path,pen,transform) + + def updateCode(self,mag=1.0): + """Generate the code describing this shape""" + self.asyCode = "xformStack.push("+self.transform[0].getCode()+");\n" + self.asyCode += "fill("+self.path.getCode()+","+self.pen.getCode()+");" + + def removeFromCanvas(self,canvas): + """Remove this shape's depiction from a tk canvas""" + if self.IDTag != None: + canvas.delete(self.IDTag) + + def drawOnCanvas(self,canvas,mag,asyFy=False,forceAddition=False): + """Add this shape to a tk canvas""" + if not asyFy: + if self.IDTag == None or forceAddition: + #add ourselves to the canvas + self.path.computeControls() + self.IDTag = canvas.create_polygon(0,0,0,0,0,0,tags=("drawn","xasyFilledShape"),fill=self.pen.tkColor(),outline=self.pen.tkColor(),width=1*mag) + self.drawOnCanvas(canvas,mag) + else: + self.path.computeControls() + pointSet = [] + previousNode = self.path.nodeSet[0] + nodeCount = 0 + if len(self.path.nodeSet) == 0: + pointSet = [0,0,0,0,0,0] + elif len(self.path.nodeSet) == 1: + if self.path.nodeSet[-1] != 'cycle': + p = self.transform[0]*(self.path.nodeSet[0][0],self.path.nodeSet[0][1]) + pointSet = [p[0],-p[1],p[0],-p[1],p[0],-p[1]] + else: + pointSet = [0,0,0,0,0,0] + elif len(self.path.nodeSet) == 2: + if self.path.nodeSet[-1] != 'cycle': + p = self.transform[0].scale(mag)*(self.path.nodeSet[0][0],self.path.nodeSet[0][1]) + p2 = self.transform[0].scale(mag)*(self.path.nodeSet[1][0],self.path.nodeSet[1][1]) + pointSet = [p[0],-p[1],p2[0],-p2[1],p[0],-p[1]] + else: + pointSet = [0,0,0,0,0,0] + else: + for node in self.path.nodeSet[1:]: + if node == 'cycle': + node = self.path.nodeSet[0] + transform = self.transform[0].scale(mag) + points = CubicBezier.makeBezier(transform*previousNode,transform*self.path.controlSet[nodeCount][0],transform*self.path.controlSet[nodeCount][1],transform*node) + for point in points: + pointSet += [point[0],-point[1]] + nodeCount += 1 + previousNode = node + canvas.coords(self.IDTag,*pointSet) + canvas.itemconfigure(self.IDTag,fill=self.pen.tkColor(),outline=self.pen.tkColor(),width=1*mag) + else: + #first asyfy then add an image list + pass + + def __str__(self): + """Return a string describing this shape""" + return "xasyFilledShape code:%s"%("\n\t".join(self.getCode().splitlines())) + +class xasyText(xasyItem): + """Text created by the GUI""" + def __init__(self,text,location,pen=asyPen(),transform=identity()): + """Initialize this item with text, a location, pen, and transform""" + xasyItem.__init__(self) + self.label=asyLabel(text,location,pen) + self.transform = [transform] + self.onCanvas = None + + def updateCode(self,mag=1.0): + """Generate the code describing this object""" + self.asyCode = "xformStack.push("+self.transform[0].getCode()+");\n" + self.asyCode += "label("+self.label.getCode()+");" + + def removeFromCanvas(self): + """Removes the label's images from a tk canvas""" + if self.onCanvas == None: + return + for image in self.imageList: + if image.IDTag != None: + self.onCanvas.delete(image.IDTag) + + def drawOnCanvas(self,canvas,mag,asyFy=True,forceAddition=False): + """Adds the label's images to a tk canvas""" + if self.onCanvas == None: + self.onCanvas = canvas + elif self.onCanvas != canvas: + raise Exception,"Error: item cannot be added to more than one canvas" + self.asyfy(mag) + + def __str__(self): + return "xasyText code:%s"%("\n\t".join(self.getCode().splitlines())) + +class xasyScript(xasyItem): + """A set of images create from asymptote code. It is always deconstructed.""" + def __init__(self,canvas,script="",transforms=[]): + """Initialize this script item""" + xasyItem.__init__(self,canvas) + self.transform = transforms[:] + self.script = script + + def clearTransform(self): + """Reset the transforms for each of the deconstructed images""" + self.transform = [identity() for im in self.imageList] + + def updateCode(self,mag=1.0): + """Generate the code describing this script""" + self.asyCode = ""; + if len(self.transform) > 0: + self.asyCode = "xformStack.add(" + isFirst = True + count = 0 + for xform in self.transform: + if not isFirst: + self.asyCode+=",\n" + self.asyCode += "indexedTransform(%d,%s)"%(count,str(xform)) + isFirst = False + count += 1 + self.asyCode += ");\n" + self.asyCode += "startScript(); {\n" + self.asyCode += self.script.replace("\t"," ") + self.asyCode = self.asyCode.rstrip() + self.asyCode += "\n} endScript();\n" + + def setScript(self,script): + """Sets the content of the script item.""" + self.script = script + self.updateCode() + + def removeFromCanvas(self): + """Removes the script's images from a tk canvas""" + if self.onCanvas == None: + return + for image in self.imageList: + if image.IDTag != None: + self.onCanvas.delete(image.IDTag) + + def asyfy(self,mag): + """Generate the list of images described by this object and adjust the length of the transform list.""" + xasyItem.asyfy(self,mag) + while len(self.imageList) > len(self.transform): + self.transform.append(identity()) + while len(self.imageList) < len(self.transform): + self.transform.pop() + self.updateCode() + + def drawOnCanvas(self,canvas,mag,asyFy=True,forceAddition=False): + """Adds the script's images to a tk canvas""" + if self.onCanvas == None: + self.onCanvas = canvas + elif self.onCanvas != canvas: + raise Exception,"Error: item cannot be added to more than one canvas" + self.asyfy(mag) + + def __str__(self): + """Return a string describing this script""" + retVal = "xasyScript\n\tTransforms:\n" + for xform in self.transform: + retVal += "\t"+str(xform)+"\n" + retVal += "\tCode Ommitted" + return retVal + +if __name__=='__main__': + root = Tk() + t=xasyText("test",(0,0)) + t.asyfy() diff --git a/Build/source/utils/asymptote/GUI/xasyActions.py b/Build/source/utils/asymptote/GUI/xasyActions.py new file mode 100755 index 00000000000..38aae1d5c7e --- /dev/null +++ b/Build/source/utils/asymptote/GUI/xasyActions.py @@ -0,0 +1,387 @@ +#!/usr/bin/env python +########################################################################### +# +# xasyActions implements the possible actions and their inverses +# for the undo/redo stack in xasy +# +# Author: Orest Shardt +# Created: July 23, 2007 +# +########################################################################### +import math +import UndoRedoStack +import xasy2asy +from Tkinter import * + +class translationAction(UndoRedoStack.action): + def __init__(self,owner,itemList,indexList,translation): + self.translation = translation + self.owner = owner + self.itemList = itemList + self.indexList = indexList + UndoRedoStack.action.__init__(self,self.transF,self.unTransF) + + def transF(self): + mag = self.owner.magnification + for i in range(len(self.itemList)): + for index in self.indexList[i]: + self.owner.translateSomething(-1,(self.translation[0]/mag,self.translation[1]/mag),self.itemList[i],index) + if index==None: + index = 0 + try: + self.owner.mainCanvas.move(self.itemList[i].imageList[index].IDTag,self.translation[0]*mag,-self.translation[1]*mag) + except: + self.owner.mainCanvas.move(self.itemList[i].IDTag,self.translation[0]*mag,-self.translation[1]*mag) + self.owner.updateSelection() + self.owner.updateCanvasSize() + + def unTransF(self): + mag = self.owner.magnification + for i in range(len(self.itemList)): + for index in self.indexList[i]: + self.owner.translateSomething(-1,(-self.translation[0]/mag,-self.translation[1]/mag),self.itemList[i],index) + try: + self.owner.mainCanvas.move(self.itemList[i].imageList[index].IDTag,-self.translation[0]*mag,self.translation[1]*mag) + except: + self.owner.mainCanvas.move(self.itemList[i].IDTag,-self.translation[0]*mag,self.translation[1]*mag) + self.owner.updateSelection() + self.owner.updateCanvasSize() + + def __str__(self): + return "Translation of "+str(self.itemList)+str(self.indexList)+" by "+str(self.translation) + +class rotationAction(UndoRedoStack.action): + def __init__(self,owner,itemList,indexList,angle,origin): + self.owner = owner + self.itemList = itemList + self.indexList = indexList + self.angle = angle + self.origin = origin + UndoRedoStack.action.__init__(self,self.rotF,self.unRotF) + + def rotF(self): + for i in range(len(self.itemList)): + for index in self.indexList[i]: + self.owner.rotateSomething(-1,self.angle,self.origin,self.itemList[i],index) + for item in self.itemList: + item.drawOnCanvas(self.owner.mainCanvas,self.owner.magnification) + self.owner.bindItemEvents(item) + self.owner.updateSelection() + self.owner.updateCanvasSize() + + def unRotF(self): + for i in range(len(self.itemList)): + for index in self.indexList[i]: + self.owner.rotateSomething(-1,-self.angle,self.origin,self.itemList[i],index) + for item in self.itemList: + item.drawOnCanvas(self.owner.mainCanvas,self.owner.magnification) + self.owner.bindItemEvents(item) + self.owner.updateSelection() + self.owner.updateCanvasSize() + + def __str__(self): + return "Rotation of "+str(self.itemList)+str(self.indexList)+" by "+"%.3f"%(self.angle*180.0/math.pi)+" about "+str(self.origin) + +class addLabelAction(UndoRedoStack.action): + def __init__(self,owner,label): + self.owner = owner + self.label = label + UndoRedoStack.action.__init__(self,self.addF,self.unAddF) + + def addF(self): + self.owner.addItemToFile(self.label) + self.label.drawOnCanvas(self.owner.mainCanvas,self.owner.magnification) + self.owner.bindItemEvents(self.label) + + def unAddF(self): + self.label.removeFromCanvas() + del self.owner.fileItems[-1] + self.owner.propList.delete(0) + self.owner.clearSelection() + + def __str__(self): + return "Addition of a label" + +class deleteLabelAction(UndoRedoStack.action): + def __init__(self,owner,label,index): + self.owner = owner + self.label = label + self.index = index + UndoRedoStack.action.__init__(self,self.delF,self.unDelF) + + def delF(self): + self.owner.fileItems[self.index].removeFromCanvas() + self.owner.propList.delete(len(self.owner.fileItems)-self.index-1) + del self.owner.fileItems[self.index] + + def unDelF(self): + self.owner.fileItems.insert(self.index,self.label) + self.owner.fileItems[self.index].drawOnCanvas(self.owner.mainCanvas,self.owner.magnification) + self.owner.propList.insert(len(self.owner.fileItems)-self.index-1,self.owner.describeItem(self.label)) + self.owner.bindItemEvents(self.label) + + def __str__(self): + return "Deletion of a label" + +class editLabelTextAction(UndoRedoStack.action): + def __init__(self,owner,label,newText,oldText): + self.owner = owner + self.label = label + self.newText = newText + self.oldText = oldText + UndoRedoStack.action.__init__(self,self.modT,self.unModT) + + def modT(self): + self.label.label.setText(self.newText) + self.label.drawOnCanvas(self.owner.mainCanvas,self.owner.magnification) + self.owner.bindItemEvents(self.label) + + def unModT(self): + self.label.label.setText(self.oldText) + self.label.drawOnCanvas(self.owner.mainCanvas,self.owner.magnification) + self.owner.bindItemEvents(self.label) + + def __str__(self): + return "Editing a label's text" + +class editLabelPenAction(UndoRedoStack.action): + def __init__(self,owner,oldPen,newPen,index): + self.owner = owner + self.newPen = newPen + self.oldPen = oldPen + self.index = index + UndoRedoStack.action.__init__(self,self.editF,self.unEditF) + + def editF(self): + self.owner.fileItems[self.index].removeFromCanvas() + self.owner.fileItems[self.index].label.pen = self.newPen + self.owner.fileItems[self.index].drawOnCanvas(self.owner.mainCanvas,self.owner.magnification) + self.owner.bindItemEvents(self.owner.fileItems[self.index]) + + def unEditF(self): + self.owner.fileItems[self.index].removeFromCanvas() + self.owner.fileItems[self.index].label.pen = self.oldPen + self.owner.fileItems[self.index].drawOnCanvas(self.owner.mainCanvas,self.owner.magnification) + self.owner.bindItemEvents(self.owner.fileItems[self.index]) + + def __str__(self): + return "Changing a label's pen" + +class addScriptAction(UndoRedoStack.action): + def __init__(self,owner,script): + self.owner = owner + self.script = script + UndoRedoStack.action.__init__(self,self.addF,self.unAddF) + + def addF(self): + self.owner.addItemToFile(self.script) + self.script.drawOnCanvas(self.owner.mainCanvas,self.owner.magnification) + self.owner.bindItemEvents(self.script) + + def unAddF(self): + self.script.removeFromCanvas() + del self.owner.fileItems[-1] + self.owner.propList.delete(0) + self.owner.clearSelection() + + def __str__(self): + return "Addition of a script" + +class deleteScriptAction(UndoRedoStack.action): + def __init__(self,owner,script,index): + self.owner = owner + self.script = script + self.index = index + UndoRedoStack.action.__init__(self,self.delF,self.unDelF) + + def delF(self): + self.owner.fileItems[self.index].removeFromCanvas() + self.owner.propList.delete(len(self.owner.fileItems)-self.index-1) + del self.owner.fileItems[self.index] + + def unDelF(self): + self.owner.fileItems.insert(self.index,self.script) + self.owner.fileItems[self.index].drawOnCanvas(self.owner.mainCanvas,self.owner.magnification) + self.owner.propList.insert(len(self.owner.fileItems)-self.index-1,self.owner.describeItem(self.script)) + self.owner.bindItemEvents(self.script) + + def __str__(self): + return "Deletion of a script" + +class deleteScriptItemAction(UndoRedoStack.action): + def __init__(self,owner,script,indices,oldTransforms): + self.owner = owner + self.script = script + self.indices = indices[:] + UndoRedoStack.action.__init__(self,self.delI,self.unDelI) + def delI(self): + for index in self.indices: + self.script.transform[index].deleted = True + self.owner.mainCanvas.delete(self.script.imageList[index].IDTag) + + def unDelI(self): + for i in range(len(self.indices)): + index = self.indices[i] + self.script.transform[index].deleted = False + bbox = self.script.imageList[index].originalImage.bbox + self.script.imageList[index].IDTag = self.owner.mainCanvas.create_image(bbox[0],-bbox[3],anchor=NW,tags=("image"),image=self.script.imageList[index].itk) + self.owner.bindEvents(self.script.imageList[index].IDTag) + self.owner.resetStacking() + + def __str__(self): + return "Deletion of item "+str(self.indices)+" in "+str(self.script) + +class editScriptAction(UndoRedoStack.action): + def __init__(self,owner,script,newText,oldText): + self.owner = owner + self.script = script + self.newText = newText + self.oldText = oldText + UndoRedoStack.action.__init__(self,self.modS,self.unModS) + + def modS(self): + self.script.setScript(self.newText) + self.script.drawOnCanvas(self.owner.mainCanvas,self.owner.magnification) + self.owner.bindItemEvents(self.script) + + def unModS(self): + self.script.setScript(self.oldText) + self.script.drawOnCanvas(self.owner.mainCanvas,self.owner.magnification) + self.owner.bindItemEvents(self.script) + + def __str__(self): + return "Modification of a script" + +class clearItemTransformsAction(UndoRedoStack.action): + def __init__(self,owner,item,oldTransforms): + self.owner = owner + self.item = item + self.oldTransforms = oldTransforms + UndoRedoStack.action.__init__(self,self.clearF,self.unClearF) + + def clearF(self): + for i in range(len(self.oldTransforms)): + self.item.transform[i] = xasy2asy.identity() + self.item.drawOnCanvas(self.owner.mainCanvas,self.owner.magnification) + + def unClearF(self): + for i in range(len(self.oldTransforms)): + self.item.transform[i] = self.oldTransforms[i] + self.item.drawOnCanvas(self.owner.mainCanvas,self.owner.magnification) + + def __str__(self): + return "Clear the transforms of "+str(self.item)+" from "+str(self.oldTransforms) + +class itemRaiseAction(UndoRedoStack.action): + def __init__(self,owner,items,oldPositions): + self.owner = owner + self.items = items[:] + self.oldPositions = oldPositions[:] + UndoRedoStack.action.__init__(self,self.raiseI,self.unRaiseI) + + def raiseI(self): + for item in self.items: + self.owner.raiseSomething(item) + + def unRaiseI(self): + length = len(self.owner.fileItems) + indices = self.oldPositions[:] + indices = [length-i-1 for i in indices] + indices.reverse() + for index in indices: + for i in range(index): + self.owner.raiseSomething(self.owner.fileItems[length-index-1]) + + def __str__(self): + return "Raise items "+str(self.items)+" from positions "+str(self.oldPositions) + +class itemLowerAction(UndoRedoStack.action): + def __init__(self,owner,items,oldPositions): + self.owner = owner + self.items = items[:] + self.oldPositions = oldPositions[:] + UndoRedoStack.action.__init__(self,self.lowerI,self.unLowerI) + + def lowerI(self): + for item in self.items: + self.owner.lowerSomething(item) + + def unLowerI(self): + indices = self.oldPositions[:] + indices.reverse() + for index in indices: + for i in range(index): + self.owner.lowerSomething(self.owner.fileItems[index]) + + def __str__(self): + return "Lower items "+str(self.items)+" from positions "+str(self.oldPositions) + +class addDrawnItemAction(UndoRedoStack.action): + def __init__(self,owner,item): + self.owner = owner + self.item = item + UndoRedoStack.action.__init__(self,self.drawF,self.unDrawF) + + def drawF(self): + self.owner.addItemToFile(self.item) + self.item.drawOnCanvas(self.owner.mainCanvas,self.owner.magnification,forceAddition=True) + self.owner.bindItemEvents(self.item) + + def unDrawF(self): + self.item.removeFromCanvas(self.owner.mainCanvas) + del self.owner.fileItems[-1] + self.owner.propList.delete(0) + self.owner.clearSelection() + + def __str__(self): + return "Drawing of an item" + +class deleteDrawnItemAction(UndoRedoStack.action): + def __init__(self,owner,item,index): + self.owner = owner + self.item = item + self.index = index + UndoRedoStack.action.__init__(self,self.delF,self.unDelF) + + def delF(self): + self.owner.fileItems[self.index].removeFromCanvas(self.owner.mainCanvas) + self.owner.propList.delete(len(self.owner.fileItems)-self.index-1) + del self.owner.fileItems[self.index] + + def unDelF(self): + self.owner.fileItems.insert(self.index,self.item) + self.owner.fileItems[self.index].drawOnCanvas(self.owner.mainCanvas,self.owner.magnification,forceAddition=True) + self.owner.propList.insert(len(self.owner.fileItems)-self.index-1,self.owner.describeItem(self.item)) + self.owner.bindItemEvents(self.item) + + def __str__(self): + return "Deletion of a drawn item" + +class editDrawnItemAction(UndoRedoStack.action): + def __init__(self,owner,oldItem,newItem,index): + self.owner = owner + self.oldItem = oldItem + self.newItem = newItem + self.index = index + UndoRedoStack.action.__init__(self,self.editF,self.unEditF) + + def editF(self): + self.owner.fileItems[self.index].removeFromCanvas(self.owner.mainCanvas) + self.owner.fileItems[self.index].path = self.newItem.path + self.owner.fileItems[self.index].pen = self.newItem.pen + self.owner.fileItems[self.index].transform = self.newItem.transform + self.owner.fileItems[self.index].IDTag = self.newItem.IDTag + self.owner.fileItems[self.index].drawOnCanvas(self.owner.mainCanvas,self.owner.magnification,forceAddition=True) + self.owner.bindItemEvents(self.owner.fileItems[self.index]) + + def unEditF(self): + self.owner.fileItems[self.index].removeFromCanvas(self.owner.mainCanvas) + self.owner.fileItems[self.index].path = self.oldItem.path + self.owner.fileItems[self.index].pen = self.oldItem.pen + self.owner.fileItems[self.index].transform = self.oldItem.transform + self.owner.fileItems[self.index].IDTag = self.oldItem.IDTag + self.owner.fileItems[self.index].drawOnCanvas(self.owner.mainCanvas,self.owner.magnification,forceAddition=True) + self.owner.bindItemEvents(self.owner.fileItems[self.index]) + + def __str__(self): + return "Modification of a drawn item" diff --git a/Build/source/utils/asymptote/GUI/xasyBezierEditor.py b/Build/source/utils/asymptote/GUI/xasyBezierEditor.py new file mode 100755 index 00000000000..998ee4c7c25 --- /dev/null +++ b/Build/source/utils/asymptote/GUI/xasyBezierEditor.py @@ -0,0 +1,208 @@ +#!/usr/bin/env python +########################################################################### +# +# xasyBezierEditor implements the ability to graphically edit the location +# of the nodes and control points of a bezier curve. +# +# +# Author: Orest Shardt +# Created: June 29, 2007 +# +########################################################################### + +from Tkinter import * +import math +from CubicBezier import * +import xasy2asy + +class node: + def __init__(self,precontrol,node,postcontrol,uid,isTied = True): + self.node = node + self.precontrol = precontrol + self.postcontrol = postcontrol + self.isTied = isTied + self.uid = uid + self.nodeID = self.precontrolID = self.prelineID = self.postcontrolID = self.postlineID = None + + def shiftNode(self,delta): + self.node = (self.node[0]+delta[0],self.node[1]+delta[1]) + if self.precontrol != None: + self.precontrol = (self.precontrol[0]+delta[0],self.precontrol[1]+delta[1]) + if self.postcontrol != None: + self.postcontrol = (self.postcontrol[0]+delta[0],self.postcontrol[1]+delta[1]) + + def shiftPrecontrol(self,delta): + self.precontrol = (self.precontrol[0]+delta[0],self.precontrol[1]+delta[1]) + if self.isTied and self.postcontrol != None: + self.rotatePostControl(self.precontrol) + + def shiftPostcontrol(self,delta): + self.postcontrol = (self.postcontrol[0]+delta[0],self.postcontrol[1]+delta[1]) + if self.isTied and self.precontrol != None: + self.rotatePrecontrol(self.postcontrol) + + def rotatePrecontrol(self,after): + vx,vy = after[0]-self.node[0],after[1]-self.node[1] + l = norm((vx,vy)) + if l == 0: + return + m = norm((self.precontrol[0]-self.node[0],self.precontrol[1]-self.node[1])) + vx = -m*vx/l + vy = -m*vy/l + self.precontrol = self.node[0]+vx,self.node[1]+vy + + def rotatePostControl(self,after): + vx,vy = after[0]-self.node[0],after[1]-self.node[1] + l = norm((vx,vy)) + if l == 0: + return + m = norm((self.postcontrol[0]-self.node[0],self.postcontrol[1]-self.node[1])) + vx = -m*vx/l + vy = -m*vy/l + self.postcontrol = self.node[0]+vx,self.node[1]+vy + + def draw(self,canvas): + width = 3 + if self.precontrol != None: + if self.prelineID == None: + self.prelineID = canvas.create_line(self.precontrol[0],-self.precontrol[1],self.node[0],-self.node[1],tags=("preline",self.uid)) + else: + canvas.coords(self.prelineID,self.precontrol[0],-self.precontrol[1],self.node[0],-self.node[1]) + if self.precontrolID == None: + self.precontrolID = canvas.create_oval(self.precontrol[0]-width,-self.precontrol[1]-width,self.precontrol[0]+width,-self.precontrol[1]+width, + fill="red",outline="black",tags=("precontrol",self.uid)) + else: + canvas.coords(self.precontrolID,self.precontrol[0]-width,-self.precontrol[1]-width,self.precontrol[0]+width,-self.precontrol[1]+width) + if self.postcontrol != None: + if self.postlineID == None: + self.postlineID = canvas.create_line(self.postcontrol[0],-self.postcontrol[1],self.node[0],-self.node[1],tags=("postline",self.uid)) + else: + canvas.coords(self.postlineID,self.postcontrol[0],-self.postcontrol[1],self.node[0],-self.node[1]) + if self.postcontrolID == None: + self.postcontrolID = canvas.create_oval(self.postcontrol[0]-width,-self.postcontrol[1]-width,self.postcontrol[0]+width,-self.postcontrol[1]+width, + fill="red",outline="black",tags=("postcontrol",self.uid)) + else: + canvas.coords(self.postcontrolID,self.postcontrol[0]-width,-self.postcontrol[1]-width,self.postcontrol[0]+width,-self.postcontrol[1]+width) + if self.isTied: + color = "blue" + else: + color = "green" + if self.nodeID == None: + self.nodeID = canvas.create_oval(self.node[0]-width,-self.node[1]-width,self.node[0]+width,-self.node[1]+width, + fill=color,outline="black",tags=("node",self.uid)) + else: + canvas.coords(self.nodeID,self.node[0]-width,-self.node[1]-width,self.node[0]+width,-self.node[1]+width) + canvas.itemconfigure(self.nodeID,fill=color) + +class xasyBezierEditor: + def __init__(self,parent,shape,canvas): + self.parent = parent + self.shape = shape + self.transform = self.shape.transform[0] + self.path = self.shape.path + self.canvas = canvas + self.modified = False + self.path.computeControls() + isCyclic = self.path.nodeSet[-1] == 'cycle' + segments = len(self.path.controlSet) + self.nodeList = [] + for i in range(segments): + if i == 0: + node0 = self.transform*self.path.nodeSet[i] + control = self.transform*self.path.controlSet[i][0] + self.nodeList.append(node(None,node0,control,len(self.nodeList))) + else: + node0 = self.transform*self.path.nodeSet[i] + precontrol = self.transform*self.path.controlSet[i-1][1] + postcontrol = self.transform*self.path.controlSet[i][0] + self.nodeList.append(node(precontrol,node0,postcontrol,len(self.nodeList))) + if not isCyclic: + node0 = self.transform*self.path.nodeSet[-1] + precontrol = self.transform*self.path.controlSet[-1][1] + self.nodeList.append(node(precontrol,node0,None,len(self.nodeList))) + else: + self.nodeList[0].precontrol = self.transform*self.path.controlSet[-1][1] + self.showControls() + self.bindNodeEvents() + self.bindControlEvents() + + def showControls(self): + for n in self.nodeList: + n.draw(self.canvas) + self.bindNodeEvents() + self.bindControlEvents() + self.parent.updateCanvasSize() + + def bindNodeEvents(self): + self.canvas.tag_bind("node","<B1-Motion>",self.nodeDrag) + self.canvas.tag_bind("node","<Button-1>",self.buttonDown) + self.canvas.tag_bind("node","<Double-Button-1>",self.toggleNode) + + def unbindNodeEvents(self): + self.canvas.tag_unbind("node","<B1-Motion>") + self.canvas.tag_unbind("node","<Button-1>") + self.canvas.tag_unbind("node","<Double-Button-1>") + + def bindControlEvents(self): + self.canvas.tag_bind("precontrol || postcontrol","<B1-Motion>",self.controlDrag) + self.canvas.tag_bind("precontrol || postcontrol","<Button-1>",self.buttonDown) + + def unbindControlEvents(self): + self.canvas.tag_unbind("precontrol || postcontrol","<B1-Motion>") + self.canvas.tag_unbind("precontrol || postcontrol","<Button-1>") + + def buttonDown(self,event): + self.parent.freeMouseDown = False + self.startx,self.starty = event.x,event.y + + def toggleNode(self,event): + self.parent.freeMouseDown = False + tags = self.canvas.gettags(CURRENT) + obj = tags[0] + uid = int(tags[1]) + self.nodeList[uid].isTied = not self.nodeList[uid].isTied + self.showControls() + + def nodeDrag(self,event): + self.parent.freeMouseDown = False + deltax = event.x-self.startx + deltay = event.y-self.starty + tags = self.canvas.gettags(CURRENT) + obj = tags[0] + uid = int(tags[1]) + self.nodeList[uid].shiftNode((deltax,-deltay)) + self.startx,self.starty = event.x,event.y + self.applyChanges() + self.showControls() + self.shape.drawOnCanvas(self.canvas,self.parent.magnification) + + def controlDrag(self,event): + self.parent.freeMouseDown = False + deltax = event.x-self.startx + deltay = event.y-self.starty + tags = self.canvas.gettags(CURRENT) + obj = tags[0] + uid = int(tags[1]) + if obj == "precontrol": + self.nodeList[uid].shiftPrecontrol((deltax,-deltay)) + elif obj == "postcontrol": + self.nodeList[uid].shiftPostcontrol((deltax,-deltay)) + self.startx,self.starty = event.x,event.y + self.applyChanges() + self.showControls() + self.shape.drawOnCanvas(self.canvas,self.parent.magnification) + + def applyChanges(self): + self.modified = True + self.shape.transform[0] = xasy2asy.asyTransform((0,0,1,0,0,1)) + for i in range(len(self.nodeList)): + self.path.nodeSet[i] = self.nodeList[i].node + if self.nodeList[i].postcontrol != None: + self.path.controlSet[i][0] = self.nodeList[i].postcontrol + if self.nodeList[i].precontrol != None: + self.path.controlSet[i-1][1] = self.nodeList[i].precontrol + + def endEdit(self): + self.unbindNodeEvents() + self.unbindControlEvents() + self.canvas.delete("node || precontrol || postcontrol || preline || postline") diff --git a/Build/source/utils/asymptote/GUI/xasyCodeEditor.py b/Build/source/utils/asymptote/GUI/xasyCodeEditor.py new file mode 100755 index 00000000000..93e06e447ee --- /dev/null +++ b/Build/source/utils/asymptote/GUI/xasyCodeEditor.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python +########################################################################### +# +# xasyCodeEditor implements a simple text editor for Asymptote scripts in +# xasy. +# +# +# Author: Orest Shardt +# Created: June 29, 2007 +# +############################################################################ + +from subprocess import call +from tempfile import mkstemp +from os import remove +from os import fdopen +from string import split +import xasyOptions + +def getText(text=""): + """Launch the external editor""" + temp = mkstemp() + tempf = fdopen(temp[0],"r+w") + tempf.write(text) + tempf.flush() + try: + call(split(xasyOptions.options['externalEditor'])+[temp[1]]) + except: + raise Exception('Error launching external editor.') + tempf.seek(0) + text = tempf.read() + remove(temp[1]) + return text + +if __name__ == '__main__': + #run a test + print getText("Here is some text to edit") diff --git a/Build/source/utils/asymptote/GUI/xasyColorPicker.py b/Build/source/utils/asymptote/GUI/xasyColorPicker.py new file mode 100755 index 00000000000..7415be205c4 --- /dev/null +++ b/Build/source/utils/asymptote/GUI/xasyColorPicker.py @@ -0,0 +1,248 @@ +#!/usr/bin/env python +########################################################################### +# +# xasyColorPicker implements a dialog that allows a user to choose a color +# from those already defined in Asymptote or a custom RGB color. +# +# +# Author: Orest Shardt +# Created: June 29, 2007 +# +############################################################################ + +from Tkinter import * +import tkColorChooser +asyColors = { "black":(0,0,0), + "white":(1,1,1), + "gray":(0.5,0.5,0.5), + "red":(1,0,0), + "green":(0,1,0), + "blue":(0,0,1), + "cmyk":(1,1,1), + "Cyan":(0,1,1), + "Magenta":(1,0,1), + "Yellow":(1,1,0), + "Black":(0,0,0), + "cyan":(0,1,1), + "magenta":(1,0,1), + "yellow":(1,1,0), + "palered":(1,0.75,0.75), + "palegreen":(0.75,1,0.75), + "paleblue":(0.75,0.75,1), + "palecyan":(0.75,1,1), + "palemagenta":(1,0.75,1), + "paleyellow":(1,1,0.75), + "palegray":(0.95,0.95,0.95), + "lightred":(1,0.5,0.5), + "lightgreen":(0.5,1,0.5), + "lightblue":(0.5,0.5,1), + "lightcyan":(0.5,1,1), + "lightmagenta":(1,0.5,1), + "lightyellow":(1,1,0.5), + "lightgray":(0.9,0.9,0.9), + "mediumred":(1,0.25,0.25), + "mediumgreen":(0.25,1,0.25), + "mediumblue":(0.25,0.25,1), + "mediumcyan":(0.25,1,1), + "mediummagenta":(1,0.25,1), + "mediumyellow":(1,1,0.25), + "mediumgray":(0.75,0.75,0.75), + "heavyred":(0.75,0,0), + "heavygreen":(0,0.75,0), + "heavyblue":(0,0,0.75), + "heavycyan":(0,0.75,0.75), + "heavymagenta":(0.75,0,0.75), + "lightolive":(0.75,0.75,0), + "heavygray":(0.25,0.25,0.25), + "deepred":(0.5,0,0), + "deepgreen":(0,0.5,0), + "deepblue":(0,0,0.5), + "deepcyan":(0,0.5,0.5), + "deepmagenta":(0.5,0,0.5), + "olive":(0.5,0.5,0), + "deepgray":(0.1,0.1,0.1), + "darkred":(0.25,0,0), + "darkgreen":(0,0.25,0), + "darkblue":(0,0,0.25), + "darkcyan":(0,0.25,0.25), + "darkmagenta":(0.25,0,0.25), + "darkolive":(0.25,0.25,0), + "darkgray":(0.05,0.05,0.05), + "orange":(1,0.5,0), + "fuchsia":(1,0,0.5), + "chartreuse":(0.5,1,0), + "springgreen":(0,1,0.5), + "purple":(0.5,0,1), + "royalblue":(0,0.5,1) + } +colorLayout = [['palered', + 'lightred', + 'mediumred', + 'red', + 'heavyred', + 'deepred', + 'darkred', + 'palegreen', + 'lightgreen', + 'mediumgreen', + 'green', + 'heavygreen', + 'deepgreen', + 'darkgreen', + 'paleblue', + 'lightblue', + 'mediumblue', + 'blue', + 'heavyblue', + 'deepblue', + 'darkblue'], + ['palecyan', + 'lightcyan', + 'heavycyan', + 'deepcyan', + 'darkcyan', + 'palemagenta', + 'lightmagenta', + 'mediummagenta', + 'magenta', + 'heavymagenta', + 'deepmagenta', + 'darkmagenta', + 'yellow', + 'lightyellow', + 'mediumyellow', + 'yellow', + 'lightolive', + 'olive', + 'darkolive', + 'palegray', + 'lightgray', + 'mediumgray', + 'gray', + 'heavygray', + 'deepgray', + 'darkgray'], + ['black', + 'white', + 'orange', + 'fuchsia', + 'chartreuse', + 'springgreen', + 'purple', + 'royalblue', + 'Cyan', + 'Magenta', + 'Yellow', + 'Black']] + +def makeRGBfromTkColor(tkColor): + """Convert a Tk color of the form #rrggbb to an asy rgb color""" + r = int('0x'+tkColor[1:3],16) + g = int('0x'+tkColor[3:5],16) + b = int('0x'+tkColor[5:7],16) + r /= 255.0 + g /= 255.0 + b /= 255.0 + return (r,g,b) + +def RGBreal255((r,g,b)): + """Convert an RGB color from 0-1 to 0-255""" + a,b,c = (256*r,256*g,256*b) + if a == 256: + a = 255 + if b == 256: + b = 255 + if c == 256: + c = 255 + return map(int,(a,b,c)) + +def RGB255hex((r,g,b)): + """Make a color in the form #rrggbb in hex from r,g,b in 0-255""" + rs,gs,bs = map(hex,(r,g,b)) + rs,gs,bs = rs[2:],gs[2:],bs[2:] + if len(rs) < 2: + rs += '0' + if len(gs) < 2: + gs += '0' + if len(bs) < 2: + bs += '0' + return '#'+rs+gs+bs + +class xasyColorDlg(Toplevel): + """A dialog for choosing an asymptote color. It displays the usual asy presets and allows custom rgb colors""" + def __init__(self,master=None,color=(0,0,0)): + Toplevel.__init__(self,master,width=500,height=500) + self.resizable(False,False) + self.parent = master + self.title("Color Picker") + self.transient(master) + self.focus_set() + self.wait_visibility() + self.grab_set() + self.color = self.oldColor = color + cwidth = 120 + rheight = 20 + self.pframe=Frame(self,bd=0) + self.pframe.rowconfigure(0,weight=1) + self.pframe.columnconfigure(0,weight=1) + Label(self.pframe,text="Color Presets").grid(row=0,column=0) + self.colScroll = Scrollbar(self.pframe,orient=VERTICAL) + self.colorList = Canvas(self.pframe, width=cwidth*len(colorLayout), scrollregion=(0,0,20+cwidth*len(colorLayout),20+rheight*max([len(i) for i in colorLayout])),yscrollcommand=self.colScroll.set,relief=FLAT) + self.colScroll.config(command=self.colorList.yview) + self.colScroll.grid(row=1,column=1,sticky=N+S) + self.colorList.grid(row=1,column=0,sticky=W) + ccount = 0 + for column in colorLayout: + rcount = 0 + for name in column: + self.colorList.create_rectangle(10+cwidth*ccount,10+rheight*rcount,cwidth*ccount+25,rheight*rcount+25,tags=(name,"preset"),fill=RGB255hex(RGBreal255(asyColors[name]))) + self.colorList.create_text(cwidth*ccount+30,10+rheight*rcount,text=name,anchor=NW,tags=(name,"preset"),fill="black",activefill=RGB255hex(RGBreal255(asyColors[name]))) + rcount += 1 + ccount += 1 + self.colorList.tag_bind("preset","<Button-1>",self.setColorEvt) + Button(self,text="Custom color...",command=self.getCustom).grid(row=2,column=0,sticky=W,padx=5,pady=5) + self.colDisp = Canvas(self,width=200,height=20,background=RGB255hex(RGBreal255(self.color)),relief=SUNKEN, bd=3) + self.colDisp.grid(row=2,column=1,columnspan=2) + self.rowconfigure(3,minsize=10) + self.columnconfigure(0,weight=1) + self.columnconfigure(1,weight=1) + self.columnconfigure(2,weight=1) + Button(self,text="OK",default=ACTIVE,command=self.destroy).grid(row=4,column=1,sticky=E+W,padx=5,pady=5) + Button(self,text="Cancel",command=self.cancel).grid(row=4,column=2,sticky=E+W,padx=5,pady=5) + self.pframe.grid(row=1,column=0,columnspan=3,padx=10,pady=10) + self.bind("<Return>",self.closeUp) + self.setColor(color) + def closeUp(self,event): + """Close the dialog forcibly""" + self.destroy() + def getCustom(self): + """Request a custom RGB color using a tkColorChooser""" + result=tkColorChooser.askcolor(initialcolor=RGB255hex(RGBreal255(self.color)),title="Custom Color",parent=self) + if result != (None,None): + self.setColor((result[0][0]/255.0,result[0][1]/255.0,result[0][2]/255.0)) + def cancel(self): + """Respond to the user pressing cancel""" + self.color = self.oldColor + self.destroy() + def setColor(self,color): + """Save the color and update the color display""" + self.color = color + self.colDisp.configure(background=RGB255hex(RGBreal255(self.color))) + def setColorEvt(self,event): + """Respond to the user clicking a color from the palette""" + self.setColor(asyColors[self.colorList.gettags(CURRENT)[0]]) + def getColor(self,initialColor=(0,0,0)): + """Use this method to prompt for a color. It returns the new color or the old color if the user cancelled the operation. + + e.g: + print xasyColorDlg(Tk()).getColor((1,1,0)) + """ + self.setColor(initialColor) + self.oldColor = initialColor + self.wait_window(self) + return self.color + +if __name__ == '__main__': + root = Tk() + Button(root,text="Pick Color",command=lambda:xasyColorDlg(root).getColor()).pack() + root.mainloop() diff --git a/Build/source/utils/asymptote/GUI/xasyExample.asy b/Build/source/utils/asymptote/GUI/xasyExample.asy new file mode 100644 index 00000000000..c251d45acb4 --- /dev/null +++ b/Build/source/utils/asymptote/GUI/xasyExample.asy @@ -0,0 +1,63 @@ +initXasyMode(); +// This file was generated by xasy. It may be edited manually, however, a strict +// syntax must be followed. It is advised that manually scripted items be added +// in the form of a script either by using xasy or by mimicking the format of an +// xasy-generated script item. +// Please consult the documentation or the examples provided for details. +xformStack.add(indexedTransform(0,(0, 0, 1, 0, 0, 1)), +indexedTransform(1,(0, 0, 1, 0, 0, 1)), +indexedTransform(2,(0, 0, 1, 0, 0, 1)), +indexedTransform(3,(0, 0, 1, 0, 0, 1)), +indexedTransform(4,(0, 0, 1, 0, 0, 1)), +indexedTransform(5,(0, 0, 1, 0, 0, 1)), +indexedTransform(6,(0, 0, 1, 0, 0, 1)), +indexedTransform(7,(0, 0, 1, 0, 0, 1)), +indexedTransform(8,(0, 0, 1, 0, 0, 1)), +indexedTransform(9,(0, 0, 1, 0, 0, 1)), +indexedTransform(10,(0, 0, 1, 0, 0, 1)), +indexedTransform(11,(0, 0, 1, 0, 0, 1)), +indexedTransform(12,(0, 0, 1, 0, 0, 1))); +startScript(); { +size(0,150); + +pen colour1=red; +pen colour2=green; + +pair z0=(0,0); +pair z1=(-1,0); +pair z2=(1,0); +real r=1.5; +guide c1=circle(z1,r); +guide c2=circle(z2,r); +fill(c1,colour1); +fill(c2,colour2); + +picture intersection; +fill(intersection,c1,colour1+colour2); +clip(intersection,c2); + +add(intersection); + +draw(c1); +draw(c2); + +label("$A$",z1); +label("$B$",z2); + +pair z=(0,-2); +real m=3; +margin BigMargin=Margin(0,m*dot(unit(z1-z),unit(z0-z))); + +draw(Label("$A\cap B$",0),conj(z)--z0,Arrow,BigMargin); +draw(Label("$A\cup B$",0),z--z0,Arrow,BigMargin); +draw(z--z1,Arrow,Margin(0,m)); +draw(z--z2,Arrow,Margin(0,m)); +} endScript(); + + +xformStack.push((-28.0, 7.0, 1, 0, 0, 1)); +label(Label("A Venn Diagram",(-79.0, 91.0),rgb(0,0,0)+0.5,align=SE)); + +// This is the end of the file +exitXasyMode(); + diff --git a/Build/source/utils/asymptote/GUI/xasyFile.py b/Build/source/utils/asymptote/GUI/xasyFile.py new file mode 100755 index 00000000000..890f1ad3eef --- /dev/null +++ b/Build/source/utils/asymptote/GUI/xasyFile.py @@ -0,0 +1,315 @@ +#!/usr/bin/env python +########################################################################### +# +# xasyFile implements the loading, parsing, and saving of an xasy file. +# +# +# Author: Orest Shardt +# Created: June 29, 2007 +# +############################################################################ + +from string import * +from xasy2asy import * +import re + +class xasyParseError(Exception): + """A parsing error""" + pass + +class xasyFileError(Exception): + """An i/o error or other error not related to parsing""" + pass + +def parseFile(inFile): + """Parse a file returning a list of xasyItems""" + lines = inFile.read() + lines = lines.splitlines() + #lines = [line for line in lines.splitlines() if not line.startswith("//")] + result = [] + if lines[0] != "initXasyMode();": + raise xasyFileError,"Invalid file format: First line must be \"initXasyMode();\"" + lines.pop(0) + lineCount = 2 + lineNum = len(lines) + while lineNum > 0: + line = lines[0] + lines.pop(0) + if not line.isspace() and len(line)>0: + try: + #print "Line %d: %s"%(lineCount,line), + lineResult = parseLine(line.strip(),lines) + except: + raise xasyParseError,"Parsing error: line %d in %s\n%s"%(lineCount,inFile.name,line) + + if lineResult != None: + result.append(lineResult) + #print "\tproduced: %s"%str(lineResult) + lineCount += lineNum-len(lines) + lineNum = len(lines) + return result + +transformPrefix = "xformStack" +scriptPrefix = "startScript(); {" +scriptSuffix = "} endScript();" +def extractScript(lines): + """Find the code belonging to a script item""" + theScript = "" + line = lines.pop(0) + level = 1 + while level > 0: + check = line.lstrip() + while check.endswith(scriptSuffix): + level -= 1 + line = line[:len(line)-len(scriptSuffix)] + check = line.lstrip() + if check.startswith(scriptPrefix): + level += 1 + theScript += line + "\n" + if level > 0: + line = lines.pop(0) + + global pendingTransformsD + ts = pendingTransformsD[:] + pendingTransformsD = [] + return xasyScript(None,script=theScript,transforms=ts[:]) + +pendingTransforms = [] +pendingTransformsD = [] +def addTransform(index,t,active=1): + """Place a transform in the list of transforms, expanding the list as needed""" + while len(pendingTransformsD) < index+1: + pendingTransformsD.append(identity()) + deleted = int(active==0) + pendingTransformsD[index]=asyTransform(t,deleted) + +def parseIndexedTransforms(args): + """Parse a list of indexedTransforms, adding them to the current list of transforms""" + global pendingTransformsD + pendingTransformsD = [] + args = args.replace("indexedTransform","") + false = 0 + tList = [eval(a) for a in ")?(".join(args.split("),(")).split("?")] + for a in tList: + addTransform(*a) + +def parseTransformExpression(line): + """Parse statements related to the xformStack + + Syntax: + xformStack.push(transform) + e.g.: xformStack.push((0,0,1,0,0,1)); //the identity + xformStack.add(indexedTransform(index,transform)[,...]) + e.g.: xformStack.add(indexedTransform(1,(0,0,1,0,0,1)); + """ + global pendingTransforms + stackCmd = line[len(transformPrefix)+1:line.find("(")] + if line[-2:] != ");": + raise xasyParseError,"Invalid syntax" + args = line[line.find("(")+1:-2] + if stackCmd == "push": + t = asyTransform(eval(args)) + pendingTransforms.append(t) + elif stackCmd == "add": + parseIndexedTransforms(args) + else: + raise xasyParseError,"Invalid transform stack command." + return None + +def parseLabel(line): + """Parse an asy Label statement, returning an xasyText item""" + if not (line.startswith("Label(") and line.endswith(",align=SE)")): + raise xasyParseError,"Invalid syntax" + args = line[6:-1] + loc2 = args.rfind(",align=SE") + loc1 = args.rfind(",",0,loc2-1) + loc = args.rfind(",(",0,loc1-1) + if loc < 2: + raise xasyParseError,"Invalid syntax" + text = args[1:loc-1] + location = eval(args[loc+1:args.find("),",loc)+1]) + pen = args[loc:loc2] + pen = pen[pen.find(",")+1:] + pen = pen[pen.find(",")+1:] + pen = pen[pen.find(",")+1:] + global pendingTransforms + return xasyText(text,location,parsePen(pen),pendingTransforms.pop()) + +def parseLabelCommand(line): + """Parse a label command returning an xasyText object + + Syntax: + label(Label(text,location,pen,align=SE)); + e.g.: label(Label("Hello world!",(0,0),rgb(0,0,0)+0.5,align=SE)); + """ + if line[-2:] != ");": + raise xasyParseError,"Invalid syntax" + arguments = line[6:-2] + return parseLabel(arguments) + +def parseDrawCommand(line): + """Parse a draw command returning an xasyShape object + + Syntax: + draw(path,pen); + e.g.: draw((0,0)..controls(0.33,0.33)and(0.66,0.66)..(1,1),rgb(1,0,1)+1.5); + """ + if line[-2:] != ");": + raise xasyParseError,"Invalid syntax" + args = line[5:-2] + loc = args.rfind(",rgb") + path = args[:loc] + pen = args[loc+1:] + global pendingTransforms + return xasyShape(parsePathExpression(path),parsePen(pen),pendingTransforms.pop()) + +def parseFillCommand(line): + """Parse a fill command returning an xasyFilledShape object + + Syntax: + fill(cyclic path,pen); + e.g.: fill((0,0)..controls(0.33,0.33)and(0.66,0.66)..(1,1)..controls(0.66,0)and(0.33,0)..cycle,rgb(1,0,1)+1.5); + """ + if line[-2:] != ");": + raise xasyParseError,"Invalid syntax" + args = line[5:-2] + loc = args.rfind(",rgb") + path = args[:loc] + pen = args[loc+1:] + global pendingTransforms + return xasyFilledShape(parsePathExpression(path),parsePen(pen),pendingTransforms.pop()) + +def parsePen(pen): + """Parse a pen expression returning an asyPen + + Syntax: + color+width[+options] + e.g.: rgb(0,0,0)+1.5+evenodd + e.g.: rgb(0,1,0)+1.23 + """ + try: + tokens = pen.split("+") + color = eval(tokens[0][3:]) + width = float(tokens[1]) + if len(tokens)>2: + options = "+".join(tokens[2:]) + else: + options = "" + return asyPen(color,width,options) + except: + raise xasyParseError,"Invalid pen" + +def parsePathExpression(expr): + """Parse an asy path returning an asyPath()""" + result = asyPath() + expr = "".join(expr.split()) + #print expr + if expr.find("controls") != -1: + #parse a path with control points + tokens = expr.split("..") + nodes = [a for a in tokens if not a.startswith("controls")] + for a in range(len(nodes)): + if nodes[a] != "cycle": + nodes[a] = eval(nodes[a]) + controls = [map(eval,a.replace("controls","").split("and")) for a in tokens if a.startswith("controls")] + result.initFromControls(nodes, controls) + else: + #parse a path without control points + tokens = re.split(r"(::|--|\.\.)",expr) + linkSet = re.findall("::|--|\.\.",expr) + nodeSet = [a for a in tokens if not re.match(r"::|--|\.\.",a)] + #print nodeSet + for a in range(len(nodeSet)): + if nodeSet[a] != "cycle": + nodeSet[a] = eval(nodeSet[a]) + #print nodeSet + result.initFromNodeList(nodeSet, linkSet) + return result + +def takeUntilSemicolon(line,lines): + """Read and concatenate lines until the collected lines end with a semicolon""" + data = line + while not data.endswith(";"): + newline = lines.pop(0) + data += newline + return data + +def parseLine(line,lines): + """Parse a line of the file""" + if len(line)==0 or line.isspace() or line.startswith("//"): + return None + elif line.startswith(scriptPrefix): + return extractScript(lines) + elif line.startswith(transformPrefix): + return parseTransformExpression(takeUntilSemicolon(line,lines)) + elif line.startswith("label("): + return parseLabelCommand(takeUntilSemicolon(line,lines)) + elif line.startswith("draw("): + return parseDrawCommand(takeUntilSemicolon(line,lines)) + elif line.startswith("fill("): + return parseFillCommand(takeUntilSemicolon(line,lines)) + elif line.startswith("exitXasyMode();"): + return None + raise Exception,"Could not parse the line" + +fileHeader = """initXasyMode(); +// This file was generated by xasy. It may be edited manually, however, a strict +// syntax must be followed. It is advised that manually scripted items be added +// in the form of a script either by using xasy or by mimicking the format of an +// xasy-generated script item. +// Please consult the documentation or the examples provided for details. +""" + +fileFooter = """// This is the end of the file +exitXasyMode(); + +""" + +def saveFile(file,xasyItems): + """Write a list of xasyItems to a file""" + file.write(fileHeader) + for item in xasyItems: + file.write(item.getCode()+"\n\n") + file.write(fileFooter) + +if __name__ == '__main__': + root = Tk() + try: + name = raw_input("enter file name (\"../../xasyTest.asy\"):") + if name == '': + name = "../../xasyTest.asy" + f = open(name,"rt") + except: + print "Could not open file." + asy.quit() + sys.exit(1) + + fileItems = [] + try: + fileItems = parseFile(f) + res = map(str,fileItems) + print "----------------------------------" + print "Objects in %s"%f.name + print "----------------------------------" + for a in res: + print a + print "----------------------------------" + print "successful parse" + f.close() + except: + f.close() + print "parse failed" + raise + + print "making a file" + f = open("testfile.asy","wt") + saveFile(f,fileItems) + f.close() + root.configure(width=500,height=500) + root.title("Results") + canv = Canvas(root,width=500,height=500) + canv.pack() + for i in fileItems[1].imageList: + canv.create_image(250+i.bbox[0],250-i.bbox[3],anchor = NW, image=i.image) + Button(root,image=i.image).pack(side=LEFT) + root.mainloop() diff --git a/Build/source/utils/asymptote/GUI/xasyGUIIcons.py b/Build/source/utils/asymptote/GUI/xasyGUIIcons.py new file mode 100755 index 00000000000..3afecfd349c --- /dev/null +++ b/Build/source/utils/asymptote/GUI/xasyGUIIcons.py @@ -0,0 +1,92 @@ +#!/usr/bin/env python +################################################################## +# This file stores the icons used by the xasy GUI +# +# About images and base64 +# +# Suppose you have image.gif and want to create a base64 +# string. This can be accomplished using: +# +# import base64 +# base64.encodestring(open("image.gif","rb").read()) +# +# The resulting output, including the enclosing single quotes, +# is the base64 encoding of the image and can be used in the +# dictionary below. +# +# +# Suppose you have a base64 string, b64str, and want to create +# an image. This can be accomplished using: +# +# import base64 +# open("image.gif","w").write(base64.decodestring(b64str)) +# +# +# Author: Orest Shardt +# Created: June 29, 2007 +# +################################################################## +import base64 +import os +#toolbar icon image data in base64 eliminates need to worry about files +#these are the base64 encodings of the content of the directory xasy3Imgs +iconB64 = { +'lower': 'R0lGODlhGAAYAPEBAAAAAP///8zMzAAAACH5BAEAAAIALAAAAAAYABgAAAItlI+py+0Po5yUgosz\nrrybK2giqADed6LHKCZm+p7xx2Zuqsqr95KcJpv9cJUCADs=\n', +'rotate': 'R0lGODlhGAAYAPAAAAAAAAAAACH5BAEAAAEALAAAAAAYABgAAAI7jI8JkO231mux1mkistL1zX0Q\ng2Fi6aGmurKp+8KKrJB0Zt+nzOQw6XPZgqjczuQ7eohKEDKoUYWIgQIAOw==\n', +'raise': 'R0lGODlhGAAYAPEBAAAAAP///8zMzAAAACH5BAEAAAIALAAAAAAYABgAAAIwlI+pywgND3ixzVvZ\nNDSn3nlKKH7fhaZmObKtk8Yh6dKlLcfC5vZ1jvIJh8SikVUAADs=\n', +'fillPoly': 'R0lGODlhGAAYAPEAAAAAAIOBgwAAAAAAACH5BAEAAAIALAAAAAAYABgAAAJGlI+py+0PEYgNBDCp\nDPxqY3UcRoViRzrmKWbLyqIMHI9vHbsbfuoHjfOBcrlbT0ATIo+gldKpMD1lL8vUo5oqS9vS5wsp\nAAA7\n', +'move': 'R0lGODlhGAAYAIABAAAAAP///yH5BAEAAAEALAAAAAAYABgAAAI4jI+py+0I3gNUNhqtwlVD7m3h\nkoVdUJ4MaKTYysVymbDoYcM4Tmv9eAO2cp6YEKUavY5BpvMZKgAAOw==\n', +'drawBezi': 'R0lGODlhGAAYAPEBAAAAAP///6usrQAAACH5BAEAAAIALAAAAAAYABgAAAI6lI+py+0AnYRUKhox\nsFvUFDXdM4LWUaKnEaorhqSX1noPmMquWJukzpr0YitRcfE5oobFpPIJjUoZBQA7\n', +'vertiMove': 'R0lGODlhGAAYAIABAAAAAP///yH5BAEAAAEALAAAAAAYABgAAAIsjI+py+0I3gNUNhqtwlVD7m3h\nko2QmZRooKKt+Y5xOFtc7dwrtrLd3gsKTQUAOw==\n', +'horizMove': 'R0lGODlhGAAYAIABAAAAAP///yH5BAEAAAEALAAAAAAYABgAAAIljI+py+0Po5y02oshAGu/7Skg\n143mSYpgGTYt8mbyTNf2jedWAQA7\n', +'fillEllip': 'R0lGODlhGAAYAPECAAAAAIOBg////6usrSH5BAEAAAMALAAAAAAYABgAAAJAnI+py+0PowS0gkmD\n3qE6wIXctYDi2SkmepLGyrYHHIcuXW93Lr+86BrgakHfrzjjIRGVFgVjWUqm1Kr1ijUUAAA7\n', +'text': 'R0lGODlhGAAYAIABAAAAAP///yH5BAEAAAEALAAAAAAYABgAAAI+jI+py+0Po5x0AgSu1SZvHnhS\nBnpio5Ukt2Idm3bysYrnddLwy+czH0rhFDkbTigj6UzKl68CjUqn1Ko1UAAAOw==\n', +'drawPoly': 'R0lGODlhGAAYAPAAAAAAAAAAACH5BAEAAAEALAAAAAAYABgAAAI4jI+py+0PEYhtgkmlzgFL/4DJ\nFULiVi4ns66smrUxrMj1fdqHR+60kfPdgCwLzbWTIU1LE+cJKQAAOw==\n', +'drawLines': 'R0lGODlhGAAYAPEBAAAAAP///6usrQAAACH5BAEAAAIALAAAAAAYABgAAAI3lI+py+0AnYRAPmoZ\njvlwX3Vh8j2XUIIWNXoZS3ZoO8soSK+4fRuYnQPyFEHhcHecFV+ppDNRAAA7\n', +'drawShape': 'R0lGODlhGAAYAPAAAAAAAAAAACH5BAEAAAEALAAAAAAYABgAAAI5jI+pywffIjQzIrCwdXnTplmh\nMoKmKIHVeZXp5cFcPH+0HbjbqKN17OoxgrTeKiOkPHjH3fIGjS4KADs=\n', +'drawEllip': 'R0lGODlhGAAYAPEBAAAAAP///6usrQAAACH5BAEAAAIALAAAAAAYABgAAAIylI+py+0PowS0gklX\ndRd29XmgdIQh+Z1TSSJpyxpqZMLqzOB4sgsbmKFZgrCi8YhMNgoAOw==\n', +'select': 'R0lGODlhGAAYAPIDAAAAAICAgMDAwP///6usrQAAAAAAAAAAACH5BAEAAAQALAAAAAAYABgAAANH\nSLrc/mvA6YCkGIiLIQhb54Gh2HwkZxKo4KoiSpam7L6rfdNZ4M+C3I+0Ush8wSLKCFIyPsnisyld\nAD7VabR6DWSt37BYmgAAOw==\n', +'fillShape': 'R0lGODlhGAAYAPEAAAAAAIOBgwAAAAAAACH5BAEAAAIALAAAAAAYABgAAAJHlI+pywff4gsUxgSo\nrhflzXXCB4YXWQIiCqpnubnLw8KyU8Omket77wvcgD4ZUTcMIj3KlOLYejY1N8/R0qChaCIrtgsO\nRwoAOw==\n', +'asy': 'R0lGODlhGAAYAIABAP8AAAAAACH5BAEKAAEALAIAAwAUABIAAAImjI+py+0AHINy0ZouNjBurmGd\nt40fFT4j2aydGqaBq8jvxH46UwAAOw==\n' +} + +def createGIF(key): + """Create a gif file from the data in the iconB64 list of icons""" + if key not in iconB64.keys(): + print "Error: %s not found in icon list."%key + print "Available icons:",iconB64.keys() + else: + print "Generating %s.gif"%key + open("%s.gif"%key,"w").write(base64.decodestring(iconB64[key])) + +def createGIFs(): + """Create the files for all the icons in iconB64""" + for name in iconB64.keys(): + createGIF(name) + +def createStrFromGif(gifFile): + """Create the base64 representation of a file""" + return base64.encodestring(gifFile.read()) + +if __name__=='__main__': + print "Testing the xasyGUIIcons module." + print "Generating all the GIFs:" + createGIFs() + print "Checking consistency of all icons in iconB64" + allpassed = True + for icon in iconB64.keys(): + print ("Checking %s"%icon), + if createStrFromGif(open("%s.gif"%icon,"rb")) == iconB64[icon]: + print "\tPassed." + else: + print "\tFailed." + allpassed= False + if allpassed: + print "All files succeeded." + s = raw_input("Delete generated files? (y/n)") + if s == "y": + for name in iconB64.keys(): + print "Deleting %s.gif"%name, + os.unlink(name+".gif") + print "\tdone" + print "Done" diff --git a/Build/source/utils/asymptote/GUI/xasyMainWin.py b/Build/source/utils/asymptote/GUI/xasyMainWin.py new file mode 100755 index 00000000000..865e057accb --- /dev/null +++ b/Build/source/utils/asymptote/GUI/xasyMainWin.py @@ -0,0 +1,1742 @@ +#!/usr/bin/env python +########################################################################### +# +# xasyMainWin implements the functionality of the GUI. It depends on +# xasy2asy for its interaction with Asymptote. +# +# +# Author: Orest Shardt +# Created: June 29, 2007 +# +########################################################################### + +import os +from string import * +import subprocess +import math +import copy + +from Tkinter import * +import tkMessageBox +import tkFileDialog +import tkSimpleDialog +import threading +import time + +from xasyVersion import xasyVersion +import xasyCodeEditor +from xasy2asy import * +import xasyFile +import xasyOptions +import xasyOptionsDialog +import CubicBezier +from xasyBezierEditor import xasyBezierEditor +from xasyGUIIcons import iconB64 +from xasyColorPicker import * + +from UndoRedoStack import * +from xasyActions import * + +import string + +try: + import ImageTk + import Image + PILAvailable = True +except: + PILAvailable = False + +class xasyMainWin: + def __init__(self,master,file=None,magnification=1.0): + self.opLock = threading.Lock() + self.parent = master + self.magnification = magnification + self.previousZoom = self.magnification + self.magList = [0.1,0.25,1.0/3,0.5,1,2,3,4,5,10] + self.bindGlobalEvents() + self.createWidgets() + self.resetGUI() + if sys.platform[:3] == "win": + site="http://effbot.org/downloads/PIL-1.1.6.win32-py2.5.exe" + if sys.version_info[0] != 2 or sys.version_info[1] != 5: + tkMessageBox.showerror("Failed Dependencies","Xasy requires Python 2.5 or 2.5.1. Please install http://www.python.org/ftp/python/2.5.1/python-2.5.1.msi") + self.parent.destroy() + sys.exit(1) + else: + site="http://effbot.org/downloads/Imaging-1.1.6.tar.gz after applying the patch http://asymptote.svn.sourceforge.net/viewvc/asymptote/trunk/asymptote/patches/TkAlpha-Imaging-1.1.6.patch" + if not PILAvailable: + tkMessageBox.showerror("Failed Dependencies","An error occurred loading the required PIL library. Please install "+site) + self.parent.destroy() + sys.exit(1) + if file != None: + self.loadFile(file) + self.parent.after(100,self.tickHandler) + + def testOrAcquireLock(self): + val = self.opLock.acquire(False) + if val: + self.closeDisplayLock() + return val + + def acquireLock(self): + self.closeDisplayLock() + self.opLock.acquire() + + def releaseLock(self): + self.opLock.release() + self.openDisplayLock() + + def tickHandler(self): + self.tickCount += 1 + self.mainCanvas.itemconfigure("outlineBox",dashoffset=self.tickCount%9) + self.parent.after(100,self.tickHandler) + + def closeDisplayLock(self): + self.status.config(text="Busy") + self.parent.update_idletasks() + + def openDisplayLock(self): + self.status.config(text="Ready") + + def bindGlobalEvents(self): + #global bindings + self.parent.bind_all("<Control-z>",lambda q:self.editUndoCmd())# z -> no shift + self.parent.bind_all("<Control-Z>",lambda q:self.editRedoCmd())# Z -> with shift + self.parent.bind_all("<Control-o>",lambda q:self.fileOpenCmd()) + self.parent.bind_all("<Control-n>",lambda q:self.fileNewCmd()) + self.parent.bind_all("<Control-s>",lambda q:self.fileSaveCmd()) + self.parent.bind_all("<Control-q>",lambda q:self.fileExitCmd()) + self.parent.bind_all("<F1>",lambda q:self.helpHelpCmd()) + + def unbindGlobalEvents(self): + #global bindings + self.parent.unbind("<Control-z>") + self.parent.unbind("<Control-Z>") + self.parent.unbind("<Control-o>") + self.parent.unbind("<Control-n>") + self.parent.unbind("<Control-s>") + self.parent.unbind("<Control-q>") + self.parent.unbind("<F1>") + + def createWidgets(self): + #first some configuration + self.parent.geometry("800x600") + self.parent.title("Xasy") + self.parent.resizable(True,True) + + #try to capture the closing of the window + #find a better way to do this since the widgets may + #already be destroyed when this is called + self.parent.protocol("WM_DELETE_WINDOW",self.canQuit) + + #the main menu + self.mainMenu = Menu(self.parent) + self.parent.config(menu=self.mainMenu) + + #the file menu + self.fileMenu = Menu(self.mainMenu,tearoff=0) + self.fileMenu.add_command(label="New",command=self.fileNewCmd,accelerator="Ctrl+N",underline=0) + self.fileMenu.add_command(label="Open",command=self.fileOpenCmd,accelerator="Ctrl+O",underline=0) + self.fileMenu.add_separator() + self.fileMenu.add_command(label="Save",command=self.fileSaveCmd,accelerator="Ctrl+S",underline=0) + self.fileMenu.add_command(label="Save As",command=self.fileSaveAsCmd,underline=5) + self.fileMenu.add_separator() + + #an export menu + self.exportMenu = Menu(self.fileMenu,tearoff=0) + self.exportMenu.add_command(label="EPS...",command=self.exportEPS,underline=0) + self.exportMenu.add_command(label="PDF...",command=self.exportPDF,underline=0) + self.exportMenu.add_command(label="GIF...",command=self.exportGIF,underline=0) + self.exportMenu.add_command(label="PNG...",command=self.exportPNG,underline=1) + self.fileMenu.add_cascade(label="Export",menu=self.exportMenu,underline=1) + self.fileMenu.add_separator() + + self.fileMenu.add_command(label="Quit",command=self.fileExitCmd,accelerator="Ctrl+Q",underline=0) + + self.mainMenu.add_cascade(label="File",menu=self.fileMenu,underline=0) + + #the edit menu + self.editMenu = Menu(self.mainMenu,tearoff=0) + self.editMenu.add_command(label="Undo",command=self.editUndoCmd,accelerator="Ctrl+Z",underline=0) + self.editMenu.add_command(label="Redo",command=self.editRedoCmd,accelerator="Shift+Ctrl+Z",underline=0) + self.mainMenu.add_cascade(label="Edit",menu=self.editMenu,underline=0) + + #the tools menu + self.toolsMenu = Menu(self.mainMenu,tearoff=0) + self.mainMenu.add_cascade(label="Tools",menu=self.toolsMenu,underline=0) + + #the options menu + self.optionsMenu = Menu(self.toolsMenu,tearoff=0) + self.toolsMenu.add_cascade(label="Options",menu=self.optionsMenu,underline=0) + self.optionsMenu.add_command(label="Edit...",command=self.editOptions,underline=0) + self.optionsMenu.add_command(label="Reset defaults",command=self.resetOptions,underline=6) + + #the help menu + self.helpMenu = Menu(self.mainMenu,tearoff=0) + self.helpMenu.add_command(label="Help",command=self.helpHelpCmd,state=DISABLED,accelerator="F1",underline=0) + self.helpMenu.add_command(label="Asymptote Documentation",command=self.helpAsyDocCmd,underline=10) + self.helpMenu.add_separator() + self.helpMenu.add_command(label="About xasy",command=self.helpAboutCmd,underline=0) + self.mainMenu.add_cascade(label="Help",menu=self.helpMenu,underline=0) + + #status bar + self.statusBar = Frame(self.parent,relief=FLAT) + + self.magVal = DoubleVar() + self.magVal.set(round(100*self.magnification,1)) + self.magVal.trace('w',self.zoomViewCmd) + zoomList = self.magList + if self.magnification not in zoomList: + zoomList.append(self.magnification) + zoomList.sort() + zoomList = [round(100*i,1) for i in zoomList] + self.zoomMenu = OptionMenu(self.statusBar,self.magVal,*zoomList) + self.zoomMenu.pack(side=RIGHT) + Label(self.statusBar,text="Zoom:",anchor=E,width=7).pack(side=RIGHT) + + self.coords = Label(self.statusBar,text="(0,0)",relief=SUNKEN,anchor=W) + self.coords.pack(side=RIGHT,anchor=S) + self.status = Label(self.statusBar,text="Ready",relief=SUNKEN,anchor=W) + self.status.pack(side=RIGHT,fill=X,expand=1,anchor=SW) + self.statusBar.pack(side=BOTTOM,fill=X) + + #toolbar for transformation, drawing, and adjustment commands + self.toolBar = Frame(self.parent,relief=FLAT,borderwidth=3) + + #let's load some images + self.toolIcons = {} + for x in iconB64.keys(): + self.toolIcons[x] = PhotoImage(data=iconB64[x]) + + self.transformLbl = Label(self.toolBar,text="",anchor=W) + self.transformLbl.grid(row=0,column=0,columnspan=2,sticky=W) + self.toolSelectButton = Button(self.toolBar,command=self.toolSelectCmd,image=self.toolIcons["select"]) + self.toolSelectButton.grid(row=1,column=0,sticky=N+S+E+W) + self.toolMoveButton = Button(self.toolBar,command=self.toolMoveCmd,image=self.toolIcons["move"]) + self.toolMoveButton.grid(row=2,column=0,sticky=N+S+E+W) + self.toolRotateButton = Button(self.toolBar,command=self.toolRotateCmd,image=self.toolIcons["rotate"]) + self.toolRotateButton.grid(row=2,column=1,sticky=N+S+E+W) + self.toolVertiMoveButton = Button(self.toolBar,command=self.toolVertiMoveCmd,image=self.toolIcons["vertiMove"]) + self.toolVertiMoveButton.grid(row=3,column=0,sticky=N+S+E+W) + self.toolHorizMoveButton = Button(self.toolBar,command=self.toolHorizMoveCmd,image=self.toolIcons["horizMove"]) + self.toolHorizMoveButton.grid(row=3,column=1,sticky=N+S+E+W) + + self.drawLbl = Label(self.toolBar,text="",anchor=W) + self.drawLbl.grid(row=4,column=0,columnspan=2,sticky=W) + self.toolDrawLinesButton = Button(self.toolBar,command=self.toolDrawLinesCmd,image=self.toolIcons["drawLines"]) + self.toolDrawLinesButton.grid(row=5,column=0,sticky=N+S+E+W) + self.toolDrawBeziButton = Button(self.toolBar,command=self.toolDrawBeziCmd,image=self.toolIcons["drawBezi"]) + self.toolDrawBeziButton.grid(row=5,column=1,sticky=N+S+E+W) + self.toolDrawPolyButton = Button(self.toolBar,command=self.toolDrawPolyCmd,image=self.toolIcons["drawPoly"]) + self.toolDrawPolyButton.grid(row=6,column=0,sticky=N+S+E+W) + self.toolFillPolyButton = Button(self.toolBar,command=self.toolFillPolyCmd,image=self.toolIcons["fillPoly"]) + self.toolFillPolyButton.grid(row=6,column=1,sticky=N+S+E+W) + self.toolDrawEllipButton = Button(self.toolBar,command=self.toolDrawEllipCmd,image=self.toolIcons["drawEllip"],state=DISABLED,relief=FLAT) + #self.toolDrawEllipButton.grid(row=7,column=0,sticky=N+S+E+W) + self.toolFillEllipButton = Button(self.toolBar,command=self.toolFillEllipCmd,image=self.toolIcons["fillEllip"],state=DISABLED,relief=FLAT) + #self.toolFillEllipButton.grid(row=7,column=1,sticky=N+S+E+W) + self.toolDrawShapeButton = Button(self.toolBar,command=self.toolDrawShapeCmd,image=self.toolIcons["drawShape"]) + self.toolDrawShapeButton.grid(row=8,column=0,sticky=N+S+E+W) + self.toolFillShapeButton = Button(self.toolBar,command=self.toolFillShapeCmd,image=self.toolIcons["fillShape"]) + self.toolFillShapeButton.grid(row=8,column=1,sticky=N+S+E+W) + self.toolTextButton = Button(self.toolBar,command=self.toolTextCmd,image=self.toolIcons["text"]) + self.toolTextButton.grid(row=9,column=0,sticky=N+S+E+W) + self.toolAsyButton = Button(self.toolBar,command=self.toolAsyCmd,image=self.toolIcons["asy"]) + self.toolAsyButton.grid(row=9,column=1,sticky=N+S+E+W) + + self.adjLbl = Label(self.toolBar,text="",anchor=W) + self.adjLbl.grid(row=10,column=0,columnspan=2,sticky=W) + self.toolRaiseButton = Button(self.toolBar,command=self.toolRaiseCmd,image=self.toolIcons["raise"]) + self.toolRaiseButton.grid(row=11,column=0,sticky=N+S+E+W) + self.toolLowerButton = Button(self.toolBar,command=self.toolLowerCmd,image=self.toolIcons["lower"]) + self.toolLowerButton.grid(row=11,column=1,sticky=N+S+E+W) + + self.toolBar.pack(side=LEFT,anchor=NW) + + #documentation for the tool bar buttons + self.toolDocs = { + self.toolSelectButton : "Click an item to select it. Control-Click will select/deselect additional items. Use mouse scroller (or Up/Down keys) to raise/lower highlighted items.", + self.toolMoveButton : "Drag a selected item.", + self.toolHorizMoveButton : "Drag a selected item. Only horizontal translation will be applied.", + self.toolVertiMoveButton : "Drag a selected item. Only vertical translation will be applied.", + self.toolRotateButton : "Drag a selected item to rotate it.", + self.toolDrawLinesButton : "Click to draw line segments. Double click to place last point.", + self.toolDrawBeziButton : "Click to place points. Double click to place last point.", + self.toolDrawPolyButton : "Click to place vertices. Double click to place last point.", + self.toolFillPolyButton : "Click to place vertices. Double click to place last point.", + self.toolDrawEllipButton : "(UNIMPLEMENTED)Click to place center. Move mouse to achieve correct shape and double click.", + self.toolFillEllipButton : "(UNIMPLEMENTED)Click to place center. Move mouse to achieve correct shape and double click.", + self.toolDrawShapeButton : "Click to place points. Double click to place last point.", + self.toolFillShapeButton : "Click to place points. Double click to place last point.", + self.toolTextButton : "Click location of top left label position and enter text in dialog.", + self.toolRaiseButton : "Raise selected items to top.", + self.toolLowerButton : "Lower selected items to bottom.", + self.toolAsyButton : "Insert/Edit Asymptote code." + } + + #Current pen settings + self.optionsBar = Frame(self.parent,height=100,relief=FLAT,borderwidth=3) + self.penDisp = Canvas(self.optionsBar,width=100,height=25,bg="white",relief=SUNKEN,borderwidth=3) + self.penDisp.grid(row=0,column=0,padx=3,pady=3) + self.penDisp.create_line(10,25,30,10,60,20,80,10,smooth=True,tags="penDisp") + self.penDisp.create_text(100,30,text="x1",tags="penMag",anchor=SE,font=("times","8")) + self.penColButton = Button(self.optionsBar,text="Color...",width=5,command=self.setPenColCmd,relief=FLAT) + self.penColButton.grid(row=0,column=1,padx=3,pady=3) + Label(self.optionsBar,text="Width",anchor=E).grid(row=0,column=2) + self.penWidthEntry = Entry(self.optionsBar,width=5) + self.penWidthEntry.bind("<KeyRelease>",self.penWidthChanged) + self.penWidthEntry.bind("<FocusOut>",self.applyPenWidthEvt) + self.penWidthEntry.bind("<Return>",self.applyPenWidthEvt) + self.penWidthEntry.grid(row=0,column=3) + Label(self.optionsBar,text="Options",anchor=E).grid(row=0,column=4) + self.penOptEntry = Entry(self.optionsBar) + self.penOptEntry.bind("<FocusOut>",self.applyPenOptEvt) + self.penOptEntry.bind("<Return>",self.applyPenOptEvt) + self.penOptEntry.grid(row=0,column=5) + self.optionsBar.pack(side=BOTTOM,anchor=NW) + + #a paned window for the canvas and propert explorer + self.windowPane = PanedWindow(self.parent) + + #a property explorer + self.propFrame = Frame(self.parent) + self.propFrame.rowconfigure(1,weight=1) + self.propFrame.columnconfigure(0,weight=1) + Label(self.propFrame,text="Item List").grid(row=0,column=0,columnspan=2) + self.itemScroll = Scrollbar(self.propFrame,orient=VERTICAL) + self.propList = Listbox(self.propFrame, yscrollcommand=self.itemScroll.set) + self.itemScroll.config(command=self.propList.yview) + self.itemScroll.grid(row=1,column=1,sticky=N+S) + self.propList.grid(row=1,column=0,sticky=N+S+E+W) + self.propList.bind("<Double-Button-1>",self.propSelect) + self.propList.bind("<Button-3>",self.itemPropMenuPopup) + + #the canvas's frame + self.canvFrame = Frame(self.parent,relief=FLAT,borderwidth=0) + self.canvFrame.rowconfigure(0,weight=1) + self.canvFrame.columnconfigure(0,weight=1) + self.canvVScroll = Scrollbar(self.canvFrame,orient=VERTICAL) + self.canvHScroll = Scrollbar(self.canvFrame,orient=HORIZONTAL) + self.canvHScroll.grid(row=1,column=0,sticky=E+W) + self.canvVScroll.grid(row=0,column=1,sticky=N+S) + + #add the frames to the window pane + self.windowPane.pack(side=RIGHT,fill=BOTH,expand=True) + self.windowPane.add(self.canvFrame) + self.windowPane.add(self.propFrame) + self.windowPane.paneconfigure(self.propFrame,minsize=50,sticky=N+S+E+W) + self.windowPane.bind("<Double-Button-1>",self.togglePaneEvt) + + #the highly important canvas! + self.mainCanvas = Canvas(self.canvFrame,relief=SUNKEN,background="white",borderwidth=3, + highlightthickness=0,closeenough=1.0,yscrollcommand=self.canvVScroll.set, + xscrollcommand=self.canvHScroll.set) + self.mainCanvas.grid(row=0,column=0,sticky=N+S+E+W) + + self.canvVScroll.config(command=self.mainCanvas.yview) + self.canvHScroll.config(command=self.mainCanvas.xview) + + self.mainCanvas.bind("<Motion>",self.canvMotion) + self.mainCanvas.bind("<Button-1>",self.canvLeftDown) + self.mainCanvas.bind("<Double-Button-1>",self.endDraw) + self.mainCanvas.bind("<ButtonRelease-1>",self.canvLeftUp) + self.mainCanvas.bind("<B1-Motion>",self.canvDrag) + + self.mainCanvas.bind("<Enter>",self.canvEnter) + self.mainCanvas.bind("<Leave>",self.canvLeave) + self.mainCanvas.bind("<Delete>",self.itemDelete) + #self.mainCanvas.bind("<Button-3>",self.canvRightDown) + #self.mainCanvas.bind("<ButtonRelease-3>",self.canvRightUp) + self.mainCanvas.bind("<Button-4>",self.itemRaise) + self.mainCanvas.bind("<Button-5>",self.itemLower) + self.mainCanvas.bind("<Up>",self.itemRaise) + self.mainCanvas.bind("<Down>",self.itemLower) + self.mainCanvas.bind("<Configure>",self.configEvt) + + def foregroundPenColor(self,hex): + hex = hex[1:] + rgb = max(hex[0:2], hex[2:4], hex[4:6]) + if(rgb >= "80"): + return "black" + else: + return "white" + + def resetGUI(self): + #set up the main window + self.filename = None + self.fileToOpen = None + self.retitle() + + #set up the paned window + self.paneVisible = True + + #setup the pen entries + self.pendingPenWidthChange = None + self.pendingPenOptChange = None + + #load one-time configs + xasyOptions.load() + self.tkPenColor = xasyOptions.options['defPenColor'] + self.penColor = makeRGBfromTkColor(self.tkPenColor) + self.penColButton.config(activebackground=self.tkPenColor, + activeforeground=self.foregroundPenColor(self.tkPenColor)) + self.penWidth = xasyOptions.options['defPenWidth'] + self.penWidthEntry.select_range(0,END) + self.penWidthEntry.delete(0,END) + self.penWidthEntry.insert(END,str(self.penWidth)) + self.penOptions = xasyOptions.options['defPenOptions'] + self.penOptEntry.select_range(0,END) + self.penOptEntry.delete(0,END) + self.penOptEntry.insert(END,str(self.penOptions)) + self.showCurrentPen() + + #load modifiable configs + self.applyOptions() + + #set up editing + self.editor = None + + #set up drawing + self.pathInProgress = asyPath() + self.currentIDTag = -1 + self.inDrawingMode = False + self.freeMouseDown = True + self.dragSelecting = False + self.itemsBeingRotated = [] + self.inRotatingMode = False + + #set up the toolbar + try: + self.updateSelectedButton(self.toolSelectButton) + except: + self.selectedButton = self.toolSelectButton + self.updateSelectedButton(self.toolSelectButton) + + #set up the canvas + self.mainCanvas.delete(ALL) + self.mainCanvas.create_rectangle(0,0,0,0,tags="outlineBox",width=0,outline="#801111",dash=(3,6)) + self.backColor = "white" #in future, load this from an options file. Or, should this really be an option? + self.mainCanvas.configure(background=self.backColor) + + #set up the xasy item list + self.fileItems = [] + self.propList.delete(0,END) + self.updateCanvasSize() + + #setup timer + self.tickCount = 0 + + #setup undo/redo! + self.undoRedoStack = actionStack() + self.amDragging = False + + def retitle(self): + if self.filename == None: + self.parent.title("Xasy - New File") + else: + name = os.path.abspath(self.filename) + name = os.path.basename(name) + self.parent.title("Xasy - %s"%name) + + def applyOptions(self): + self.gridcolor = xasyOptions.options['gridColor'] + self.tickcolor = xasyOptions.options['tickColor'] + self.axiscolor = xasyOptions.options['axesColor'] + self.gridVisible = xasyOptions.options['showGrid'] + self.gridxspace = xasyOptions.options['gridX'] + self.gridyspace = xasyOptions.options['gridY'] + self.axesVisible = xasyOptions.options['showAxes'] + self.axisxspace = xasyOptions.options['axisX'] + self.axisyspace = xasyOptions.options['axisY'] + self.updateCanvasSize() + #test the asyProcess + startQuickAsy() + if not quickAsyRunning(): + if tkMessageBox.askyesno("Xasy Error","Asymptote could not be executed.\r\nTry to find Asymptote automatically?"): + xasyOptions.setAsyPathFromWindowsRegistry() + xasyOptions.save() + startQuickAsy() + while not quickAsyRunning(): + if tkMessageBox.askyesno("Xasy Error","Asymptote could not be executed.\r\nEdit settings?"): + xasyOptionsDialog.xasyOptionsDlg(self.parent) + xasyOptions.save() + startQuickAsy() + else: + self.parent.destroy() + sys.exit(1) + + def drawGrid(self): + self.mainCanvas.delete("grid") + if not self.gridVisible: + return + left,top,right,bottom = map(int,self.mainCanvas.cget("scrollregion").split()) + gridyspace = int(self.magnification*self.gridyspace) + gridxspace = int(self.magnification*self.gridxspace) + if gridxspace >= 3 and gridyspace >= 3: + for i in range(0,right,gridxspace): + self.mainCanvas.create_line(i,top,i,bottom,tags=("grid","vertical"),fill=self.gridcolor) + for i in range(-gridxspace,left,-gridxspace): + self.mainCanvas.create_line(i,top,i,bottom,tags=("grid","vertical"),fill=self.gridcolor) + for i in range(-gridyspace,top,-gridyspace): + self.mainCanvas.create_line(left,i,right,i,tags=("grid","horizontal"),fill=self.gridcolor) + for i in range(0,bottom,gridyspace): + self.mainCanvas.create_line(left,i,right,i,tags=("grid","horizontal"),fill=self.gridcolor) + self.mainCanvas.tag_lower("grid") + + def drawAxes(self): + self.mainCanvas.delete("axes") + if not self.axesVisible: + return + left,top,right,bottom = map(int,self.mainCanvas.cget("scrollregion").split()) + self.mainCanvas.create_line(0,top,0,bottom,tags=("axes","yaxis"),fill=self.axiscolor) + self.mainCanvas.create_line(left,0,right,0,tags=("axes","xaxis"),fill=self.axiscolor) + axisxspace = int(self.magnification*self.axisxspace) + axisyspace = int(self.magnification*self.axisyspace) + if axisxspace >= 3 and axisyspace >= 3: + for i in range(axisxspace,right,axisxspace): + self.mainCanvas.create_line(i,-5,i,5,tags=("axes","xaxis-ticks"),fill=self.tickcolor) + for i in range(-axisxspace,left,-axisxspace): + self.mainCanvas.create_line(i,-5,i,5,tags=("axes","xaxis-ticks"),fill=self.tickcolor) + for i in range(-axisyspace,top,-axisyspace): + self.mainCanvas.create_line(-5,i,5,i,tags=("axes","yaxis-ticks"),fill=self.tickcolor) + for i in range(axisyspace,bottom,axisyspace): + self.mainCanvas.create_line(-5,i,5,i,tags=("axes","yaxis-ticks"),fill=self.tickcolor) + self.mainCanvas.tag_lower("axes") + + def updateCanvasSize(self,left=-200,top=-200,right=200,bottom=200): + self.parent.update_idletasks() + bbox = self.mainCanvas.bbox("drawn || image || node || precontrol || postcontrol") + if bbox == None: + bbox = (0,0,0,0) + #(topleft, bottomright) + left = min(bbox[0],left) + top = min(bbox[1],top) + right = max(bbox[2],right) + bottom = max(bbox[3],bottom) + w,h = self.mainCanvas.winfo_width(),self.mainCanvas.winfo_height() + if right-left < w: + extraw = w-(right-left) + right += extraw/2 + left -= extraw/2 + if bottom-top < h: + extrah = h-(bottom-top) + bottom += extrah/2 + top -= extrah/2 + self.mainCanvas.config(scrollregion=(left,top,right,bottom)) + #self.mainCanvas.xview(MOVETO,float(split(self.mainCanvas["scrollregion"])[0])) + #self.mainCanvas.yview(MOVETO,float(split(self.mainCanvas["scrollregion"])[1])) + #self.mainCanvas.xview(MOVETO,(left+right)/2) + #self.mainCanvas.yview(MOVETO,(top+bottom)/2) + self.drawAxes() + self.drawGrid() + + def bindEvents(self,tagorID): + if tagorID == None: + return + self.mainCanvas.tag_bind(tagorID,"<Control-Button-1>",self.itemToggleSelect) + self.mainCanvas.tag_bind(tagorID,"<Button-1>",self.itemSelect) + self.mainCanvas.tag_bind(tagorID,"<ButtonRelease-1>",self.itemMouseUp) + self.mainCanvas.tag_bind(tagorID,"<Double-Button-1>",self.itemEditEvt) + self.mainCanvas.tag_bind(tagorID,"<B1-Motion>",self.itemDrag) + self.mainCanvas.tag_bind(tagorID,"<Delete>",self.itemDelete) + self.mainCanvas.tag_bind(tagorID,"<Enter>",self.itemHighlight) + self.mainCanvas.tag_bind(tagorID,"<Button-3>",self.itemCanvasMenuPopup) + + def bindItemEvents(self,item): + if item == None: + return + if isinstance(item,xasyScript) or isinstance(item,xasyText): + for image in item.imageList: + self.bindEvents(image.IDTag) + else: + self.bindEvents(item.IDTag) + + def canQuit(self,force=False): + #print "Quitting" + if not force and not self.testOrAcquireLock(): + return + try: + self.releaseLock() + except: + pass + if self.undoRedoStack.changesMade(): + result = tkMessageBox._show("xasy","File has been modified.\nSave changes?",icon=tkMessageBox.QUESTION,type=tkMessageBox.YESNOCANCEL) + if str(result) == tkMessageBox.CANCEL: + return + elif result == tkMessageBox.YES: + self.fileSaveCmd() + try: + os.rmdir(getAsyTempDir()) + except: + pass + self.parent.destroy() + + def openFile(self,name): + if(not self.testOrAcquireLock()): + return + self.releaseLock() #release the lock for loadFile + self.resetGUI() + self.loadFile(name) + + def loadFile(self,name): + self.status.config(text="Loading "+name) + self.filename = os.path.abspath(name) + startQuickAsy() + self.retitle() + try: + try: + f = open(self.filename,'rt') + except: + if self.filename[-4:] == ".asy": + raise + else: + f = open(self.filename+".asy",'rt') + self.filename += ".asy" + self.retitle() + self.fileItems = xasyFile.parseFile(f) + f.close() + except IOError: + tkMessageBox.showerror("File Opening Failed.","File could not be opened.") + self.fileItems = [] + except: + self.fileItems = [] + self.autoMakeScript = True + if self.autoMakeScript or tkMessageBox.askyesno("Error Opening File", "File was not recognized as an xasy file.\nLoad as a script item?"): + try: + item = xasyScript(self.mainCanvas) + f.seek(0) + item.setScript(f.read()) + self.addItemToFile(item) + except: + tkMessageBox.showerror("File Opening Failed.","Could not load as a script item.") + self.fileItems = [] + self.populateCanvasWithItems() + self.populatePropertyList() + self.updateCanvasSize() + + def populateCanvasWithItems(self): + if(not self.testOrAcquireLock()): + return + self.mainCanvas.delete("drawn || image") + self.itemCount = 0 + for item in self.fileItems: + item.drawOnCanvas(self.mainCanvas,self.magnification,forceAddition=True) + self.bindItemEvents(item) + self.releaseLock() + + def propListCountItem(self,item): + plist = self.propList.get(0,END) + count = 1 + for text in plist: + if text.startswith(item): + count += 1 + return count + + def describeItem(self,item): + if isinstance(item,xasyScript): + return "Code Module "+str(self.propListCountItem("Code Module")) + elif isinstance(item,xasyText): + return "Text Label "+str(self.propListCountItem("Text Label")) + elif isinstance(item,xasyFilledShape): + return "Filled Shape "+str(self.propListCountItem("Filled Shape")) + elif isinstance(item,xasyShape): + return "Outline "+str(self.propListCountItem("Outline")) + else: + return "If this happened, the program is corrupt!" + + def populatePropertyList(self): + self.propList.delete(0,END) + for item in self.fileItems: + self.propList.insert(0,self.describeItem(item)) + + def saveFile(self,name): + if(not self.testOrAcquireLock()): + return + f = open(name,"wt") + xasyFile.saveFile(f,self.fileItems) + f.close() + self.undoRedoStack.setCommitLevel() + self.retitle() + self.releaseLock() + + #menu commands + def fileNewCmd(self): + if(not self.testOrAcquireLock()): + return + self.releaseLock() + #print "Create New File" + if self.undoRedoStack.changesMade(): + result = tkMessageBox._show("xasy","File has been modified.\nSave changes?",icon=tkMessageBox.QUESTION,type=tkMessageBox.YESNOCANCEL) + if str(result) == tkMessageBox.CANCEL: + return + elif result == tkMessageBox.YES: + self.fileSaveCmd() + self.resetGUI() + + def fileOpenCmd(self): + if(not self.testOrAcquireLock()): + return + self.releaseLock() + #print "Open a file" + if self.undoRedoStack.changesMade(): + result = tkMessageBox._show("xasy","File has been modified.\nSave changes?",icon=tkMessageBox.QUESTION,type=tkMessageBox.YESNOCANCEL) + if str(result) == tkMessageBox.CANCEL: + return + elif result == tkMessageBox.YES: + self.fileSaveCmd() + filename=tkFileDialog.askopenfilename(filetypes=[("asy files","*.asy"),("All files","*")],title="Open File",parent=self.parent) + if type(filename) != type((0,)) and filename != None and filename != '': + self.filename = filename + self.openFile(self.filename) + + def fileSaveCmd(self): + #print "Save current file" + if(not self.testOrAcquireLock()): + return + self.releaseLock() + if self.filename == None: + filename=tkFileDialog.asksaveasfilename(defaultextension=".asy",filetypes=[("asy files","*.asy")],initialfile="newDrawing.asy",parent=self.parent,title="Save File") + if type(filename) != type((0,)) and filename != None and filename != '': + self.filename = filename + if self.filename != None: + self.saveFile(self.filename) + + def fileSaveAsCmd(self): + if(not self.testOrAcquireLock()): + return + self.releaseLock() + #print "Save current file as" + filename=tkFileDialog.asksaveasfilename(defaultextension=".asy",filetypes=[("asy files","*.asy")],initialfile="newDrawing.asy",parent=self.parent,title="Save File") + if type(filename) != type((0,)) and filename != None and filename != '': + self.filename = filename + self.saveFile(self.filename) + + #export the file + def exportEPS(self): + self.exportFile(self.filename,"eps") + + def exportPDF(self): + self.exportFile(self.filename,"pdf") + + def exportGIF(self): + self.exportFile(self.filename,"gif") + + def exportPNG(self): + self.exportFile(self.filename,"png") + + def exportFile(self,inFile, outFormat): + if(not self.testOrAcquireLock()): + return + self.releaseLock() + if inFile == None: + if tkMessageBox.askyesno("xasy","File has not been saved.\nSave?"): + self.fileSaveAsCmd() + inFile = self.filename + else: + return + elif self.undoRedoStack.changesMade(): + choice = tkMessageBox._show("xasy","File has been modified.\nOnly saved changes can be exported.\nDo you want to save changes?",icon=tkMessageBox.QUESTION,type=tkMessageBox.YESNOCANCEL) + choice = str(choice) + if choice != tkMessageBox.YES: + return + else: + self.fileSaveCmd() + name = os.path.splitext(os.path.basename(self.filename))[0]+'.'+outFormat + outfilename = tkFileDialog.asksaveasfilename(defaultextension = '.'+outFormat,filetypes=[(outFormat+" files","*."+outFormat)],initialfile=name,parent=self.parent,title="Choose output file") + if type(outfilename)==type((0,)) or not outfilename or outfilename == '': + return + fullname = os.path.abspath(outfilename) + outName = os.path.basename(outfilename) + dirname = os.path.dirname(fullname) + command = xasyOptions.options['asyPath']+" -f %s -o %s %s"%(outFormat,fullname,inFile) + saver = subprocess.Popen(split(command),stdin=PIPE,stdout=PIPE,stderr=PIPE) + saver.wait() + if saver.returncode != 0: + tkMessageBox.showerror("Export Error","Export Error:\n"+saver.stdout.read()+saver.stderr.read()) + self.status.config(text="Error exporting file") + else: + self.status.config(text="File exported successfully") + + def fileExitCmd(self): + #print "Exit xasy" + self.canQuit() + + def editUndoCmd(self): + if not self.editor == None: + return + if(not self.testOrAcquireLock()): + return + self.undoOperation() + self.releaseLock() + + def editRedoCmd(self): + if not self.editor == None: + return + if(not self.testOrAcquireLock()): + return + self.redoOperation() + self.releaseLock() + + def helpHelpCmd(self): + print "Get help on xasy" + + def helpAsyDocCmd(self): + #print "Open documentation about Asymptote" + asyExecute("\nhelp;\n") + + def helpAboutCmd(self): + tkMessageBox.showinfo("About xasy","A graphical interface for Asymptote "+xasyVersion) + + def updateSelectedButton(self,newB): + if(not self.testOrAcquireLock()): + return + self.releaseLock() + #disable switching modes during an incomplete drawing operation + if self.inDrawingMode: + return + self.selectedButton.config(relief = RAISED) + if newB == self.toolSelectButton or self.selectedButton == self.toolSelectButton: + self.mainCanvas.delete("highlightBox") + if self.editor != None: + self.editor.endEdit() + if self.editor.modified: + self.undoRedoStack.add(editDrawnItemAction(self,self.itemBeingEdited,copy.deepcopy(self.editor.shape),self.fileItems.index(self.editor.shape))) + if newB not in (self.toolSelectButton,self.toolMoveButton,self.toolHorizMoveButton,self.toolVertiMoveButton,self.toolRotateButton): + self.clearSelection() + self.selectedButton = newB + self.selectedButton.config(relief = SUNKEN) + self.status.config(text=self.toolDocs[newB]) + + #toolbar commands + def toolSelectCmd(self): + self.updateSelectedButton(self.toolSelectButton) + def toolMoveCmd(self): + self.updateSelectedButton(self.toolMoveButton) + def toolRotateCmd(self): + self.updateSelectedButton(self.toolRotateButton) + def toolVertiMoveCmd(self): + self.updateSelectedButton(self.toolVertiMoveButton) + def toolHorizMoveCmd(self): + self.updateSelectedButton(self.toolHorizMoveButton) + def toolDrawLinesCmd(self): + self.updateSelectedButton(self.toolDrawLinesButton) + def toolDrawBeziCmd(self): + self.updateSelectedButton(self.toolDrawBeziButton) + def toolDrawPolyCmd(self): + self.updateSelectedButton(self.toolDrawPolyButton) + def toolFillPolyCmd(self): + self.updateSelectedButton(self.toolFillPolyButton) + def toolDrawEllipCmd(self): + self.updateSelectedButton(self.toolDrawEllipButton) + def toolFillEllipCmd(self): + self.updateSelectedButton(self.toolFillEllipButton) + def toolDrawShapeCmd(self): + self.updateSelectedButton(self.toolDrawShapeButton) + def toolFillShapeCmd(self): + self.updateSelectedButton(self.toolFillShapeButton) + def toolTextCmd(self): + self.updateSelectedButton(self.toolTextButton) + def toolAsyCmd(self): + # ignore the command if we are too busy to process it + if not self.testOrAcquireLock(): + return + self.updateSelectedButton(self.toolSelectButton) + self.clearSelection() + self.clearHighlight() + self.unbindGlobalEvents() + try: + self.getNewText("// enter your code here") + except Exception, e: + tkMessageBox.showerror('xasy Error',e.message) + else: + self.addItemToFile(xasyScript(self.mainCanvas)) + text = self.newText + self.undoRedoStack.add(addScriptAction(self,self.fileItems[-1])) + self.fileItems[-1].setScript(text) + self.fileItems[-1].drawOnCanvas(self.mainCanvas,self.magnification) + self.bindItemEvents(self.fileItems[-1]) + self.bindGlobalEvents() + self.releaseLock() + def toolRaiseCmd(self): + if(not self.testOrAcquireLock()): + return + self.releaseLock() + if not self.inDrawingMode and self.editor == None: + itemList = [] + indexList = [] + for ID in self.mainCanvas.find_withtag("selectedItem"): + item = self.findItem(ID) + if item not in itemList: + itemList.append(item) + indexList.append(self.fileItems.index(item)) + self.raiseSomething(item) + self.undoRedoStack.add(itemRaiseAction(self,itemList,indexList)) + def toolLowerCmd(self): + if(not self.testOrAcquireLock()): + return + self.releaseLock() + if not self.inDrawingMode and self.editor == None: + itemList = [] + indexList = [] + for ID in self.mainCanvas.find_withtag("selectedItem"): + item = self.findItem(ID) + if item not in itemList: + itemList.append(item) + indexList.append(self.fileItems.index(item)) + self.lowerSomething(item) + self.undoRedoStack.add(itemLowerAction(self,itemList,indexList)) + def itemRaise(self,event): + self.mainCanvas.tag_raise(CURRENT) + def itemLower(self,event): + self.mainCanvas.tag_lower(CURRENT) + + #options bar commands + def setPenColCmd(self): + if not self.testOrAcquireLock(): + return + old = self.penColor + self.penColor = xasyColorDlg(self.parent).getColor(self.penColor) + if self.penColor != old: + self.tkPenColor = RGB255hex(RGBreal255(self.penColor)) + self.penColButton.config(activebackground=self.tkPenColor, + activeforeground=self.foregroundPenColor(self.tkPenColor)) + self.showCurrentPen() + self.releaseLock() + + def clearSelection(self): + self.hideSelectionBox() + self.mainCanvas.dtag("selectedItem","selectedItem") + + def hideSelectionBox(self): + self.mainCanvas.itemconfigure("outlineBox",width=1,outline=self.backColor) + self.mainCanvas.tag_lower("outlineBox") + self.mainCanvas.coords("outlineBox",self.mainCanvas.bbox(ALL)) + + def showSelectionBox(self): + self.mainCanvas.itemconfigure("outlineBox",width=2,outline="#801111") + self.mainCanvas.tag_raise("outlineBox") + + def setSelection(self,what): + self.mainCanvas.addtag_withtag("selectedItem",what) + self.updateSelection() + if self.selectedButton == self.toolSelectButton and len(self.mainCanvas.find_withtag("selectedItem")) > 0: + self.updateSelectedButton(self.toolMoveButton) + + def unSelect(self,what): + self.mainCanvas.dtag(what,"selectedItem") + self.updateSelection() + + def updateSelection(self): + self.clearHighlight() + theBbox = self.mainCanvas.bbox("selectedItem") + if theBbox != None: + theBbox = (theBbox[0]-2,theBbox[1]-2,theBbox[2]+2,theBbox[3]+2) + self.mainCanvas.coords("outlineBox",theBbox) + self.showSelectionBox() + else: + self.clearSelection() + + #event handlers + def updateZoom(self): + self.zoomMenu.config(state=DISABLED) + self.magnification = self.magVal.get()/100.0 + if self.magnification != self.previousZoom: + self.populateCanvasWithItems() + self.updateCanvasSize() + self.updateSelection() + self.drawAxes() + self.drawGrid() + self.previousZoom = self.magnification + self.zoomMenu.config(state=NORMAL) + + def zoomViewCmd(self,*args): + magnification = self.magVal.get()/100.0 + self.updateZoom(); + + def selectItem(self,item): + self.clearSelection() + if isinstance(item,xasyScript) or isinstance(item,xasyText): + for image in item.imageList: + self.setSelection(image.IDTag) + else: + self.setSelection(item.IDTag) + + def propSelect(self,event): + items = map(int, self.propList.curselection()) + if len(items)>0: + try: + self.selectItem(self.fileItems[len(self.fileItems)-items[0]-1]) + except: + raise + + def findItem(self,ID): + for item in self.fileItems: + if isinstance(item,xasyScript) or isinstance(item,xasyText): + for image in item.imageList: + if image.IDTag == ID: + return item + else: + if item.IDTag == ID: + return item + raise Exception,"Illegal operation: Item with matching ID could not be found." + + def findItemImageIndex(self,item,ID): + count = 0 + for image in item.imageList: + if image.IDTag == ID: + return count + else: + count += 1 + raise Exception,"Illegal operation: Image with matching ID could not be found." + return None + + def raiseSomething(self,item,force=False): + if self.fileItems[-1] != item or force: + index = len(self.fileItems)-self.fileItems.index(item)-1 + text = self.propList.get(index) + self.propList.delete(index) + self.propList.insert(0,text) + for i in range(self.fileItems.index(item),len(self.fileItems)-1): + self.fileItems[i] = self.fileItems[i+1] + self.fileItems[-1] = item + if isinstance(item,xasyScript) or isinstance(item,xasyText): + for im in item.imageList: + if im.IDTag != None: + self.mainCanvas.tag_raise(im.IDTag) + else: + if item.IDTag != None: + self.mainCanvas.tag_raise(item.IDTag) + + def lowerSomething(self,item): + if self.fileItems[0] != item: + index = len(self.fileItems)-self.fileItems.index(item)-1 + text = self.propList.get(index) + self.propList.delete(index) + self.propList.insert(END,text) + indices = range(self.fileItems.index(item)) + indices.reverse() + for i in indices: + self.fileItems[i+1] = self.fileItems[i] + self.fileItems[0] = item + if isinstance(item,xasyScript) or isinstance(item,xasyText): + item.imageList.reverse() + for im in item.imageList: + if im.IDTag != None: + self.mainCanvas.tag_lower(im.IDTag) + item.imageList.reverse() + else: + if item.IDTag != None: + self.mainCanvas.tag_lower(item.IDTag) + self.mainCanvas.tag_lower("axes || grid") + + def translateSomething(self,ID,translation,specificItem=None,specificIndex=None): + transform = asyTransform((translation[0],translation[1],1,0,0,1)) + if ID == -1: + item = specificItem + else: + item = self.findItem(ID) + if isinstance(item,xasyText) or isinstance(item,xasyScript): + if ID == -1: + index = specificIndex + else: + index = self.findItemImageIndex(item,ID) + try: + original = item.transform[index] + except: + original = identity() + item.transform[index] = transform*original + bbox = item.imageList[index].originalImage.bbox + item.imageList[index].originalImage.bbox = bbox[0]+translation[0],bbox[1]+translation[1],bbox[2]+translation[0],bbox[3]+translation[1] + else: + item.transform = [transform*item.transform[0]] + + def makeRotationMatrix(self,theta,origin): + rotMat = (math.cos(theta),-math.sin(theta),math.sin(theta),math.cos(theta)) + shift = asyTransform((0,0,1-rotMat[0],-rotMat[1],-rotMat[2],1-rotMat[3]))*origin + return asyTransform((shift[0],shift[1],rotMat[0],rotMat[1],rotMat[2],rotMat[3])) + + def rotateSomething(self,ID,theta,origin,specificItem=None,specificIndex=None): + #print "Rotating by",theta*180.0/math.pi,"around",origin + rotMat = self.makeRotationMatrix(theta,(origin[0]/self.magnification,origin[1]/self.magnification)) + #print rotMat + if ID == -1: + item = specificItem + else: + item = self.findItem(ID) + if isinstance(item,xasyText) or isinstance(item,xasyScript): + #transform the image + if ID == -1: + index = specificIndex + else: + index = self.findItemImageIndex(item,ID) + try: + original = item.transform[index] + except: + original = identity() + oldBbox = item.imageList[index].originalImage.bbox + oldBbox = (oldBbox[0],-oldBbox[1],oldBbox[2],-oldBbox[3]) + item.transform[index] = rotMat*item.transform[index] + item.transform[index] = rotMat*original + item.imageList[index].originalImage.theta += theta + item.imageList[index].image = item.imageList[index].originalImage.rotate(item.imageList[index].originalImage.theta*180.0/math.pi,expand=True,resample=Image.BICUBIC) + item.imageList[index].itk = ImageTk.PhotoImage(item.imageList[index].image) + self.mainCanvas.itemconfigure(ID,image=item.imageList[index].itk) + #the image has been rotated in place + #now, compensate for any resizing and shift to the correct location + # + # p0 --- p1 p1 + # | | ---> / \ + # p2 --- p3 p0 p3 + # \ / + # p2 + # + rotMat2 = self.makeRotationMatrix(item.imageList[index].originalImage.theta,origin) + p0 = rotMat2*(oldBbox[0],-oldBbox[3])#switch to usual coordinates + p1 = rotMat2*(oldBbox[2],-oldBbox[3]) + p2 = rotMat2*(oldBbox[0],-oldBbox[1]) + p3 = rotMat2*(oldBbox[2],-oldBbox[1]) + newTopLeft = (min(p0[0],p1[0],p2[0],p3[0]),-max(p0[1],p1[1],p2[1],p3[1]))#switch back to screen coords + shift = (newTopLeft[0]-oldBbox[0],newTopLeft[1]-oldBbox[3]) + #print theta*180.0/math.pi,origin,oldBbox,newTopLeft,shift + #print item.imageList[index].originalImage.size + #print item.imageList[index].image.size + #print + self.mainCanvas.coords(ID,oldBbox[0]+shift[0],oldBbox[3]+shift[1]) + else: + #transform each point of the object + xform = rotMat*item.transform[0] + item.transform = [identity()] + for i in range(len(item.path.nodeSet)): + if item.path.nodeSet[i] != 'cycle': + item.path.nodeSet[i] = xform*item.path.nodeSet[i] + for i in range(len(item.path.controlSet)): + item.path.controlSet[i][0] = xform*item.path.controlSet[i][0] + item.path.controlSet[i][1] = xform*item.path.controlSet[i][1] + item.drawOnCanvas(self.mainCanvas,self.magnification) + + def deleteItem(self,item): + if isinstance(item,xasyScript) or isinstance(item,xasyText): + if isinstance(item,xasyScript): + self.undoRedoStack.add(deleteScriptAction(self,item,self.fileItems.index(item))) + else: + self.undoRedoStack.add(deleteLabelAction(self,item,self.fileItems.index(item))) + for image in item.imageList: + self.mainCanvas.delete(image.IDTag) + else: + if isinstance(item,xasyDrawnItem): + self.undoRedoStack.add(deleteDrawnItemAction(self,item,self.fileItems.index(item))) + self.mainCanvas.delete(item.IDTag) + self.fileItems.remove(item) + self.populatePropertyList() + self.clearSelection() + + def deleteSomething(self,ID): + self.clearSelection() + self.clearHighlight() + if self.editor != None: + self.editor.endEdit() + if self.editor.modified: + self.undoRedoStack.add(editDrawnItemAction(self,self.itemBeingEdited,copy.deepcopy(self.editor.shape),self.fileItems.index(self.editor.shape))) + item = self.findItem(ID) + #save an event on the undoredo stack + if isinstance(item,xasyScript): + index = self.findItemImageIndex(item,ID) + item.transform[index].deleted = True + else: + if isinstance(item,xasyText): + self.undoRedoStack.add(deleteLabelAction(self,item,self.fileItems.index(item))) + elif isinstance(item,xasyDrawnItem): + self.undoRedoStack.add(deleteDrawnItemAction(self,item,self.fileItems.index(item))) + self.fileItems.remove(item) + self.mainCanvas.delete(ID) + self.populatePropertyList() + + def scriptEditThread(self,oldText): + try: + self.newText = xasyCodeEditor.getText(oldText) + except: + self.newText = -1 + + def getNewText(self,oldText): + editThread = threading.Thread(target=self.scriptEditThread,args=(oldText,)) + editThread.start() + while editThread.isAlive(): + time.sleep(0.05) + self.parent.update() + editThread.join() + if type(self.newText)==type(-1): + self.newText = '' + raise Exception('Error launching external editor. Please check xasy options.') + + def itemEdit(self,item): + # are we too busy? + if not self.testOrAcquireLock(): + return + self.updateSelectedButton(self.toolSelectButton) + if isinstance(item,xasyScript): + self.unbindGlobalEvents() + oldText = item.script + try: + self.getNewText(oldText) + except Exception,e: + tkMessageBox.showerror('xasy Error',e.message) + else: + if self.newText != oldText: + self.undoRedoStack.add(editScriptAction(self,item,self.newText,oldText)) + item.setScript(self.newText) + item.drawOnCanvas(self.mainCanvas,self.magnification) + self.bindItemEvents(item) + self.bindGlobalEvents() + elif isinstance(item,xasyText): + theText = tkSimpleDialog.askstring(title="Xasy - Text",prompt="Enter text to display:",initialvalue=item.label.text,parent=self.parent) + if theText != None and theText != "": + self.undoRedoStack.add(editLabelTextAction(self,item,theText,item.label.text)) + item.label.text = theText + item.drawOnCanvas(self.mainCanvas,self.magnification) + self.bindItemEvents(item) + elif isinstance(item,xasyShape): + self.clearSelection() + self.clearHighlight() + self.itemBeingEdited = copy.deepcopy(item) + self.editor = xasyBezierEditor(self,item,self.mainCanvas) + self.updateSelection() + self.releaseLock() + + def itemEditEvt(self,event): + if not self.inDrawingMode: + ID = self.mainCanvas.find_withtag(CURRENT)[0] + item = self.findItem(ID) + self.itemEdit(item) + + def itemDrag(self,event): + x0,y0 = self.mainCanvas.canvasx(event.x),self.mainCanvas.canvasy(event.y) + x = x0/self.magnification + y = y0/self.magnification + if self.selectedButton not in [self.toolMoveButton,self.toolVertiMoveButton,self.toolHorizMoveButton]: + return + if "selectedItem" in self.mainCanvas.gettags(CURRENT): + self.amDragging = True + for ID in self.mainCanvas.find_withtag("selectedItem"): + transform = identity() + if self.selectedButton == self.toolMoveButton: + translation = (x0-self.dragStartx,-(y0-self.dragStarty)) + elif self.selectedButton == self.toolVertiMoveButton: + translation = (0,-(y0-self.dragStarty)) + elif self.selectedButton == self.toolHorizMoveButton: + translation = (x0-self.dragStartx,0) + self.translateSomething(ID,(translation[0]/self.magnification,translation[1]/self.magnification)) + self.mainCanvas.move(ID,translation[0],-translation[1]) + self.updateSelection() + self.updateCanvasSize() + self.distanceDragged = (self.distanceDragged[0]+translation[0],self.distanceDragged[1]-translation[1]) + self.dragStartx,self.dragStarty = x0,y0 + + def itemMouseUp(self,event): + self.freeMouseDown = True + if self.amDragging: + IDList = self.mainCanvas.find_withtag("selectedItem") + itemList = [] + indexList = [] + for ID in IDList: + item = self.findItem(ID) + if item not in itemList: + itemList.append(item) + try: + indexList.append([self.findItemImageIndex(item,ID)]) + except: + indexList.append([None]) + else: + indexList[itemList.index(item)].append(self.findItemImageIndex(item,ID)) + self.undoRedoStack.add(translationAction(self,itemList,indexList,(self.distanceDragged[0],-self.distanceDragged[1]))) + self.amDragging = False + + def itemSelect(self,event): + x0,y0 = self.mainCanvas.canvasx(event.x),self.mainCanvas.canvasy(event.y) + x = x0/self.magnification + y = y0/self.magnification + self.dragStartx,self.dragStarty = x0,y0 + self.distanceDragged = (0,0) + if self.selectedButton in [self.toolSelectButton,self.toolMoveButton,self.toolVertiMoveButton,self.toolHorizMoveButton,self.toolRotateButton]: + self.freeMouseDown = False + if self.selectedButton == self.toolSelectButton or (len(self.mainCanvas.find_withtag("selectedItem"))<=1 and self.selectedButton in [self.toolMoveButton,self.toolVertiMoveButton,self.toolHorizMoveButton,self.toolRotateButton]): + self.clearSelection() + self.setSelection(CURRENT) + + def itemToggleSelect(self,event): + #print "control click" + x0,y0 = self.mainCanvas.canvasx(event.x),self.mainCanvas.canvasy(event.y) + x = x0/self.magnification + y = y0/self.magnification + if self.selectedButton in [self.toolSelectButton,self.toolMoveButton,self.toolVertiMoveButton,self.toolHorizMoveButton,self.toolRotateButton]: + self.freeMouseDown = False + self.dragStartx,self.dragStarty = x0,y0 + if "selectedItem" in self.mainCanvas.gettags(CURRENT): + self.unSelect(CURRENT) + else: + self.setSelection(CURRENT) + + def itemDelete(self,event): + if(not self.testOrAcquireLock()): + return + itemList = [] + self.undoRedoStack.add(endActionGroup) + for ID in self.mainCanvas.find_withtag("selectedItem"): + item = self.findItem(ID) + if isinstance(item,xasyScript): + index = self.findItemImageIndex(item,ID) + if item not in itemList: + itemList.append([item,[index],[item.transform[index]]]) + else: + x = None + for i in itemList: + if i[0] == item: + x = i + x[1].append(index) + x[2].append(item.transform[index]) + self.deleteSomething(ID) + for entry in itemList: + self.undoRedoStack.add(deleteScriptItemAction(self,entry[0],entry[1],entry[2])) + self.undoRedoStack.add(beginActionGroup) + self.clearSelection() + self.releaseLock() + + def itemMotion(self,event): + pass + + def itemHighlight(self,event): + if self.selectedButton in [self.toolSelectButton] and self.editor == None: + box = self.mainCanvas.bbox(CURRENT) + box = (box[0]-2,box[1]-2,box[2]+2,box[3]+2) + if len(self.mainCanvas.find_withtag("highlightBox"))==0: + self.mainCanvas.create_rectangle(box,tags="highlightBox",width=2,outline="red") + else: + self.mainCanvas.tag_raise("highlightBox") + self.mainCanvas.coords("highlightBox",*box) + self.mainCanvas.tag_bind("highlightBox","<Leave>",self.itemUnHighlight) + + def itemUnHighlight(self,event): + self.clearHighlight() + + def clearHighlight(self): + self.mainCanvas.delete("highlightBox") + + def itemLeftDown(self,event): + pass + + def itemLeftUp(self,event): + pass + + def itemRightDown(self,event): + pass + + def itemRightUp(self,event): + pass + + def canvMotion(self,event): + self.coords.config( + text="(%.3f,%.3f)"%(self.mainCanvas.canvasx(event.x)/self.magnification,-self.mainCanvas.canvasy(event.y)/self.magnification) + ) + + def addItemToFile(self,item): + self.fileItems.append(item) + self.propList.insert(0,self.describeItem(item)) + self.updateCanvasSize() + + def startDraw(self,event): + # don't start if we can't finish + if not self.testOrAcquireLock() and not self.inDrawingMode: + return + x0,y0 = self.mainCanvas.canvasx(event.x),self.mainCanvas.canvasy(event.y) + x = x0/self.magnification + y = y0/self.magnification + #self.mainCanvas.create_oval(x,y,x,y,width=5) + if self.selectedButton == self.toolDrawEllipButton: + pass + elif self.selectedButton == self.toolFillEllipButton: + pass + elif self.selectedButton == self.toolTextButton: + theText = tkSimpleDialog.askstring(title="Xasy - Text",prompt="Enter text to display:",initialvalue="",parent=self.parent) + if theText != None and theText != "": + theItem = xasyText(theText,(x,-y),asyPen(self.penColor,self.penWidth,self.penOptions)) + theItem.drawOnCanvas(self.mainCanvas,self.magnification) + self.bindItemEvents(theItem) + self.addItemToFile(theItem) + self.undoRedoStack.add(addLabelAction(self,theItem)) + self.releaseLock() + self.updateSelectedButton(self.toolSelectButton) + elif self.selectedButton in [self.toolDrawLinesButton,self.toolDrawBeziButton,self.toolDrawPolyButton,self.toolDrawShapeButton,self.toolFillPolyButton,self.toolFillShapeButton]: + self.inDrawingMode = True + try: + if len(self.itemBeingDrawn.path.nodeSet) == 0: + raise Exception + else: + if self.selectedButton in [self.toolDrawLinesButton,self.toolDrawPolyButton,self.toolFillPolyButton]: + self.itemBeingDrawn.appendPoint((x,-y),'--') + else:#drawBezi,drawShape,fillShape + self.itemBeingDrawn.appendPoint((x,-y),'..') + except: + path = asyPath() + if self.selectedButton == self.toolDrawLinesButton: + path.initFromNodeList([(x,-y),(x,-y)],['--']) + elif self.selectedButton == self.toolDrawBeziButton: + path.initFromNodeList([(x,-y),(x,-y)],['..']) + elif self.selectedButton == self.toolDrawPolyButton or self.selectedButton == self.toolFillPolyButton: + path.initFromNodeList([(x,-y),(x,-y),'cycle'],['--','--']) + elif self.selectedButton == self.toolDrawShapeButton or self.selectedButton == self.toolFillShapeButton: + path.initFromNodeList([(x,-y),(x,-y),'cycle'],['..','..']) + if self.selectedButton in [self.toolDrawLinesButton,self.toolDrawBeziButton,self.toolDrawPolyButton,self.toolDrawShapeButton]: + self.itemBeingDrawn = xasyShape(path,pen=asyPen(self.penColor,self.penWidth,self.penOptions)) + else: + if self.penOptions.find("fillrule") != -1 or self.penOptions.find("evenodd") != -1 or self.penOptions.find("zerowinding") != -1: + options = self.penOptions + else: + options = "evenodd" + self.itemBeingDrawn = xasyFilledShape(path,pen=asyPen(self.penColor,self.penWidth,options)) + self.itemBeingDrawn.drawOnCanvas(self.mainCanvas,self.magnification) + self.bindItemEvents(self.itemBeingDrawn) + self.mainCanvas.bind("<Motion>",self.extendDraw) + + def extendDraw(self,event): + x0,y0 = self.mainCanvas.canvasx(event.x),self.mainCanvas.canvasy(event.y) + x = x0/self.magnification + y = y0/self.magnification + tags = self.mainCanvas.gettags("itemBeingDrawn") + self.itemBeingDrawn.setLastPoint((x,-y)) + self.itemBeingDrawn.drawOnCanvas(self.mainCanvas,self.magnification) + self.canvMotion(event) + + def endDraw(self,event): + if not self.inDrawingMode or self.itemBeingDrawn == None: + return + x0,y0 = self.mainCanvas.canvasx(event.x),self.mainCanvas.canvasy(event.y) + x = x0/self.magnification + y = y0/self.magnification + #if self.selectedButton in [self.toolDrawLinesButton,self.toolDrawPolyButton,self.toolFillPolyButton]: + #self.itemBeingDrawn.appendPoint((x,-y),'--') + #else: + #self.itemBeingDrawn.appendPoint((x,-y),'..') + + #only needed for certain key bindings when startDraw is triggered right before an endDraw + #e.g.: single click: startDraw, double click: endDraw + self.itemBeingDrawn.removeLastPoint() + self.itemBeingDrawn.setLastPoint((x,-y)) + self.itemBeingDrawn.drawOnCanvas(self.mainCanvas,self.magnification) + self.addItemToFile(self.itemBeingDrawn) + self.undoRedoStack.add(addDrawnItemAction(self,self.itemBeingDrawn)) + self.itemBeingDrawn = None + self.mainCanvas.dtag("itemBeingDrawn","itemBeingDrawn") + self.mainCanvas.bind("<Motion>",self.canvMotion) + self.inDrawingMode = False + self.releaseLock() + + def canvLeftDown(self,event): + #print "Left Mouse Down" + self.selectDragStart = (self.mainCanvas.canvasx(event.x),self.mainCanvas.canvasy(event.y)) + theBbox = self.mainCanvas.bbox("selectedItem") + if theBbox != None: + self.selectBboxMidpoint = (theBbox[0]+theBbox[2])/2.0,-(theBbox[1]+theBbox[3])/2.0 + if self.freeMouseDown and self.editor != None: + self.editor.endEdit() + if self.editor.modified: + self.undoRedoStack.add(editDrawnItemAction(self,self.itemBeingEdited,copy.deepcopy(self.editor.shape),self.fileItems.index(self.editor.shape))) + self.editor = None + elif self.selectedButton in (self.toolSelectButton,self.toolMoveButton,self.toolVertiMoveButton,self.toolHorizMoveButton,self.toolRotateButton): + if self.freeMouseDown: + self.clearSelection() + self.dragSelecting = False + else: + self.startDraw(event) + + def canvLeftUp(self,event): + #print "Left Mouse Up" + # if we're busy, ignore it + if not self.testOrAcquireLock(): + return + self.freeMouseDown = True + if self.inRotatingMode: + for item in self.itemsBeingRotated: + item.drawOnCanvas(self.mainCanvas,self.magnification) + self.bindItemEvents(item) + self.updateSelection() + self.itemsBeingRotated = [] + self.inRotatingMode = False + if self.dragSelecting: + self.hideSelectionBox() + self.dragSelecting = False + self.mainCanvas.addtag_enclosed("enclosed",self.selectDragStart[0],self.selectDragStart[1],self.mainCanvas.canvasx(event.x),self.mainCanvas.canvasy(event.y)) + for item in self.mainCanvas.find_withtag("enclosed"): + tags = self.mainCanvas.gettags(item) + if "drawn" not in tags and "image" not in tags: + self.mainCanvas.dtag(item,"enclosed") + self.mainCanvas.addtag_withtag("selectedItem","enclosed") + self.mainCanvas.dtag("enclosed","enclosed") + if self.selectedButton == self.toolSelectButton and len(self.mainCanvas.find_withtag("selectedItem")) > 0: + self.updateSelectedButton(self.toolMoveButton) + self.updateSelection() + self.releaseLock() + + def canvDrag(self,event): + x0,y0 = self.mainCanvas.canvasx(event.x),self.mainCanvas.canvasy(event.y) + x = x0/self.magnification + y = y0/self.magnification + if self.selectedButton == self.toolSelectButton and self.editor == None: + self.mainCanvas.coords("outlineBox",self.selectDragStart[0],self.selectDragStart[1],x0,y0) + self.showSelectionBox() + self.dragSelecting = True + elif self.selectedButton == self.toolRotateButton and self.editor == None: + bbox = self.mainCanvas.bbox("selectedItem") + if bbox != None: + p1 = self.selectDragStart[0]-self.selectBboxMidpoint[0],-self.selectDragStart[1]-self.selectBboxMidpoint[1] + mp1 = math.sqrt(p1[0]**2+p1[1]**2) + p2 = x0-self.selectBboxMidpoint[0],-y0-self.selectBboxMidpoint[1] + mp2 = math.sqrt(p2[0]**2+p2[1]**2) + if mp1 != 0: + t1 = math.acos(p1[0]/mp1) + if p1[1] < 0: + t1 *= -1 + else: + t1 = 0 + if mp2 != 0: + t2 = math.acos(p2[0]/mp2) + if p2[1] < 0: + t2 *= -1 + else: + t2 = 0 + theta = t2-t1 + self.selectDragStart = x0,y0 + self.itemsBeingRotated = [] + for ID in self.mainCanvas.find_withtag("selectedItem"): + self.rotateSomething(ID,theta,self.selectBboxMidpoint) + item = self.findItem(ID) + if not item in self.itemsBeingRotated: + self.itemsBeingRotated.append(item) + self.updateSelection() + self.updateCanvasSize() + if not self.inRotatingMode: + self.currentRotationAngle = theta + IDList = self.mainCanvas.find_withtag("selectedItem") + itemList = [] + indexList = [] + for ID in IDList: + item = self.findItem(ID) + if item not in itemList: + itemList.append(item) + try: + indexList.append([self.findItemImageIndex(item,ID)]) + except: + indexList.append([None]) + else: + indexList[itemList.index(item)].append(self.findItemImageIndex(item,ID)) + self.undoRedoStack.add(rotationAction(self,itemList,indexList,self.currentRotationAngle,self.selectBboxMidpoint)) + self.inRotatingMode = True + else: + self.currentRotationAngle += theta + self.undoRedoStack.undoStack[-1].angle = self.currentRotationAngle + + def canvEnter(self,event): + self.freeMouseDown = True + event.widget.focus_set() + + def canvLeave(self,event): + self.freeMouseDown = False + + def canvRightDown(self,event): + pass + #print "Right Mouse Down" + + def canvRightUp(self,event): + pass + #print "Right Mouse Up" + + def configEvt(self,event): + self.updateCanvasSize() + self.sizePane() + + def sizePane(self): + width = self.windowPane.winfo_width()-10 + cwidth = min(int(0.87*self.windowPane.winfo_width()),width-75) + if self.paneVisible: + self.windowPane.paneconfigure(self.canvFrame,minsize=cwidth) + else: + self.windowPane.paneconfigure(self.canvFrame,minsize=width) + self.windowPane.paneconfigure(self.propFrame,minsize=75) + + def togglePaneEvt(self,event): + self.paneVisible = not self.paneVisible + self.sizePane() + + def popupDelete(self): + self.deleteItem(self.itemPopupMenu.item) + + def popupEdit(self): + self.itemEdit(self.itemPopupMenu.item) + + def popupViewCode(self): + tkMessageBox.showinfo("Item Code",self.itemPopupMenu.item.getCode()) + + def popupClearTransform(self): + self.undoRedoStack.add(clearItemTransformsAction(self,self.itemPopupMenu.item,copy.deepcopy(self.itemPopupMenu.item.transform))) + if isinstance(self.itemPopupMenu.item,xasyScript) or isinstance(self.itemPopupMenu.item,xasyText): + for i in range(len(self.itemPopupMenu.item.transform)): + self.itemPopupMenu.item.transform[i] = identity() + else: + self.itemPopupMenu.item.transform = [identity()] + self.popupRedrawItem() + + def popupRedrawItem(self): + if not self.testOrAcquireLock(): + return + self.clearSelection() + self.clearHighlight() + self.itemPopupMenu.item.drawOnCanvas(self.mainCanvas,self.magnification) + self.bindItemEvents(self.itemPopupMenu.item) + self.updateCanvasSize() + self.releaseLock() + + def hidePopupMenu(self): + try: + self.itemPopupMenu.unpost() + except: + pass + + def itemMenuPopup(self,parent,item,x,y): + self.hidePopupMenu() + self.itemPopupMenu = Menu(parent,tearoff=False) + self.itemPopupMenu.add_command(label="Edit",command=self.popupEdit) + self.itemPopupMenu.add_command(label="Clear Transforms",command=self.popupClearTransform) + self.itemPopupMenu.add_command(label="Redraw",command=self.popupRedrawItem) + self.itemPopupMenu.add_command(label="View code",command=self.popupViewCode) + self.itemPopupMenu.add_separator() + self.itemPopupMenu.add_command(label="Delete",command=self.popupDelete) + self.itemPopupMenu.item = item + #self.itemPopupMenu.bind("<Leave>",lambda a:self.itemPopupMenu.unpost()) + #self.itemPopupMenu.bind("<FocusOut>",lambda a:self.itemPopupMenu.unpost()) + self.itemPopupMenu.post(x,y) + + def itemPropMenuPopup(self,event): + try: + item = self.fileItems[len(self.fileItems)-int(self.propList.curselection()[0])-1] + self.itemMenuPopup(self.propList,item,event.x_root,event.y_root) + except: + pass + + def itemCanvasMenuPopup(self,event): + if self.selectedButton in (self.toolSelectButton,self.toolMoveButton,self.toolVertiMoveButton,self.toolHorizMoveButton,self.toolRotateButton): + try: + item = self.findItem(self.mainCanvas.find_withtag(CURRENT)[0]) + except: + item = None + if item != None: + self.itemMenuPopup(self.mainCanvas,item,event.x_root,event.y_root) + + def editOptions(self): + if(not self.testOrAcquireLock()): + return + self.releaseLock() + xasyOptionsDialog.xasyOptionsDlg(self.parent) + self.applyOptions() + + def resetOptions(self): + xasyOptions.setDefaults() + self.applyOptions() + + def applyPenWidth(self): + self.pendingPenWidthChange = None + if self.validatePenWidth(): + old = self.penWidth + self.penWidth = float(self.penWidthEntry.get()) + if old != self.penWidth: + self.showCurrentPen() + + def validatePenWidth(self): + text = self.penWidthEntry.get() + try: + width = float(text) + if width <= 0: + return False + else: + return True + except: + return False + + def showCurrentPen(self): + mag = 1 + width = self.penWidth + while width > 10: + width /= 2 + mag *= 2 + self.penDisp.itemconfigure("penDisp",width=width,fill=self.tkPenColor) + self.penDisp.itemconfigure("penMag",text="x%d"%mag) + #apply the new pen to any selected items + IDs = self.mainCanvas.find_withtag("selectedItem") + madeAChange = False + for ID in IDs: + item = self.findItem(ID) + if not isinstance(item,xasyScript): + if not madeAChange: + self.undoRedoStack.add(endActionGroup) + madeAChange = True + if isinstance(item,xasyText): + temp = item.label.pen + item.label.pen = asyPen(self.penColor,self.penWidth,self.penOptions) + item.drawOnCanvas(self.mainCanvas,self.magnification) + self.bindItemEvents(item) + self.setSelection(item.imageList[0].IDTag) + self.undoRedoStack.add(editLabelPenAction(self,temp,asyPen(self.penColor,self.penWidth,self.penOptions),self.fileItems.index(item))) + else: + temp = copy.deepcopy(item) + item.pen = asyPen(self.penColor,self.penWidth,self.penOptions) + item.drawOnCanvas(self.mainCanvas,self.magnification) + self.undoRedoStack.add(editDrawnItemAction(self,temp,copy.deepcopy(item),self.fileItems.index(item))) + if madeAChange: + self.undoRedoStack.add(beginActionGroup) + + def applyPenWidthEvt(self,event): + if not self.testOrAcquireLock(): + return + self.applyPenWidth() + self.releaseLock() + + def penWidthChanged(self,event): + if self.pendingPenWidthChange is not None: + self.penWidthEntry.after_cancel(self.pendingPenWidthChange) + self.pendingPenWidthChange = self.penWidthEntry.after(1000,self.applyPenWidth) + + def applyPenOptEvt(self,event): + if not self.testOrAcquireLock(): + return + self.applyPenOpt() + self.releaseLock() + + def validatePenOpt(self): + try: + penTest = asyPen(self.penColor,self.penWidth,self.penOptEntry.get()) + return True + except: + self.penOptEntry.select_range(0,END) + self.penOptEntry.delete(0,END) + self.penOptEntry.insert(END,"Invalid Pen Options") + self.penOptEntry.after(5000,self.clearInvalidOptEntry) + self.penOptions = "" + return False + + def clearInvalidOptEntry(self): + self.penOptEntry.select_range(0,END) + self.penOptEntry.delete(0,END) + + def applyPenOpt(self): + if self.validatePenOpt(): + old = self.penOptions + self.penOptions = self.penOptEntry.get() + if old != self.penOptions: + self.showCurrentPen() + + def undoOperation(self): + self.undoRedoStack.undo() + + def redoOperation(self): + self.undoRedoStack.redo() + + def resetStacking(self): + for item in self.fileItems: + self.raiseSomething(item,force=True) diff --git a/Build/source/utils/asymptote/GUI/xasyOptions.py b/Build/source/utils/asymptote/GUI/xasyOptions.py new file mode 100755 index 00000000000..4406687cbf4 --- /dev/null +++ b/Build/source/utils/asymptote/GUI/xasyOptions.py @@ -0,0 +1,126 @@ +#!/usr/bin/env python +########################################################################### +# +# xasyOptions provides a mechanism for storing and restoring a user's +# preferences. +# +# +# Author: Orest Shardt +# Created: June 29, 2007 +# +########################################################################### + +import pickle +import sys,os +import errno + +defaultOptions = { + 'asyPath':'asy', + 'showDebug':False, + 'showGrid':False, + 'gridX':10, + 'gridY':10, + 'gridColor':'#eeeeee', + 'showAxes':True, + 'axisX':10, + 'axisY':10, + 'axesColor':'#cccccc', + 'tickColor':'#eeeeee', + 'defPenOptions':'', + 'defPenColor':'#000000', + 'defPenWidth':1.0, + 'externalEditor':'' + } + +if sys.platform[:3] == "win": + defaultOptions['externalEditor'] = "notepad.exe" +else: + defaultOptions['externalEditor'] = "emacs" + + +options = defaultOptions.copy() + +def settingsFileLocation(): + folder = "" + try: + folder = os.path.expanduser("~/.asy/") + except: + pass + return os.path.normcase(os.path.join(folder,"xasy.conf")) + +def setAsyPathFromWindowsRegistry(): + try: + import _winreg as registry + #test both registry locations + try: + key = registry.OpenKey(registry.HKEY_LOCAL_MACHINE,"Software\\Microsoft\\Windows\\CurrentVersion\\App Paths\\Asymptote") + options['asyPath'] = registry.QueryValueEx(key,"Path")[0]+"\\asy.exe" + registry.CloseKey(key) + except: + key = registry.OpenKey(registry.HKEY_LOCAL_MACHINE,"Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Asymptote") + options['asyPath'] = registry.QueryValueEx(key,"InstallLocation")[0]+"\\asy.exe" + registry.CloseKey(key) + except: + #looks like asy is not installed or this isn't Windows + pass + +def setDefaults(): + global options + options = defaultOptions.copy() + if sys.platform[:3] == 'win': #for windows, wince, win32, etc + setAsyPathFromWindowsRegistry() + save() + +def load(): + global options + fileName = settingsFileLocation() + if not os.path.exists(fileName): + #make folder + thedir = os.path.dirname(fileName) + if not os.path.exists(thedir): + try: + os.makedirs(thedir) + except: + raise Exception,"Could not create configuration folder" + if not os.path.isdir(thedir): + raise Exception,"Configuration folder path does not point to a folder" + setDefaults() + try: + f = open(fileName,"rb") + newOptions = pickle.load(f) + for key in options.keys(): + if type(newOptions[key]) != type(options[key]): + raise Exception,"Bad type for entry in xasy settings" + options = newOptions + except: + setDefaults() + +def save(): + global options + fileName = settingsFileLocation() + try: + f = open(fileName,"wb") + pickle.dump(options,f) + f.close() + except: + raise Exception,"Error saving preferences" + +load() + +if __name__=='__main__': + print settingsFileLocation() + print "Current content" + load() + print "Setting defaults" + setDefaults() + save() + load() + options['showAxes'] = options['showGrid'] = False + save() + print "Set to False" + load() + options['showAxes'] = options['showGrid'] = True + save() + print "Set to True" + load() + print options diff --git a/Build/source/utils/asymptote/GUI/xasyOptionsDialog.py b/Build/source/utils/asymptote/GUI/xasyOptionsDialog.py new file mode 100755 index 00000000000..b93b2a7fed9 --- /dev/null +++ b/Build/source/utils/asymptote/GUI/xasyOptionsDialog.py @@ -0,0 +1,245 @@ +#!/usr/bin/env python +########################################################################### +# +# xasyOptionsDialog implements a dialog window to allow users to edit +# their preferences and specify program options +# +# +# Author: Orest Shardt +# Created: June 29, 2007 +# +########################################################################### + +from Tkinter import * +import xasyOptions +import tkSimpleDialog +import xasyColorPicker +import tkMessageBox +import tkFileDialog +import tkColorChooser +import os +import subprocess +import sys + +class xasyOptionsDlg(tkSimpleDialog.Dialog): + """A dialog to interact with users about their preferred settings""" + def __init__(self,master=None): + tkSimpleDialog.Dialog.__init__(self,master,"xasy Options") + + def body(self,master): + optFrame = Frame(master) + optFrame.grid(row=0,column=0,sticky=N+S+E+W) + + asyGrp = LabelFrame(optFrame,text="Asymptote",padx=5,pady=5) + asyGrp.grid(row=0,column=0,sticky=E+W) + asyGrp.rowconfigure(0,weight=1) + asyGrp.rowconfigure(1,weight=1) + asyGrp.columnconfigure(0,weight=1) + asyGrp.columnconfigure(0,weight=2) + Label(asyGrp,text="Command").grid(row=0,column=0,sticky=W) + self.ap = Entry(asyGrp) + self.ap.insert(END,xasyOptions.options['asyPath']) + self.ap.grid(row=0,column=1,sticky=E+W) + Button(asyGrp,text="...",command=self.findAsyPath).grid(row=0,column=2,sticky=E+W) + self.showDebug = BooleanVar() + self.showDebug.set(xasyOptions.options['showDebug']) + self.sd = Checkbutton(asyGrp,text="Show debugging info in console",var=self.showDebug) + self.sd.grid(row=1,column=0,columnspan=2,sticky=W) + + editGrp = LabelFrame(optFrame,text="External Editor",padx=5,pady=5) + editGrp.grid(row=1,column=0,sticky=E+W) + editGrp.rowconfigure(0,weight=1) + editGrp.rowconfigure(1,weight=1) + editGrp.columnconfigure(0,weight=1) + editGrp.columnconfigure(0,weight=2) + Label(editGrp,text="Program").grid(row=0,column=0,sticky=W) + self.ee = Entry(editGrp) + self.ee.insert(END,xasyOptions.options['externalEditor']) + self.ee.grid(row=0,column=1,sticky=E+W) + Button(editGrp,text="...",command=self.findEEPath).grid(row=0,column=2,sticky=E+W) + + penGrp = LabelFrame(optFrame,text="Default Pen",padx=5,pady=5) + penGrp.grid(row=2,column=0,sticky=E+W) + penGrp.rowconfigure(0,weight=1) + penGrp.rowconfigure(1,weight=1) + penGrp.rowconfigure(2,weight=1) + penGrp.columnconfigure(1,weight=1) + Label(penGrp,text="Color").grid(row=0,column=0,sticky=E) + self.pc = xasyOptions.options['defPenColor'] + Button(penGrp,text="Change",command=self.changePenColor).grid(row=0,column=1,sticky=W) + Label(penGrp,text="Width").grid(row=1,column=0,sticky=E) + self.pw = Entry(penGrp) + self.pw.insert(END,str(xasyOptions.options['defPenWidth'])) + self.pw.grid(row=1,column=1,sticky=E+W) + Label(penGrp,text="Options").grid(row=2,column=0,sticky=E) + self.po = Entry(penGrp) + self.po.insert(END,xasyOptions.options['defPenOptions']) + self.po.grid(row=2,column=1,sticky=E+W) + + dispGrp = LabelFrame(optFrame,text="Display Options",padx=5,pady=5) + dispGrp.grid(row=3,column=0,sticky=E+W) + dispGrp.rowconfigure(0,weight=1) + dispGrp.rowconfigure(1,weight=1) + dispGrp.rowconfigure(2,weight=1) + dispGrp.rowconfigure(3,weight=1) + dispGrp.columnconfigure(0,weight=1) + dispGrp.columnconfigure(1,weight=1) + dispGrp.columnconfigure(2,weight=1) + self.showAxes = BooleanVar() + self.showAxes.set(xasyOptions.options['showAxes']) + self.sa = Checkbutton(dispGrp,text="Show Axes",var=self.showAxes) + self.sa.grid(row=0,column=0,sticky=W) + self.ac = xasyOptions.options['axesColor'] + Button(dispGrp,text="Color...",command=self.changeAxesColor).grid(row=1,column=0) + Label(dispGrp,text="x").grid(row=0,column=1,padx=5,sticky=E) + self.axs = Entry(dispGrp,width=6) + self.axs.insert(END,xasyOptions.options['axisX']) + self.axs.grid(row=0,column=2,sticky=W+E) + Label(dispGrp,text="y").grid(row=1,column=1,padx=5,sticky=E) + self.ays = Entry(dispGrp,width=6) + self.ays.insert(END,xasyOptions.options['axisY']) + self.ays.grid(row=1,column=2,sticky=W+E) + + self.showGrid = BooleanVar() + self.showGrid.set(xasyOptions.options['showGrid']) + self.sg = Checkbutton(dispGrp,text="Show Grid",var=self.showGrid) + self.sg.grid(row=4,column=0,sticky=W) + self.gc = xasyOptions.options['gridColor'] + Button(dispGrp,text="Color...",command=self.changeGridColor).grid(row=3,column=0) + Label(dispGrp,text="x").grid(row=2,column=1,padx=5,sticky=E) + self.gxs = Entry(dispGrp,width=6) + self.gxs.insert(END,xasyOptions.options['gridX']) + self.gxs.grid(row=2,column=2,sticky=W+E) + Label(dispGrp,text="y").grid(row=3,column=1,padx=5,sticky=E) + self.gys = Entry(dispGrp,width=6) + self.gys.insert(END,xasyOptions.options['gridY']) + self.gys.grid(row=3,column=2,sticky=W+E) + + def findEEPath(self): + if sys.platform[:3] == 'win': #for windows, wince, win32, etc + file=tkFileDialog.askopenfile(filetypes=[("Programs","*.exe"),("All files","*")],title="Choose External Editor",parent=self) + else: + file=tkFileDialog.askopenfile(filetypes=[("All files","*")],title="Choose External Editor",parent=self) + if file != None: + name = os.path.abspath(file.name) + file.close() + self.ee.delete(0,END) + self.ee.insert(END,name) + self.validate() + + def findAsyPath(self): + if sys.platform[:3] == 'win': #for windows, wince, win32, etc + file=tkFileDialog.askopenfile(filetypes=[("Programs","*.exe"),("All files","*")],title="Find Asymptote Executable",parent=self) + else: + file=tkFileDialog.askopenfile(filetypes=[("All files","*")],title="Find Asymptote Executable",parent=self) + if file != None: + name = os.path.abspath(file.name) + file.close() + self.ap.delete(0,END) + self.ap.insert(END,name) + self.validate() + + def getAColor(self,color): + result = xasyColorPicker.xasyColorDlg(self).getColor(xasyColorPicker.makeRGBfromTkColor(color)) + return xasyColorPicker.RGB255hex(xasyColorPicker.RGBreal255(result)) + + def changeAxesColor(self): + self.ac = self.getAColor(self.ac) + + def changeGridColor(self): + self.gc = self.getAColor(self.gc) + + def changePenColor(self): + self.pc = self.getAColor(self.pc) + + def apply(self): + xasyOptions.options['externalEditor'] = self.ee.get() + xasyOptions.options['asyPath'] = self.ap.get() + xasyOptions.options['showDebug'] = bool(self.showDebug.get()) + + xasyOptions.options['defPenColor'] = self.pc + xasyOptions.options['defPenWidth'] = float(self.pw.get()) + xasyOptions.options['defPenOptions'] = self.po.get() + + xasyOptions.options['showAxes'] = bool(self.showAxes.get()) + xasyOptions.options['axesColor'] = self.ac + xasyOptions.options['tickColor'] = self.ac + xasyOptions.options['axisX'] = int(self.axs.get()) + xasyOptions.options['axisY'] = int(self.ays.get()) + xasyOptions.options['showGrid'] = bool(self.showGrid.get()) + xasyOptions.options['gridColor'] = self.gc + xasyOptions.options['gridX'] = int(self.gxs.get()) + xasyOptions.options['gridY'] = int(self.gys.get()) + xasyOptions.save() + + def validateAColor(self,color): + hexdigits = '0123456789abcdef' + if len(self.pc) != 7 or self.pc[0] != '#' or sum([1 for a in self.pc[1:] if a in hexdigits]) != 6: + return False + else: + return True + + def validate(self): + """Validate the data entered into the dialog""" + #validate the path + pathOK = True + try: + test = subprocess.Popen([self.ap.get(),"-interactive"],stdin=subprocess.PIPE,stdout=subprocess.PIPE,stderr=subprocess.PIPE) + test.stdin.close() + test.wait() + idString = test.stdout.read() + except: + pathOK = False + if not pathOK or not idString.startswith("Welcome to Asymptote"): + tkMessageBox.showerror("xasy Options","Specified file does not exist or is not an Asymptote executable.\r\n"+self.ap.get(),parent=self) + return False + + #validate the external editor + # how can I validate the editor? + + #validate the color + hexdigits = '0123456789abcdef' + if not self.validateAColor(self.pc): + tkMessageBox.showerror("xasy Options","Invalid pen color.\r\n"+self.pc,parent=self) + return False + #validate the width + try: + test = float(self.pw.get()) + except: + tkMessageBox.showerror("xasy Options","Pen width must be a number.",parent=self) + return False + + #validate the options + #nothing to do + + #validate the axis spacing + try: + test = int(self.axs.get()) + test = int(self.ays.get()) + except: + tkMessageBox.showerror("xasy Options","Axes' x- and y-spacing must be numbers.",parent=self) + return False + + #validate the grid spacing + try: + test = int(self.gxs.get()) + test = int(self.gys.get()) + except: + tkMessageBox.showerror("xasy Options","Grid's x- and y-spacing must be numbers.",parent=self) + return False + + if not self.validateAColor(self.ac): + tkMessageBox.showerror("xasy Options","Invalid axis color.\r\n"+self.ac,parent=self) + return False + + if not self.validateAColor(self.gc): + tkMessageBox.showerror("xasy Options","Invalid grid color.\r\n"+self.gc,parent=self) + return False + + return True + +if __name__ == '__main__': + root = Tk() + xasyOptions.load() + d = xasyOptionsDlg(root) + print d.result |