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()