from Tkinter import *
import pickle
import tkSimpleDialog
# Hever Zsolt 1999
class Objects:
"The object definition."
def __init__(self):
"""We will use this constructor in the App class
to get initial information about this module."""
self.objtype= {'window': self.makeNewButton,'rectangle': self.makeNewRect,
'text': self.makeNewText}
##########################################################
# 'New' : Make a new canvas object
##########################################################
def makeNewButton(self):
"Function to make a new window canvas item"
#It makes a new object
Widget.bind(self.l, "<1>", self._pass)
Widget.bind(self.l, "<B1-Motion>", self._pass)
Widget.bind(self.l, "<Double-Button-1>",self._pass)
Widget.bind(self.l, "<ButtonRelease-1>",self._pass)
self.l.tag_bind('obj', "<Enter>", self._mouseEnter)
self.l.delete(self.frm)
x1,x2 = self.l.xview();x1=x1*1600 # To place it always
y1,y2 = self.l.yview();y1=y1*1600 # into the top left corner
### Button
b1,b2=x1+50,y1+50
attr={'text':'Button'}
B=Button(self,attr)
object=self.l.create_window(b1,b2,window=B,anchor=NW)
a,b,c,d=self.l.bbox(object)
obbox=a+1,b+1,c-1,d-1
objcoord=a-1,b-1
self.dattr[object]=B
### General part
# Configure the self.dobject dictionary
self.ocounter=self.ocounter+1 #Global
# Ox is the key of self.dobject dictionary
Ox="O"+`self.ocounter`
t=self.l.type(object)
# Configure the self.dobject dictionary with
#( 0 , 1 , 2 , 3 , 4 , 5)
#(object id, object bbox coordinates, connected lines, obj attributes,obj coordinates, objtype)
self.dobject[Ox]=(object,obbox,[],attr,objcoord,t) #Global
# Configure the canvas item with (oId, Ox,"obj") tags
self.oId="o"+`object` #Global
self.l.itemconfigure(object,tags=(self.oId,Ox,"obj"))
# Set the statusbar
self.m.configure(text="id = "+`object`)
# Binding, moving of objects is based on it
self.l.tag_bind(object, "<Enter>", self._mouseEnter)
def makeNewRect(self):
"Function to make a new rectangle canvas item"
#It makes a new object
Widget.bind(self.l, "<1>", self._pass)
Widget.bind(self.l, "<B1-Motion>", self._pass)
Widget.bind(self.l, "<Double-Button-1>",self._pass)
Widget.bind(self.l, "<ButtonRelease-1>",self._pass)
self.l.tag_bind('obj', "<Enter>", self._mouseEnter)
self.l.delete(self.frm)
x1,x2 = self.l.xview();x1=x1*1600 # To place it always
y1,y2 = self.l.yview();y1=y1*1600 # into the top left corner
### Rectangle
obbox=objcoord=x0, y0, x1, y1=x1+3, y1+3, x1+43, y1+43
attr={}
object = self.l.create_rectangle(x0, y0, x1, y1)
### General part
# Configure the self.dobject dictionary
self.ocounter=self.ocounter+1 #Global
# Ox is the key of self.dobject dictionary
Ox="O"+`self.ocounter`
t = self.l.type(object)
# Configure the self.dobject dictionary with
#( 0 , 1 , 2 , 3 , 4 , 5)
#(object id, object bbox coordinates, connected lines, obj attributes,obj coordinates, type)
self.dobject[Ox]=(object,obbox,[],attr,objcoord,t) #Global
# Configure the canvas item with (oId, Ox,"obj") tags
self.oId="o"+`object` #Global
self.l.itemconfigure(object,tags=(self.oId,Ox,"obj"))
# Set the statusbar
self.m.configure(text="id = "+`object`)
# Binding, moving of objects is based on it
self.l.tag_bind(object, "<Enter>", self._mouseEnter)
def makeNewText(self):
"Function to make a new text canvas item"
#It makes a new object
Widget.bind(self.l, "<1>", self._pass)
Widget.bind(self.l, "<B1-Motion>", self._pass)
Widget.bind(self.l, "<Double-Button-1>",self._pass)
Widget.bind(self.l, "<ButtonRelease-1>",self._pass)
self.l.tag_bind('obj', "<Enter>", self._mouseEnter)
self.l.delete(self.frm)
x1,x2 = self.l.xview();x1=x1*1600 # To place it always
y1,y2 = self.l.yview();y1=y1*1600 # into the top left corner
#### Text
objcoord=t1,t2=x1+50,y1+50
result=tkSimpleDialog.askstring('Szoveg?','Text')
attr={'text':result}
object=self.l.create_text(t1,t1,attr, anchor=NW)
a,b,c,d=self.l.bbox(object)
obbox=a-1,b-1,c+1,d+1
### General part
# Configure the self.dobject dictionary
self.ocounter=self.ocounter+1 #Global
# Ox is the key of self.dobject dictionary
Ox="O"+`self.ocounter`
t=self.l.type(object)
# Configure the self.dobject dictionary with
#( 0 , 1 , 2 , 3 , 4 , 5)
#(object id, object bbox coordinates, connected lines, obj attributes,obj coordinates, type)
self.dobject[Ox]=(object,obbox,[],attr,objcoord,t) #Global
# Configure the canvas item with (oId, Ox,"obj") tags
self.oId="o"+`object` #Global
self.l.itemconfigure(object,tags=(self.oId,Ox,"obj"))
# Set the statusbar
self.m.configure(text="id = "+`object`)
# Binding, moving of objects is based on it
self.l.tag_bind(object, "<Enter>", self._mouseEnter)
def _mouseEnter(self, event):
"The CURRENT tag is applied."
self.l.delete(self.frm)
a,b,c,d=self.l.bbox(CURRENT)
self.c=self.l.gettags(CURRENT)
# self.frm Global
self.frm=self.l.create_rectangle(a-2,b-2,c+2,d+2,outline='red',width=3,tags=self.c[0])
self.lastx=self.l.canvasx(event.x)
self.lasty=self.l.canvasy(event.y)
self.l.tag_bind(self.l, "<Double-Button-1>", self._attrwin)
##########################################################
# '<-' state functions : Additional attribution window
##########################################################
def _attrwin (self,event):
'self.l.tag_bind(self.frm, "<Double-Button-1>", self._attrwin)'
n=self.c[0][1:]
id=int(n)
t=self.l.type(id)
key=self.c[1]
if t != 'rectangle':
self.l.delete(self.frm)
result=tkSimpleDialog.askstring('Caption','Text')
if t == 'window':
widget=self.dattr[id]
widget['text']=result
else:
self.l.itemconfigure(id,text=result)
c1,c2,c3,c4,c5,c6=self.dobject[key]
c4['text']=result
self.dobject[key]=c1,c2,c3,c4,c5,c6
##########################################################
# "Load" : Load the canvas objects
##########################################################
def load (self):
"Loading saved canvas items."
self.clearCanvas()
import tkFileDialog
s=tkFileDialog.askopenfilename(initialdir='/',defaultextension="sav",
initialfile='obj.sav',filetypes=[("Visual utility files","*.sav"),("All files","*")])
try:
f=open(s,'r')
only_for_unpacking=pickle.load(f)
f.close()
except:
only_for_unpacking=({},{})
self.dobject, self.dline=only_for_unpacking
if self.dobject :
self.LoadObj()
if self.dline :
self.LoadLines()
def LoadObj(self):
# Creating objects
k=self.dobject.keys();k.sort()
for next in k:
# c1 : object id
# c2 : object bbox coordinates
# c3 : connected lines
# c4 : obj attributes
# c5 : obj coordinates
# c6 : obj type
c1,c2,c3,c4,c5,c6=self.dobject[next]
# This is the object specific part
# Here we select the canvas items.
if c6 == 'window':
### Buttons
b1,b2=c5
B=Button(self,c4)
object=self.l.create_window(b1,b2,window=B,anchor=NW)
self.dattr[object]=B
elif c6 == 'rectangle':
### Rectangle
x0, y0, x1, y1 =c2
c2 = x0, y0, x1, y1 = x0+1, y0+1, x1-1, y1-1
object = self.l.create_rectangle(x0, y0, x1, y1, c4)
elif c6 == 'text':
#### Text
t1,t2=c5
object=self.l.create_text(t1,t2,c4,anchor=NW)
# General part
self.oId="o"+`object`
self.l.itemconfigure(object,tags=(self.oId,next,"obj"))
self.dobject[next]=object,c2,c3,c4,c5,c6
# Bind it if it is for visual utility
self.l.tag_bind(object,"<Enter>",self._mouseEnter)
else:
c=next[1:]
self.ocounter=int(c)
def LoadLines(self):
# Creating lines
k=self.dline.keys();k.sort()
for next in k:
c1,c2,c3=self.dline[next]
x0,y0,x1,y1=c2
self.rline=self.l.create_line(x0,y0,x1,y1,{'width':2,'capstyle':'projecting','fill':'black'})
self.oId="l"+`self.rline`
self.l.itemconfigure(self.rline,tags=(self.oId,next))
self.dline[next]=self.rline,c2,c3
self.l.tag_bind(self.rline, "<Any-Enter>",self._mouseEnterLine)
self.l.tag_bind(self.rline, "<Any-Leave>",self._mouseLeaveLine)
else:
c=next[1:]
self.lcounter=int(c)
# Set the Toolbar
self.tbcont.r.set('x')
class Connecting_Objects:
"""An object class, which can connect any kind of objects.
It should not be overridden.
It is an component."""
def __init__(self):
self.objtype['Line']=self.line_
def line_(self):
"This is the 'Line' state."
Widget.bind(self.l, "<1>", self._mouseDownLine)
Widget.bind(self.l, "<B1-Motion>", self._mouseMotionLine)
Widget.bind(self.l, "<ButtonRelease-1>",self._mouseReleaseLine)
Widget.bind(self.l, "<Double-Button-1>",self._pass)
self.l.delete(self.frm)
self.m.configure(text='Line')
def _mouseDownLine(self, event):
"The start point of a line."
self.rline=None
self.startx=self.l.canvasx(event.x)
self.starty=self.l.canvasy(event.y)
# The start coordinates for the line
self.lsx, self.lsy, self.ids = self._coord(event)
def _mouseMotionLine(self, event):
"Drawing a line as the mouse pointer is moved."
if not (self.lsx == None) and not (self.lsy == None):
ex = self.l.canvasx(event.x)
ey = self.l.canvasy(event.y)
if (self.startx != ex) and (self.starty != ey):
self.l.delete(self.rline)
# try because of small objects
try:
self.lex, self.ley, self.idv = self._coord(event)
except:
self.lex, self.ley, self.idv= None, None, ()
if (self.idv == self.ids) or (self.idv == ()):
self.rline = self.l.create_line(
self.lsx, self.lsy, ex, ey,{'width':2,'fill':'black'})
else:
# Draw it if it reaches a rectangle
self.rline = self.l.create_line(
self.lsx, self.lsy, self.lex, self.ley,{'width':2,'capstyle':'projecting','fill':'black'})
self.l.tag_bind(self.rline, "<Any-Enter>",self._mouseEnterLine)
self.l.tag_bind(self.rline, "<Any-Leave>",self._mouseLeaveLine)
# this flushes the output
self.update_idletasks()
def _mouseEnterLine(self, event):
"The CURRENT tag is applied."
self.l.itemconfig(CURRENT, fill="grey")
def _mouseLeaveLine(self, event):
"The CURRENT tag is applied."
self.l.itemconfig(CURRENT, fill="black")
def _mouseReleaseLine(self,event):
"""Delete a line if it is not pointed to an rectangle item or
pointed to the same rectangle item."""
if self.ids==self.idv or self.idv ==():
self.l.delete(self.rline)
self.ids,self.idv=(),()
else:
if not (self.rline==None):
Ra, Rb = self.l.gettags(self.ids)[1],self.l.gettags(self.idv)[1]
self.oId="l"+`self.rline`
self.m.configure(text="id = "+`self.rline`)
self.lcounter=self.lcounter+1
ltmp="L"+`self.lcounter`
self.dline[ltmp]=(self.rline,(self.lsx,self.lsy,self.lex,self.ley),(Ra,Rb))
self.dobject[Ra][2].append(ltmp)
self.dobject[Rb][2].append(ltmp)
self.l.itemconfigure(self.rline,tags=(self.oId,ltmp),fill='black')
# fill='black' because it left garbage on the screen.
self.l.delete(self.frm)
def _coord(self,event):
"It counts the coords for the line end points."
a=self.l.canvasx(event.x)
b=self.l.canvasy(event.y)
id=self.l.find_closest(a,b)
if not (id == ()): # at start, empty canvas
id=self.l.gettags(id[0])
if self.objtype.has_key(self.l.type(id[0][1:])):
self.startx=a;self.starty=b
key=self.l.gettags(id[0][1:])[1]
x0,y0,x1,y1 = self.dobject[key][1]
xh, yh = int((x1-x0)/2), int((y1-y0)/2)
m=yh/xh; db=b-y0
py=(a-x0)*m; ny=(a-x1)*m*-1
if py >= db and ny > db:
return x0+xh, y0, id[0]
elif py > db and ny <= db:
return x1, y0+yh, id[0]
elif py <= db and ny < db:
return x0+xh, y1, id[0]
elif py < db and ny >= db:
return x0, y0+yh, id[0]
else: # if it isn't self.objtype
return None, None, ()
else:
return None, None, id
class App(Frame,Objects,Connecting_Objects):
""" This is the main object class from which we inherit."""
def __init__(self, master=None):
Frame.__init__(self,master)
Pack.config(self,expand=YES,fill=BOTH)
Objects.__init__(self)
Connecting_Objects.__init__(self)
self.w()
self.lastx=0
self.lasty=0
self.ids=()
self.idv=()
self.rline=None
self.ocounter=0
self.lcounter=0
self.dobject={}
self.dline={}
self.dattr={}
self.not_moved=1
self.frm=0
##########################################################
# Toolbar : It adjusts itself according to the objects
##########################################################
def Toolbar(self):
#Toolbar
self.tbcont=Toplevel()
self.tbcont.geometry("+180+100")
self.tbcont.title('Toolbar')
self.tbcont.r=StringVar()
self.tbcont.r.set('x')
j=1
self.tbcont.b=Radiobutton(self.tbcont,text='<-',variable=self.tbcont.r
,value="x",indicatoron=0,selectcolor='white',command = self.x_)
self.tbcont.b.grid(row=j,column=0,sticky=W+E+N+S)
l=len(self.objtype)
tkeys = self.objtype.keys()
tkeys.sort()
for i in range(l):
j=j+1
textv= 'New '+ tkeys[i]
self.tbcont.b = Radiobutton(self.tbcont, text= textv,variable=self.tbcont.r
,value=tkeys[i],indicatoron=0,selectcolor='white',
command=self.objtype[tkeys[i]])
self.tbcont.b.grid(row=j,column=0,sticky=W+E+N+S)
j=j+1
self.tbcont.b=Radiobutton(self.tbcont,text='Del',variable=self.tbcont.r
,value='del',indicatoron=0,selectcolor='white', command=self.del_)
self.tbcont.b.grid(row=j,column=0,sticky=W+E+N+S)
j=j+1
self.tbcont.QUIT = Button(self.tbcont, {'text': 'QUIT','fg': 'red','command': self.tbcont.quit})
self.tbcont.QUIT.grid(row=j,column=0,sticky=W+E+N+S)
##########################################################
# Application window
##########################################################
def w(self):
"Function to create a scrolled canvas"
# The status bar
self.m=Label(self,relief='groove',text="Free hand",anchor="w")
self.m.pack(side=BOTTOM,fill=BOTH,expand=YES)
# The canvas of the application
self.l=Canvas(self,bg='yellow',height="300",width="350",
scrollregion=(0,0,"1600","1600"))
self.l.scrollX = Scrollbar(self, orient=HORIZONTAL)
self.l.scrollY = Scrollbar(self, orient=VERTICAL)
self.l['xscrollcommand'] = self.l.scrollX.set
self.l['yscrollcommand'] = self.l.scrollY.set
self.l.scrollX['command'] = self.l.xview
self.l.scrollY['command'] = self.l.yview
self.l.scrollX.pack(side=BOTTOM,fill=X,expand=0)
self.l.scrollY.pack(side=RIGHT,fill=Y,expand=0)
self.l.pack(expand=YES,fill=BOTH)
##########################################################
# '<-' state functions : Moving objects
##########################################################
def x_ (self):
"This is the '<-' state"
Widget.bind(self.l, "<1>", self._mouseDown)
Widget.bind(self.l, "<B1-Motion>", self._mouseMove)
Widget.bind(self.l, "<ButtonRelease-1>",self._mouseRelease)
Widget.bind(self.l, "<Double-Button-1>",self._attrwin)
self.l.delete(self.frm)
self.m.config(text="Free hand")
def _mouseDown(self, event):
"It gets the CURRENT item."
if self.l.type(CURRENT) == 'rectangle':
self.lsx = self.l.canvasx(event.x)
self.lsy = self.l.canvasy(event.y)
else:
self.l.delete(self.frm)
self.c=()
def _mouseMove(self, event):
"To move the current object."
if not (self.c == ()) and (self.l.type(CURRENT)=="rectangle") :
x = self.l.canvasx(event.x)
y = self.l.canvasy(event.y)
if not (self.lsx == x and self.lsy == y) and self.not_moved:
# It clears the lines connected if moving
self.not_moved = 0
line_list = self.dobject[self.c[1]][2]
for l in line_list:
self.l.delete(l)
self.l.move(self.c[0], x-self.lastx, y-self.lasty)
self.lastx = x
self.lasty = y
def _mouseRelease(self,event):
"Saving the rectangle coordinates."
if not (self.c == ()) and (self.c[2] == "obj"):
# Rectangule, setting parameters
id=int(self.c[0][1:])
key=self.c[1]
x0,y0,x1,y1=self.l.bbox(id)
newCoord= x0-1,y0-1,x1+1,y1+1
c1,oldCoord,c3,c4,c5,c6=self.dobject[key]
self.dobject[key]=c1,newCoord,c3,c4,(x0-1,y0-1),c6
self.not_moved = 1
# Lines, setting parameters
oldx0,oldy0,oldx1,oldy1=oldCoord # old bbox coordinates of an object
dx=x0-1-oldx0
dy=y0-1-oldy0
list_line=self.dobject[key][2]
for l in list_line :
lid,lcoord,lends=self.dline[l]
xRa,yRa,xRb,yRb=lcoord
Ra,Rb=lends
if key == Ra :
xRa=xRa+dx
yRa=yRa+dy
else:
xRb=xRb+dx
yRb=yRb+dy
self.rline=self.l.create_line( xRa,yRa,xRb,yRb,{'width':2,'capstyle':'projecting','fill':'black'})
self.oId="l"+`self.rline`
self.l.itemconfigure(self.rline,tags=(self.oId,l))
self.dline[l]=self.rline,(xRa,yRa,xRb,yRb),lends
self.l.tag_bind(self.rline, "<Any-Enter>",self._mouseEnterLine)
self.l.tag_bind(self.rline, "<Any-Leave>",self._mouseLeaveLine)
def _mouseEnterLine(self, event):
"The CURRENT tag is applied."
self.l.itemconfig(CURRENT, fill="grey")
def _mouseLeaveLine(self, event):
"The CURRENT tag is applied."
self.l.itemconfig(CURRENT, fill="black")
##########################################################
# "Del" state : Deleting canvas objects
##########################################################
def del_ (self):
"This is the 'Delete' state"
Widget.bind(self.l, "<1>",self._pass)
Widget.bind(self.l, "<B1-Motion>",self._pass)
Widget.bind(self.l, "<ButtonRelease-1>",self._pass)
Widget.bind(self.l, "<Double-Button-1>",self._deleting)
self.m.config(text="Delete")
def _deleting(self,event):
"Deleting by Double-Button-1"
self.c=self.l.gettags(CURRENT)
if not (self.c == ()):
if self.l.type(CURRENT) == 'rectangle':
id=self.l.gettags(self.frm)[0][1:]
self.c=self.l.gettags(id)
else:
id=self.c[0][1:]
T=self.l.type(id)
if self.objtype.has_key(T):
r=self.c[1] # key for the self.dobject dictionary
lista=self.dobject[r][2] # list of lines connected to the object
copy=lista[:] # it is necessary because of pointer ...
for item in copy :
sid=self.dline[item][0]
self._subdel(item,sid)
self.l.delete(id)
del self.dobject[r]
self.l.delete(self.frm) # Deleting the frame binded to the object
else:
T== 'line'
l=self.c[1]
self._subdel(l,id)
self.l.delete(self.frm)
def _subdel(self,l,id):
"Sub-function to _deleting()."
Ra,Rb = self.dline[l][2]
self.dobject[Ra][2].remove(l)
self.dobject[Rb][2].remove(l)
self.l.delete(id)
del self.dline[l]
##########################################################
# The nothing doing function
##########################################################
def _pass (self,event):
"This is the doing nothing function."
pass
##########################################################
# Save and Clear canvas Functions
##########################################################
def save (self):
"Saving canvas items."
self.l.delete(self.frm)
import tkFileDialog
s=tkFileDialog.asksaveasfilename(initialdir='/',defaultextension="sav",
initialfile='obj.sav',filetypes=[("Visual utility files","*.sav"),("All files","*")])
only_for_packing=(self.dobject,self.dline)
f=open(s,'w')
pickle.dump(only_for_packing,f)
f.close()
def clearCanvas(self):
"Cleaning the entire canvas."
self.l.delete(ALL)
self.dobject={}
self.dline={}
self.dattr={}
if __name__=='__main__':
root = Tk()
app = App(root)
app.mainloop()