summaryrefslogtreecommitdiff
path: root/Build/source/utils/asymptote/GUI
diff options
context:
space:
mode:
authorKarl Berry <karl@freefriends.org>2009-05-16 00:19:13 +0000
committerKarl Berry <karl@freefriends.org>2009-05-16 00:19:13 +0000
commitbab45528d65eaafe68a705dbb2a57075c7b7cbd8 (patch)
tree10b4ae2b5195c8dede153ab89359ec00f55f325f /Build/source/utils/asymptote/GUI
parent8643d90372e9c31e0f461c15c596b60a545bd7d3 (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-xBuild/source/utils/asymptote/GUI/CubicBezier.py101
-rwxr-xr-xBuild/source/utils/asymptote/GUI/UndoRedoStack.py117
-rwxr-xr-xBuild/source/utils/asymptote/GUI/xasy.py35
-rwxr-xr-xBuild/source/utils/asymptote/GUI/xasy2asy.py785
-rwxr-xr-xBuild/source/utils/asymptote/GUI/xasyActions.py387
-rwxr-xr-xBuild/source/utils/asymptote/GUI/xasyBezierEditor.py208
-rwxr-xr-xBuild/source/utils/asymptote/GUI/xasyCodeEditor.py37
-rwxr-xr-xBuild/source/utils/asymptote/GUI/xasyColorPicker.py248
-rw-r--r--Build/source/utils/asymptote/GUI/xasyExample.asy63
-rwxr-xr-xBuild/source/utils/asymptote/GUI/xasyFile.py315
-rwxr-xr-xBuild/source/utils/asymptote/GUI/xasyGUIIcons.py92
-rwxr-xr-xBuild/source/utils/asymptote/GUI/xasyMainWin.py1742
-rwxr-xr-xBuild/source/utils/asymptote/GUI/xasyOptions.py126
-rwxr-xr-xBuild/source/utils/asymptote/GUI/xasyOptionsDialog.py245
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