################################################################################
#
#       This file is part of Gato (Graph Animation Toolbox)
#
#	file:   GraphEditor.py
#	author: Alexander Schliep (alexander@schliep.org)
#
#       Copyright (C) 1998-2015, Alexander Schliep, Winfried Hochstaettler and 
#       Copyright 1998-2001 ZAIK/ZPR, Universitaet zu Koeln
#                                   
#       Contact: alexander@schliep.org, winfried.hochstaettler@fernuni-hagen.de             
#
#       Information: http://gato.sf.net
#
#       This library is free software; you can redistribute it and/or
#       modify it under the terms of the GNU Library General Public
#       License as published by the Free Software Foundation; either
#       version 2 of the License, or (at your option) any later version.
#
#       This library is distributed in the hope that it will be useful,
#       but WITHOUT ANY WARRANTY; without even the implied warranty of
#       MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
#       Library General Public License for more details.
#
#       You should have received a copy of the GNU Library General Public
#       License along with this library; if not, write to the Free
#       Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
#
#
#       This file is version $Revision: 670 $ 
#                       from $Date: 2015-01-13 16:04:11 -0500 (Tue, 13 Jan 2015) $
#             last change by $Author: schliep $.
#
################################################################################


from Tkinter import *
from Graph import Graph, Point2D
from math import sqrt
import GatoGlobals
from GraphDisplay import GraphDisplay
from tkSimpleDialog import askinteger, askfloat
import tkSimpleDialog 
import string
import tkMessageBox

g = GatoGlobals.AnimationParameters

class EditWeightsDialog(tkSimpleDialog.Dialog):
    """ Provide a dialog for editing vertex and edge weigths
    
         - title        the title in the dialog box
         - nrOfWeights  how many weights are there
         - weights      an array of initial values 
         - intFlag      an array denoting whether the corresponding
                        entry is an integer (=1) or a float (=0) 
                        Hack: A negative value will disable editing
         - label        an optional array of strings to use for weight names """
    def __init__(self, master, title, nrOfWeights, weights, intFlag, label=None):
        self.nrOfWeights = nrOfWeights
        self.weights = weights
        self.intFlag = intFlag
        self.label = label
        tkSimpleDialog.Dialog.__init__(self, master, title)
        
        
    def body(self, master):
        self.resizable(0,0)
        #label = Label(master, text="Weight", anchor=W)
        #label.grid(row=0, column=0, padx=4, pady=3)
        label = Label(master, text="Value", anchor=W)
        label.grid(row=0, column=1, padx=4, pady=3)
        
        self.entry = [None] * self.nrOfWeights
        
        for i in xrange(self.nrOfWeights):
            if self.label == None:
                label = Label(master, text="Weight %d" %(i+1), anchor=W)
            else:
                label = Label(master, text=self.label[i], anchor=W)
            label.grid(row=i+1, column=0, padx=4, pady=3, sticky="e")
            self.entry[i] = Entry(master, width=6, exportselection=FALSE)
            if self.intFlag[i]:
                self.entry[i].insert(0,"%d" % self.weights[i])
            else:
                self.entry[i].insert(0,"%f" % self.weights[i])
            self.entry[i].grid(row=i+1, column=1, padx=4, pady=3, sticky="w")
            
    def validate(self):
        self.result = [None] * self.nrOfWeights
        for i in xrange(self.nrOfWeights):	    
            try:
                if self.intFlag[i]:
                    self.result[i] = string.atoi(self.entry[i].get())
                else:
                    self.result[i] = string.atof(self.entry[i].get())
            except ValueError:
                if self.intFlag[i]:
                    m = "Please enter an integer value for weight %d." % (i+1) 
                else:
                    m = "Please enter an floating point number for weight %d." % (i+1) 
                tkMessageBox.showwarning("Invalid Value", m, parent=self)
                self.entry[i].selection_range(0,"end")
                self.entry[i].focus_set()
                self.result = None
                return 0
        return 1
        
class GraphEditor(GraphDisplay):
    """ GraphEditor is a subclass of GraphDisplay providing an user interface
        for editing options. Core edit operations are defined in  GraphDisplay.
        GraphEditor is not designed for direct consumption, use 
    
        - GraphEditorFrame
        - GraphEditorToplevel
    
        instead. 
    
        Bindings:
        - Mouse, button 1 down/up: Add a vertex if nothing underneath mouse
          else select for move vertex
        - Mouse, move: move vertex 
        - Mouse, button 2 down: select tail for adding an edge 
        - Mouse, button 2 up: select head for adding an edge 
        - Mouse, button 3 up: delete vertex/edge  underneath mouse
        """
    
    
    def __init__(self):
        GraphDisplay.__init__(self)
        
        self.rubberbandLine = None
        self.movedVertex = None
        self.startx = None # position where MouseDown first occurred
        self.starty = None
        self.gridSize = g.GridSize
        self.gridding = 0
        self.mode = 'AddOrMoveVertex'
        # 'AddEdge' 'DeleteEdgeOrVertex' 'SwapOrientation' 'EditWeight'
        
    def ToggleGridding(self):
        """ Toggle gridding """
        if self.gridding:
            self.gridding = 0
        else:
            self.gridding = 1
            
    def SetEditMode(self,mode):
        self.mode = mode
        
    def WindowToCanvasCoords(self,event):
        """ Given an event return the (x,y) in canvas coordinates while 
            using gridding if a gridsize is specified in gGridSize """
        if not self.gridding:
            x = self.canvas.canvasx(event.x)
            y = self.canvas.canvasy(event.y)
        else:
            x = self.canvas.canvasx(event.x,self.gridSize)
            y = self.canvas.canvasy(event.y,self.gridSize)
        return (x,y)
        
    def Zoom(self,percent):
        try:
            GraphDisplay.Zoom(self,percent)
            self.gridSize = (g.GridSize * self.zoomFactor) / 100.0
        except:
            return None
            
    def CreateWidgets(self):
        """ Add additional bindings with proper callbacks to canvas  """
        GraphDisplay.CreateWidgets(self)
        
        Widget.bind(self.canvas, "<Motion>", self.MouseMotion)
        Widget.bind(self.canvas, "<1>", self.MouseDown) 
        Widget.bind(self.canvas, "<B1-Motion>", self.MouseMove)
        Widget.bind(self.canvas, "<B1-ButtonRelease>", self.MouseUp) 
        Widget.bind(self.canvas, "<2>", self.Mouse2Down) 
        Widget.bind(self.canvas, "<B2-Motion>", self.Mouse2Move)
        Widget.bind(self.canvas, "<B2-ButtonRelease>", self.Mouse2Up) 
        Widget.bind(self.canvas, "<B3-ButtonRelease>", self.Mouse3Up)
        Widget.bind(self.canvas, "<Enter>", self.CanvasEnter)
        Widget.bind(self.canvas, "<Leave>", self.CanvasLeave)
        
        
        
        #===== ACTIONS ==============================================================
        
    def ShowCoords(self,event):
        x,y = self.WindowToCanvasCoords(event)
        v=self.FindVertex(event)
        if v == None and self.gridding:
            v = self.FindGridVertex(event)
        e = self.FindEdge(event)
        if e!=None:
            infoString = "Edge (%d,%d)" % (e[0], e[1]) 
        elif v!=None:
            t = self.G.GetEmbedding(v)
            infoString = "Vertex %d at position (%d,%d)" % (v, int(t.x), int(t.y))
        elif x>=0 and y>=0:
            x,y = self.CanvasToEmbedding(x,y)
            infoString = "(%d,%d)" % (x,y)
        else:
            infoString = ""
        self.UpdateInfo(infoString)
        
        
    def AddOrMoveVertexDown(self,event):
        v = self.FindVertex(event)
        if v == None:
            if self.FindGridVertex(event) == None:
                x,y = self.WindowToCanvasCoords(event)
                x = max(x,0)
                y = max(y,0)
                self.AddVertexCanvas(x,y)
            self.movedVertex = None
        else:
            self.canvas.addtag("mySel", "withtag", self.drawVertex[v])
            self.canvas.addtag("mySel", "withtag", self.drawLabel[v])
            try:
                self.canvas.addtag("mySel", "withtag", self.drawEdges[(v,v)])
            except:
                pass
            self.canvas.lift("mySel")
            # We want to start off with user clicking smack in middle of
            # vertex -- cant force him, so we fake it
            c = self.canvas.coords(self.drawVertex[v])
            # c already canvas coordinates
            self.oldx = (c[2] - c[0])/2 + c[0]
            self.oldy = (c[3] - c[1])/2 + c[1]
            self.movedVertex = v
            self.didMoveVertex = 0
            
    def AddOrMoveVertexMove(self,event):
        if not self.canvasleft:
            self.newx,self.newy = self.WindowToCanvasCoords(event)
            self.newx = max(self.newx,0)
            self.newy = max(self.newy,0)
            self.update_idletasks()
            try:
                self.canvas.lift("mySel")
                self.canvas.move("mySel", 
                                 self.newx - self.oldx, 
                                 self.newy - self.oldy)
                
                #self.MoveVertex(self.movedVertex,self.newx,self.newy)
                
                self.oldx = self.newx
                self.oldy = self.newy
                
                x,y = self.CanvasToEmbedding(self.newx,self.newy)
                infoString = "Vertex %d at position (%d,%d)" % (self.movedVertex,x,y)
                self.UpdateInfo(infoString)
                
                self.didMoveVertex = 1
            except:
                i = 1 # Need instruction after except
                
    def AddOrMoveVertexUp(self,event):
        if self.movedVertex != None:
            # Moving within vertex oval does not move vertex
            self.update_idletasks()
            if self.didMoveVertex:
                self.MoveVertex(self.movedVertex,self.newx,self.newy)
            self.movedVertex = None
            self.canvas.dtag("mySel")
            
            
    def AddEdgeDown(self,event):
        self.tail = self.FindVertex(event)
        if self.tail != None:
            c = self.canvas.coords(self.drawVertex[self.tail])
            self.startx = (c[2] - c[0])/2 + c[0]
            self.starty = (c[3] - c[1])/2 + c[1] 
            
            
    def AddEdgeMove(self,event):
        if self.tail != None:	
            # canvas x and y take the screen coords from the event and translate
            # them into the coordinate system of the canvas object
            x = self.canvas.canvasx(event.x)
            y = self.canvas.canvasy(event.y)
            
            if (self.startx != event.x)  and (self.starty != event.y) : 
                self.canvas.delete(self.rubberbandLine)
                self.rubberbandLine = self.canvas.create_line(
                    self.startx, self.starty, x, y)
                self.canvas.lower(self.rubberbandLine,"vertices")
                # this flushes the output, making sure that 
                # the rectangle makes it to the screen 
                # before the next event is handled
                self.update_idletasks()
                
                
                
    def AddEdgeUp(self,event):
        if self.rubberbandLine != None:
            self.canvas.delete(self.rubberbandLine)
            
        if self.tail != None:	
            x = self.canvas.canvasx(event.x)
            y = self.canvas.canvasy(event.y)
            
            widget = event.widget.find_closest(x,y,None,self.rubberbandLine)
            if widget:
                widget = widget[0]
                tags = self.canvas.gettags(widget)
                head = None
                if "vertices" in tags:
                    head = self.vertex[widget]
                elif "labels" in tags:
                    head = self.label[widget]
                    
                if head != None:
                    self.AddEdge(self.tail,head)
                    
                    
    def DeleteEdgeOrVertexUp(self,event):
        if event.widget.find_withtag(CURRENT):
            widget = event.widget.find_withtag(CURRENT)[0]
            tags = self.canvas.gettags(widget)
            if "edges" in tags:
                (tail,head) = self.edge[widget]
                self.DeleteEdge(tail,head)
            else:
                if "vertices" in tags:
                    v = self.vertex[widget]
                elif "labels" in tags:
                    v = self.label[widget]
                self.DeleteVertex(v)
                self.tail = None
                if self.rubberbandLine != None:
                    self.canvas.delete(self.rubberbandLine)
                    
                    
    def SwapOrientationUp(self,event):
        if event.widget.find_withtag(CURRENT):
            widget = event.widget.find_withtag(CURRENT)[0]
            tags = self.canvas.gettags(widget)
            if "edges" in tags:
                (tail,head) = self.edge[widget]
                self.SwapEdgeOrientation(tail,head)
                

    def SetEdgeWeight(self, i, tail, head, val):
        self.G.SetEdgeWeight(i, tail, head, val)

    def SetVertexWeight(self, i, v, val):
        self.G.SetVertexWeight(i, v, val)
                
    def EditWeightUp(self,event):
        if event.widget.find_withtag(CURRENT):
            widget = event.widget.find_withtag(CURRENT)[0]
            tags = self.canvas.gettags(widget)
            if "edges" in tags:
                (tail,head) = self.edge[widget]
                
                weights = ()
                intFlag = ()
                count = len(self.G.edgeWeights.keys())
                for i in xrange(count):
                    weights = weights + (self.G.GetEdgeWeight(i,tail,head),)
                    intFlag = intFlag + (self.G.edgeWeights[i].QInteger(),)
                    
                d = EditWeightsDialog(self, "Weight of edge (%d,%d)" % (tail,head), 
                                      count, weights, intFlag) 
                if d.result is not None:
                    for i in xrange(count):
                        self.SetEdgeWeight(i, tail, head, d.result[i])
            else: # We have a vertex
                v = self.FindVertex(event)
                if v != None and self.G.NrOfVertexWeights() > 0:
                    weights = ()
                    intFlag = ()
                    count = len(self.G.vertexWeights.keys())
                    for i in xrange(count):
                        weights = weights + (self.G.GetVertexWeight(i, v),)
                        intFlag = intFlag + (self.G.vertexWeights[i].QInteger(),)
                        
                    d = EditWeightsDialog(self, "Edit vertex weights %d" % v, 
                                              count, weights, intFlag) 
                    if d.result is not None:
                        for i in xrange(count):
                            self.G.SetVertexWeight(i, v, d.result[i])
                            
                            
                            #===== GUI-Bindings FOR ACTIONS ================================================
                            
    def MouseMotion(self,event):
        if self.mode == 'AddOrMoveVertex':	
            self.ShowCoords(event)
            
    def MouseDown(self,event):
        if self.mode == 'AddOrMoveVertex':
            self.AddOrMoveVertexDown(event)
        elif self.mode == 'AddEdge':
            self.AddEdgeDown(event)
            
    def MouseMove(self,event):
        if self.mode == 'AddOrMoveVertex':
            self.AddOrMoveVertexMove(event)
        elif self.mode == 'AddEdge':
            self.AddEdgeMove(event)
            
    def MouseUp(self,event):
        if self.mode == 'AddOrMoveVertex':
            self.AddOrMoveVertexUp(event)
        elif self.mode == 'AddEdge':
            self.AddEdgeUp(event)
        elif self.mode == 'DeleteEdgeOrVertex':
            self.DeleteEdgeOrVertexUp(event)
        elif self.mode == 'SwapOrientation':
            self.SwapOrientationUp(event)
        elif self.mode == 'EditWeight':
            self.EditWeightUp(event)
            
    def Mouse2Down(self,event):
        self.AddEdgeDown(event)
        
    def Mouse2Move(self,event):
        self.AddEdgeMove(event)
        
    def Mouse2Up(self,event):
        self.AddEdgeUp(event)
        
    def Mouse3Up(self,event):
        self.DeleteEdgeOrVertexUp(event)
        
    def CanvasEnter(self,event):
        self.canvasleft = 0
        
    def CanvasLeave(self, event):
        self.canvasleft = 1
        
        
class GraphEditorFrame(GraphEditor, Frame):
    """ A GraphEditor in a frame """
    def __init__(self, master=None):
        Frame.__init__(self, master)
        self.pack() 
        self.pack(expand=1,fill=BOTH) # Makes whole window resizeable
        GraphEditor.__init__(self)
        
    def SetTitle(self,title):
        sys.info("change window title to %s" % title)
        
class GraphEditorToplevel(GraphEditor, Toplevel):
    """ A GraphEditor in a top-level window """
    
    def __init__(self, master=None):
        Toplevel.__init__(self, master)
        GraphEditor.__init__(self)
        
    def SetTitle(self,title):
        self.title(title)
