# This file is part of the Coral project.
# Please read the copyright notice in the LICENSE file.

import copy
from coral import log
import coral

from coral.modeler.diagram.degen import DiagramElementGenerator, addForReparenting

logger=log.getLogger("Gestures")

from qt import *
from qtcanvas import *

from presentation import *
from layout import *

DI = coral.lang.XMIDI20

from coral.modeler.diagram import di_support as DISUPP

## This is sort of a testclass to make an own rubber
## band widget work when creating associations etc.

class RubberBandLine(QCanvasLine):
    pass

 

# This are the basic gestures supported by the UMLDiagram Editor

class Gesture:
    """Gesture is the base class for all gestures"""
    def __init__(self,editor):
        self.markers=[]
        self.editor=editor
        self.currentButton=None
        

    def detectStart(self,e):
        """detectStart is called when user clicks the mouse and the gesture
        is not activated yet"""
        return None
        
    def start(self,e):
        """start is called when setting the current gesture"""
        return None
    
    def onMousePress(self,e):
        """onMousePress is called when mouse is pressed"""
        return self
    def onMouseMove(self,e):
        """onMouseMove is called when the mouse is moved"""
        return self
    def onMouseRelease(self,e):
        """onMouseRelease is called when the mouse button is released"""
        return self
    
    def stop(self):
        """stop is called to deactivate the current gesture"""
        return None


class Select1ElementG(Gesture):
    """Select1ElementG is the gesture used when the user
    clicks an elemement with the mouse in order to select it
    """

    def detectStart(self,e,button=Qt.LeftButton):
        if not (e.button() == button):
            return None

        lElements = self.editor.getSelectableItems(QPoint(e.sX,e.sY))
        
        if lElements!=[]:
            if not (e.state() & Qt.ControlButton) and not (lElements[0] in self.editor.selection.currentSelection()):
                self.editor.selection.clear()
            
            if ((e.state() & Qt.ControlButton) and 
                lElements[0] in self.editor.selection.currentSelection()):
                self.editor.selection.deSelect(lElements[0])
            else:
                self.editor.selection.select(lElements[0])

            #if button == Qt.LeftButton:
            #   putSelectedElementsOnTop(self.editor)

            self.editor.canvas().update()
        return None

class SelectDiagramG(Gesture):
    """ SelectDiagramG is used when the user presses left button over no selection. """
    def detectStart(self,e,button=Qt.LeftButton):
        if not (e.button() == button):
            return None

        lElements = self.editor.getSelectableItems(QPoint(e.sX,e.sY))
        if not lElements:
            ## This is about policy. If there is a selection, and the user presses Ctrl+left button
            ## over the diagram, should the gesture deselect the previous or not?
            ## self.editor.selection.clear() 
            d = self.editor.diagram
            self.editor.tellOthersToFocusOn(d.owner.element,d)
        return None

def putSelectedElementsOnTop(editor):
    """Puts currently selected elements ahead in the XMI-DI
    hierarchy. Useful for maintaining a nice rendering order."""
    coral.getTransactionManager().beginModifyCmd()
    try:
        sel = editor.selection.currentSelection()
        c = dict.fromkeys(sel)
        GraphElementWidget.shallowContainers(c)
        # Shortcut for just one element
        if len(c) == 1:
            ge = c.keys()[0].graphElement
            if ge.container.contained[-1] == ge:
                return
            
        for gew in c.keys():
            ge = gew.graphElement
            owner = ge.container
            owner.contained.drop(ge)
            owner.contained.append(ge)
    finally:
        coral.getTransactionManager().endModifyCmd()


class CreateNewElementG(Gesture):
    """CreateNewElementG is responsible for creating
    new abstract elements and XMI-DI elements. These _actions_
    are defined per-profile."""
    def detectStart(self, e, button=Qt.LeftButton):
        if not (e.button() == button):
            return None

        lElements = self.editor.getSelectableItems(QPoint(e.sX,e.sY))
        activebtn = self.editor.getEditorForView().toolbar.selectedButton()
        if not activebtn:
            return
        if lElements!=[]:
            # add something to an existing element
            ge = lElements[0].graphElement
            activebtn.executeAction(hints=[[e.sX,e.sY]],parentge=ge)

        else:
            # add someting to a diagram
            coral.getTransactionManager().beginModifyCmd()
            try:
                activebtn.executeAction(hints=[[e.sX,e.sY]],parentge=None)                
            finally:
                coral.getTransactionManager().endModifyCmd()
    
class MoveSelectionG(Gesture):
    """MoveSelectionG is the gesture used when moving an element in the diagram
    if the user clicked an element that is not selected the element will first
    be selected and then moved
    """
    def detectStart(self,e):
        if not (e.button() == Qt.LeftButton):
            return None

        if self.editor.getSelectableItems(QPoint(e.sX,e.sY)):
            self.startState=e.state()
            self.state=1
            return self
        
        
    def start(self,e):
        logger.info("start moving selection")
        self.markers=[]
        m=self.editor.getSelectableItems(QPoint(e.sX,e.sY))
        if m!=[]:
            if m[0] not in self.editor.selection.currentSelection():
                if not (self.startState & Qt.ControlButton): 
                    self.editor.selection.clear()
                self.editor.selection.select(m[0])

        for p in self.editor.selection.currentSelection():
            ## BUG: This is ugly. We add something so we can prune it away later.
            if p.movable() or p.graphElement.oclIsKindOf(DI.GraphEdge):
                b=p.boundingRect()
                m=MarkerRectangle(p,b.x()+1,b.y()+1,b.width()-3,b.height()-3,self.editor.canvas())
                self.markers.append(m)
        self.movingStart=(e.sX,e.sY)
        self.state=2
        return self
    
    def onMouseMove(self,e):
        if (e.state() & Qt.LeftButton):
            if self.state==1:
                self.start(e)                

            # Check if all elements can be moved
            # Both horizontal and vertical directions
            # Change the mouse event accordingly
            for m in self.markers:
                nx=m.oX+e.sX-self.movingStart[0]
                ny=m.oY+e.sY-self.movingStart[1]
                if not (nx>=0):
                    e.sX = self.movingStart[0] - m.oX

                if not (nx <  m.canvas().width()  - m.width()):
                    e.sX = self.movingStart[0] - m.oX +  m.canvas().width()  - m.width() - 1 

                if not (ny>=0):
                    e.sY = self.movingStart[1] - m.oY
                if not (ny < m.canvas().height() - m.height()):
                    e.sY = self.movingStart[1] - m.oY +  m.canvas().height() - m.height() - 1 
                    
            for m in self.markers:
                nx=m.oX+e.sX-self.movingStart[0]
                ny=m.oY+e.sY-self.movingStart[1]
                if nx>=0 and nx <  m.canvas().width()  - m.width():
                    m.setX(m.oX+e.sX-self.movingStart[0])
                if ny>=0 and ny <  m.canvas().height()  - m.height():
                    m.setY(m.oY+e.sY-self.movingStart[1])

            if self.markers:
                self.markers[0].canvas().update()
        return self
    
    def onMouseRelease(self,e):
        from time import time
        start=time()
        if self.state==1:
            # this was a selection
            if not (self.startState & Qt.ControlButton):
                self.editor.selection.clear()
            g=Select1ElementG(self.editor)
            g.detectStart(e)
            return None

        logger.info("stop moving selection")
        presentations = {}
        graphElements = {}
        arrange = {}

        if not self.markers:
            return self.stop()

        coral.getTransactionManager().beginModifyCmd()
        try:
            m0 = self.markers[0]
            dx = m0.x() - m0.oX
            dy = m0.y() - m0.oY

            for m in self.markers:
                pres = m.presentation
                if isinstance(pres,GraphElementWidget):
                    presentations[pres] = True
                    graphElements[pres.graphElement] = True

            # Remove all widgets whose parents are transitively in
            # the set
            GraphElementWidget.shallowContainers(presentations)

            
            for pres in presentations:
                ## Skip graphedges
                if pres.graphElement.oclIsKindOf(DI.GraphEdge):
                    continue
                gep = pres.graphElement.position
                gep.x += dx
                gep.y += dy

            # Are we dropping on some other element?
            #
            lElements=self.editor.getSelectableItems(QPoint(e.sX,e.sY))

            parent2be=None
            children2be = self.editor.selection.currentSelection()

            #
            # If we don't do this, reparenting a graphnode and its
            # child reparents both of them separately. We only
            # want to reparent the top-most elements
            #
            c = dict.fromkeys(children2be)
            GraphElementWidget.shallowContainers(c)
            children2be = c.keys()


            
            for gew in lElements:
                #decide witch one should be the new parent
                if not parent2be and not gew in children2be:
                    parent2be = gew
                    break
            for gew in lElements:
                if gew.graphElement.semanticModel.oclIsKindOf(DI.SimpleSemanticModelElement):
                    return self.stop()
                if gew in children2be:
                    continue
                if gew.z() > parent2be.z() and not gew.graphElement.getExtension(gew.graphElement.semanticModel.element.__type__()).isMinorElement(element=gew.graphElement.semanticModel.element, xparent=gew.graphElement.container):
                    uiw = gew.graphElement.ui.__getPyValue__()
                    for childwidget in children2be:
                        if uiw.acceptChild(childwidget.graphElement.semanticModel.element):
                            parent2be = gew

            diagram_root=None
            # If so, insert it into the element, otherwise on diagram
            if parent2be:
                parentelement = parent2be.graphElement.semanticModel.element
            else:
                parentelement = None
                for gew in children2be:
                    diagram_root =  DISUPP.findDiagramOfElement(gew.graphElement)
                    parentelement = diagram_root.owner.element
                    
                    if parentelement:
                        break
                
            assert parentelement
            allParents = coral.Element__getMyselfAndParents__(parentelement)
            for gew in children2be:
                if gew.graphElement.semanticModel.oclIsKindOf(DI.SimpleSemanticModelElement):
                    return self.stop()
                child_di = gew.graphElement
                child_element = child_di.semanticModel.element
                child_parent = child_element.__parent__()
                if diagram_root or parent2be.graphElement.ui.__getPyValue__().acceptChild(child_element):
                    # do not make a circular ownership
                    if child_element in allParents:
                        continue

                    if child_parent == parentelement:
                        continue
                    if diagram_root == child_di.__parent__():
                        continue
                    logger.debug("REPARENTING: %s\n TO %s\n OLD PARENT %s " %
                                 (unicode(child_element),
                                  unicode(parentelement),
                                  unicode(child_parent)))

                    gew.graphElement.ui.__getPyValue__().reparent(child_element, parentelement)
                    ps_x, ps_y = coral.XMIDI_LocalToGlobalPosition(child_di, 0, 0)
                    if diagram_root:
                        addForReparenting(diagram_root, child_di, ps_x, ps_y)
                    else:
                        addForReparenting(parent2be.graphElement, child_di, ps_x, ps_y) 

            putSelectedElementsOnTop(self.editor)
        finally:
            coral.getTransactionManager().endModifyCmd()

        stop=time()
        logger.debug("Moving delay:"+unicode(stop-start))
        return self.stop()
        
    def stop(self):
        c=None
        for m in self.markers:
            m.hide()
            c=m.canvas()
            m.setCanvas(None)

        self.markers=[]

        
        #self.editor.calculateCanvasSize()
        if c:
            c.update()
        return None
    

class SelectBBoxG(Gesture):
    """SelectBBoxG is the gesture used when not clicking on an element
    and the user wants to select multiple elements"""
    def detectStart(self,e):
        if not (e.button() == Qt.LeftButton):
            return None
        if self.editor.getSelectableItems(QPoint(e.sX,e.sY))==[]:
            return self.start(e)
        else:
            return None
        
    def start(self,e):
        logger.info("start bbox")
        if not (e.state() &  Qt.ControlButton):
                self.editor.selection.clear()
        self.markers=[MarkerRectangle(None,e.sX,e.sY,0,0,self.editor.canvas(),plus=0)]
        return self
    
    def stop(self):
        logger.info("stop bbox")
        self.markers[0].setCanvas(None)
        self.markers=[]
        self.editor.canvas().update()
        return None
    
    def onMouseMove(self,e):
        if (e.state() & Qt.LeftButton):
            oldx,oldy,oldw,oldh=self.markers[0].x(),self.markers[0].y(),self.markers[0].width(),self.markers[0].height()
            x,y,w,h = oldx, oldy, oldw, oldh
            if e.sX<self.markers[0].oX:
                x=e.sX
                w=self.markers[0].oX-x
            else:
                x=self.markers[0].oX
                w=e.sX-x
            if e.sY<self.markers[0].oY:
                y=e.sY
                h=self.markers[0].oY-y
            else:
                h=e.sY-y

            if oldx != x or oldy != y or oldw != w or oldh != h:
                self.markers[0].setX(x)
                self.markers[0].setY(y)
                self.markers[0].setSize(w,h)
                self.editor.canvas().update()
        return self
        
    def onMouseRelease(self,e):
        self.editor.selection.selectList(
            self.editor.getSelectableItems(self.markers[0].boundingRect()))
        return self.stop()

class PanView(Gesture):
    """Pan the view."""
    def detectStart(self,e):
        if not (e.button() == Qt.MidButton):
            return None

        self.startCoords = (e.sX, e.sY)
        return self

    def stop(self):
        return None

    def onMouseMove(self,e):
        speed = 3
        dx = (e.sX - self.startCoords[0]) * speed
        dy = (e.sY - self.startCoords[1]) * speed

        cx = self.editor.contentsX()
        cy = self.editor.contentsY()

        # We have to make sure new scroll position is ok, otherwise
        # we get a "shaky" movement
        if cx + dx < 0:
            dx = -cx
        if cx + dx + self.editor.visibleWidth() > self.editor.canvas().width():
            dx = self.editor.canvas().width() - cx - self.editor.visibleWidth()

        if cy + dy < 0:
            dy = -cy
        if cy + dy + self.editor.visibleHeight() > self.editor.canvas().height():
            dy = self.editor.canvas().height() - cy - self.editor.visibleHeight()

        self.editor.setContentsPos(cx + dx, cy + dy)

        # Reset the start value.
        self.startCoords = (e.sX + dx, e.sY + dy)
        return self

    def onMouseRelease(self, e):
        return None


class CreateNewAssociationG(Gesture):
    def __init__(self,editor):
        Gesture.__init__(self, editor)
        self.gecls1 = None
        self.gecls2 = None
        self.startpoint = None

        ## For rubberband :)        
        self.lines=[]
        
    def detectStart(self,e):
        """ Check if we click on a class with \"create associaton\" selected """
        if not (e.button() == Qt.LeftButton):
            return None
        
        self.firstclick = True 
        self.wpts = []
        self.qpa = QPointArray(1)
        
        self.startpoint = QPoint(e.sX, e.sY)
        self.qpa.setPoint(0,self.startpoint)
        self.qpa.putPoints(self.qpa.size(),[500,500])
        self.activebtn = self.editor.getEditorForView().toolbar.selectedButton()
        if not self.activebtn or not self.activebtn.action.handler:
            return None
        createEdge = self.activebtn.action.handler.createEdgeOrNot()

        if not createEdge:
            return None

        lElements=self.editor.getSelectableItems(QPoint(e.sX,e.sY))
        if lElements==[]:
            return None


        clswidget = None
        firstone = True
        for gew in lElements:
            ## decide which widget is the topmost in clicked point
            ## and assign it's graphElement to gecls1
            if gew.graphElement.oclIsKindOf(DI.GraphElement):
                if firstone:
                    clswidget = gew
                    firstone = False
                elif gew.z() > clswidget.z():
                    clswidget = gew
        if not clswidget:
            return None

        self.gecls1 = clswidget.graphElement
        
        self.createNewRubberBandLine(e.sX,e.sY)
        
        return self
    
    def createNewRubberBandLine(self,x,y):
        c = self.editor.canvas()
        l=RubberBandLine(c)
        self.lines.append(l)
        l.setPoints(x,y,x,y)
        l.setZ(60000) # Rubberband always on top
        l.setPen(QPen(Qt.DashLine))
        l.update()
        l.show()
        c.update()
        return l

        
    def onMouseMove(self,e):
        #print "running move in gesture"
        """ If |wp|>1 then move around the last created with movement"""
        if self.lines:
            l=self.lines[-1]
            l.setPoints(l.startPoint().x(),l.startPoint().y(),e.sX, e.sY)
            l.update()
            l.show()
            self.editor.canvas().update()
        return self


    
    def onMouseRelease(self,e):
        """ when released outside a class, create next wp. 
        if over a class, end the gesture and complete the 
        association to that class. ClassStart can be the same as ClassEnd. """

        self.qpa.putPoints(self.qpa.size(),[e.sX,e.sY])
    

        if not self.editor.getEditorForView().toolbar.selectedButton():
            return None


        lElements=self.editor.getSelectableItems(QPoint(e.sX,e.sY))
        
        self.wpts.append([e.sX,e.sY])

        if not lElements:
            self.createNewRubberBandLine(e.sX,e.sY)
            return self

        if self.firstclick:
            self.firstclick = False
            return self

        clswidget = None
        for gew in lElements:
            ## decide which widget is the topmost in clicked point
            ## and asign it's graphElement to gecls2
            if gew.graphElement.oclIsKindOf(DI.GraphElement):
                if not clswidget:
                    clswidget = gew
                elif gew.z() > clswidget.z():
                    clswidget = gew
        if clswidget:
            self.gecls2 = clswidget.graphElement
            return self.stop()
        return self

    def createAssociation(self):
        """The action will receive the waypoints and the classes"""
        coral.getTransactionManager().beginModifyCmd()
        try:
            # self-association special case... create a loop at the bottom
            # right
            if len(self.wpts)==2 and (self.gecls1 == self.gecls2):
                left,top = DISUPP.localToGlobalPosition(self.gecls1,0.0,0.0)
                right = left+float(self.gecls1.size.width)-1.0
                bottom = top+float(self.gecls1.size.height)-1.0
                waypoints = [ DI.Point(x=right,y=bottom-50.0),
                              DI.Point(x=right+50.0,y=bottom-50.0),
                              DI.Point(x=right+50.0,y=bottom+50.0),
                              DI.Point(x=right-50.0,y=bottom+50.0),
                              DI.Point(x=right-50.0,y=bottom) ]
            else:
                # send ALL waypoints, including start and end, as hints
                waypoints = [ DI.Point(x=float(a),y=float(b)) for (a,b) in self.wpts ]
            self.activebtn.executeAction(hints=[self.gecls1, self.gecls2, waypoints])
        finally:
            coral.getTransactionManager().endModifyCmd()
        self.editor.canvas().update()

    def stop(self):
        for l in self.lines:
            l.setCanvas(None)
        self.lines=[]
        self.editor.canvas().update()
        self.createAssociation()
        return None    

        
class CreateNewWPG(Gesture):
    def __init__(self,editor):
        Gesture.__init__(self, editor)
        ## Store the segment where new point should be created
        self.newpointat = 1
        
    def detectStart(self,e):
        self.newpointat = 1

        if not (e.state() & (Qt.LeftButton + Qt.ShiftButton)):
            return None
        
        lElements=self.editor.getSelectableItems(QPoint(e.sX,e.sY))
        
        if not lElements or not lElements[0].relinkable():
            return None

        if not (e.state() & Qt.ControlButton):
            self.editor.selection.clear()
            
            if ((e.state() & Qt.ControlButton) and 
                lElements[0] in self.editor.selection.currentSelection()):
                self.editor.selection.deSelect(lElements[0])
            else:
                self.editor.selection.select(lElements[0])

        p=lElements[0]
        self.gew = p
        originalpoint = [e.sX, e.sY]

        xo,yo = DISUPP.localToGlobalPosition(self.gew.graphElement,0.0,0.0)
        
        segments = xrange(0, len(p.points)-1)
        mindist = 11
        for s in segments:
            # Horrible glue due to calcDistFromPoint2Line
            class _f:
                def __init__(self, x, y):
                    self.x = x
                    self.y = y
            xy = p.points[s]
            ps = _f(xy[0] + xo, xy[1] + yo)

            xy = p.points[s+1]
            ps1 = _f(xy[0] + xo, xy[1] + yo)

            op = _f(originalpoint[0], originalpoint[1])

            _,_,d = DISUPP.calcDistFromPoint2Line(ps, ps1, op)
            if d != -1 and d < mindist:
                mindist = d
                self.newpointat = (s+1)
        if mindist > 10:
            return None
        else:
            coral.getTransactionManager().beginModifyCmd()
            try:
                self.gew.graphElement.waypoints.insert(DI.Point(x=float(originalpoint[0]) - xo, y=float(originalpoint[1]) - yo), self.newpointat)
            finally:
                coral.getTransactionManager().endModifyCmd()

            self.editor.selection.deSelect(lElements[0])
            self.editor.selection.select(lElements[0])

            self.gew.updateWidget()
            self.editor.canvas().update()
            return self

    
    def onMouseMove(self,e):
        if e.state() & Qt.ShiftButton:
            xo,yo = DISUPP.localToGlobalPosition(self.gew.graphElement,0.0,0.0)

            # BUG transaction missing?
            thepoint = self.gew.graphElement.waypoints[self.newpointat]
            thepoint.x = float(e.sX - xo)
            thepoint.y = float(e.sY - yo)

            self.gew.updateWidget()
            self.editor.canvas().update()
        return self


    def onMouseRelease(self,e):
        self.gew.updateWidget()
        self.editor.canvas().update()        
        return self.stop()



    def stop(self):
        self.editor.canvas().update()
        return None


class MovePointG(Gesture):
    def __init__(self,editor):
        Gesture.__init__(self,editor)
        self.link=None
        self.point=None
        self.gew = None
        self.pindex = 0

        self.relink = False

        self.oldpoints = []

    def detectStart(self,e):
        self.link=None
        self.point=None
        self.gew = None
        self.pindex = 0

        self.relink=False

        if not (e.button()== Qt.LeftButton):
            return None

        lElements=self.editor.getSelectableItems(QPoint(e.sX,e.sY))
        
        if lElements==[] or not lElements[0].relinkable():
            return None

        if not (e.state() & Qt.ControlButton):
            self.editor.selection.clear()
            
            if ((e.state() & Qt.ControlButton) and 
                lElements[0] in self.editor.selection.currentSelection()):
                self.editor.selection.deSelect(lElements[0])
            else:
                self.editor.selection.select(lElements[0])
            
        p=lElements[0]
        self.gew = p
        i=0
        xo,yo = DISUPP.localToGlobalPosition(self.gew.graphElement,0.0,0.0)
        mx = e.sX - xo
        my = e.sY - yo
        for point in p.points:
            if abs(point[0] - mx) < 10 and abs(point[1] - my) < 10:
                self.point=point
                self.link=p

                # begin transaction here, it will be ended when the
                # gesture ends
                coral.getTransactionManager().beginModifyCmd()

                if i>0 and i<len(p.points)-1:
                    self.pindex = i
                else:
                    self.relink = True
                    self.pindex = i

                return self
            i += 1

        return None

    def onMouseMove(self,e):
        if self.point:
            xo,yo = DISUPP.localToGlobalPosition(self.gew.graphElement,0.0,0.0)
            self.point[0] = e.sX - xo
            self.point[1] = e.sY - yo

            ui = self.gew.graphElement.ui.__getPyValue__()
            wpi = self.gew.graphElement.waypoints[self.pindex]
            if ui.relinkable(): 
                wpi.x = float(self.point[0])
                wpi.y = float(self.point[1])
            else:
                if not (self.pindex == 0 or self.pindex == len(self.gew.graphElement.waypoints)-1):
                    wpi.x = float(self.point[0])
                    wpi.y = float(self.point[1])

            layouter = self.gew.graphElement.layouter.__getPyValue__()
            if layouter and isinstance(layouter,GraphEdgeLayouter):
                from coral.modeler.diagram import layout
                sz = layout._SizeHinter()
                layouter.layoutEdgeAndLabels(sz, self.pindex!=0,self.pindex!=len(self.gew.graphElement.waypoints)-1)
                    
            self.gew.updateWidget()
            self.editor.canvas().update()
        return self

    def onMouseRelease(self,e):
        try:
            if self.relink:
                edge = self.gew.graphElement
                lElements=self.editor.getSelectableItems(QPoint(e.sX,e.sY))

                modelnode = None
                relink_to_node = None                
                ui = edge.ui.__getPyValue__()
                # all transitive childnodes for the edge to be relinked
                allparts = edge.__getAllParts__()
                    
                for gew in lElements:
                    ## Don't relink to yourself, allow target to be all GraphElements
                    if gew == self.gew and gew.graphElement in allparts:
                        continue
                    if gew.graphElement.oclIsKindOf(DI.GraphElement):
                        if not (ui.relinkable() and ui.accepts(DISUPP.findUpwardsModelElement(gew.graphElement))):
                            continue
                        else:
                            modelnode = DISUPP.findUpwardsModelElement(gew.graphElement)
                        relink_to_node = gew.graphElement
                        break


                modeledge = edge.semanticModel.element

                if self.pindex == 0:
                    end = 0
                else:
                    end = 1
                    
                if modelnode:

                    while relink_to_node not in modelnode.xmidi_get_graphelements():
                        relink_to_node = relink_to_node.container

                    if ui.relinkable() and ui.accepts(modelnode):
                        if relink_to_node.semanticModel.element == modelnode:
                            edge.anchor[end].graphElement = relink_to_node                    
                        ui.relink(modeledge, modelnode, end)

        finally:
            return self.stop()


    def stop(self):
        coral.getTransactionManager().endModifyCmd()
        self.gew.updateWidget()
        self.editor.canvas().update()
        return None
    
class ResizeSelectionG(Gesture):
    """ResizeSelectionG is the gesture used when we have a selection and
    the user wants to resize it
    """
    resizeN=1
    resizeE=2
    resizeW=4
    resizeS=8
    resizeNE=resizeN | resizeE
    resizeNW=resizeN | resizeW
    resizeSE=resizeS | resizeE
    resizeSW=resizeS | resizeW

    def detectStart(self,e):
        if not (e.button()== Qt.LeftButton):
            return None
        self.resizing=0
        coll = self.editor.canvas().collisions(QPoint(e.sX,e.sY))
        if not coll or not isinstance(coll[0],NodeHandleWidget):
            return None

        # if all elements in the selection are not resizable, don't start
        # resize
        ok = False
        for p in self.editor.selection.currentSelection():
            if p.resizable():
                ok = True
                break
        if not ok:
            return None
        
        self.resizing = coll[0].role
            
        if self.resizing:
            self.resizingStart=(e.sX,e.sY)
            self.markers=[]
            for p in self.editor.selection.currentSelection():
                if p.resizable():
                    self.markers.append(
                        MarkerRectangle(p,p.x(),p.y(),
                                        p.width(),p.height(),
                                        self.editor.canvas()))
            self.editor.canvas().update()
            return self
        else:
            return None

    def onMouseRelease(self,e):
        coral.getTransactionManager().beginModifyCmd()
        try:
            for m in self.markers:
                pres = m.presentation
                updx = m.x()-pres.x()
                updy = m.y()-pres.y()
                downdx = m.x()+m.width() - (pres.x()+pres.width())
                downdy = m.y()+m.height() - (pres.y()+pres.height())
                ge = pres.graphElement

                ge.position.x = ge.position.x+updx
                ge.position.y = ge.position.y+updy
                ge.size.width = m.width()
                ge.size.height = m.height()

        finally:
            coral.getTransactionManager().endModifyCmd()
                
        self.stop()
        return None
                
    def stop(self):
        logger.info("stop resizing selection")
        c = None
        for m in self.markers:
            m.hide()
            c=m.canvas()
            m.setCanvas(None)
        self.markers=[]
        #self.editor.calculateCanvasSize()
        if c: ## Added this to prevent crash (TLU)
            c.update()
        return None


    def onMouseMove(self,e):       
        for m in self.markers:
            ## BUG: This sometimes causes unrealistic barriers
            ## to what sizes an element can be
            magic=20
            if m.oH<magic or m.oW<magic:
                #we are resizing an element smaller than the magic number (i.e. fork/join)
                magic=8
            if self.resizing & self.resizeN:
                iY=max(-(m.oH-magic),self.resizingStart[1]-e.sY)
                if m.oY-iY>=0:
                    m.setY(m.oY-iY)
                    m.setSize(m.width(),m.oH+iY)
            if self.resizing & self.resizeS:
                iY=max(-(m.oH-magic),e.sY-self.resizingStart[1])
                if m.y()+iY+m.oH<m.canvas().height():
                    m.setSize(m.width(),iY+m.oH)
            if self.resizing & self.resizeW:
                iX=max(-(m.oW-magic),self.resizingStart[0]-e.sX)
                if m.oX-iX>=0:
                    m.setX(m.oX-iX)
                    m.setSize(m.oW+iX,m.height())
            if self.resizing & self.resizeE:
                iX=max(-(m.oW-magic),e.sX-self.resizingStart[0])
                if m.x()+iX+m.oW< m.canvas().width():
                    m.setSize(iX+m.oW,m.height())
        self.editor.canvas().update()
        return self

        

                
class Selection:
    "Selection manages the selection of items in a diagram editor"

    def __init__(self,editor):
        self.items={}
        self.editor=editor

    def select(self,i,focus=1, create_primary_selection=True):
        "Add presentation i to the current selection"
        #if i.alsoSelect():
        #   i=i.alsoSelect()

        if (i.selectable() and i not in self.items):
            if isinstance(i,GraphNodeWidget):
                handles = [ NodeHandleWidget(i,role,i.canvas()) for role in
                            (NodeHandleWidget.RESIZE_N,
                             NodeHandleWidget.RESIZE_E,
                             NodeHandleWidget.RESIZE_W,
                             NodeHandleWidget.RESIZE_S,
                             NodeHandleWidget.RESIZE_NE,
                             NodeHandleWidget.RESIZE_NW,
                             NodeHandleWidget.RESIZE_SE,
                             NodeHandleWidget.RESIZE_SW) ]
            elif isinstance(i,GraphEdgeWidget):
                handles = [ EdgeHandleWidget(i,n,i.canvas()) for n in xrange(len(i.points)) ]
            elif isinstance(i,PolylineWidget):
                handles = [ EdgeHandleWidget(i,n,i.canvas()) for n in xrange(len(i.points)) ]
            else:
                logger.warn("CANNOT SELECT " +  str(i))
                return
                
            self.items[i] = handles
            i.selection = handles
            for handle in handles:
                handle.show()
                
        if self.items and focus:
            logger.info("FOCUS "+unicode(focus))
            #self.editor.mainWindow.actions["selectionGroup"].setEnabled(1)
            if not isinstance(i,LeafElementWidget):
                if i.graphElement.semanticModel.element:
                    self.editor.tellOthersToFocusOn(i.graphElement.semanticModel.element,i)

        #if create_primary_selection:
        #    self.editor.copySelectedWidgets(True)
            
    def deSelect(self,i):
        "Remove presentation i from the current selection"
        if i in self.items:
            i.selection = []
            for handle in self.items[i]:
                handle.setCanvas(None)
            del self.items[i]
            # Ivan: Copy is not cheap in Coral, so we do not want
            # to have automatic copy of selection a la X11
            # The user should invoke Edit Copy like in Windows
            # I said. 
            # self.editor.copySelectedWidgets(True)
        if not self.items:
            #self.editor.mainWindow.actions["selectionGroup"].setEnabled(0)
            pass


    def clear(self):
        "Deselects all selected presentations"
        for i in self.items:
            i.selection = []
            for handle in self.items[i]:
                handle.setCanvas(None)
        self.items = {}
        #self.editor.copySelectedWidgets(True)
        #self.editor.mainWindow.actions["selectionGroup"].setEnabled(0)
        
    def currentSelection(self):
        "Returns a list with the selected presentations"
        return self.items.keys()

    def selectList(self,list):
        # we sort the list of items to select by z
        # so the parents will be processed first
        fl=[]
        for i in list:
            if not isinstance(i,MarkerRectangle) and i not in self.items:
                fl.append(i)
        fl.sort(lambda o1,o2: cmp(o1.z(),o2.z()))
        for i in fl[:-1]:
            self.select(i,focus=0, create_primary_selection = False)
        if fl[-1:]:            
            self.select(fl[-1])
                       
