#
#  GraphicsView.py
#  GraphicsBindings
#
#  Converted by u.fiedler on feb 2005
#  with great help from Bob Ippolito - Thank you Bob!
#
#  The original version was written in Objective-C by Malcolm Crawford
#  http://homepage.mac.com/mmalc/CocoaExamples/controllers.html

PropertyObservationContext = 1091
GraphicsObservationContext = 1092
SelectionIndexesObservationContext = 1093


import objc
from objc import super
from Cocoa import NSView, NSKeyValueObservingOptionNew, NSKeyValueObservingOptionOld
from Cocoa import NSKeyValueChangeNewKey, NSKeyValueChangeOldKey, NSUnionRect, NSMakeRect
from Cocoa import NSDrawLightBezel, NSBezierPath, NSNotFound, NSIntersectsRect, NSColor
from Cocoa import NSShiftKeyMask, NSIndexSet, NSInsetRect
from Circle import Circle

class GraphicsView (NSView):
    graphicsContainer = objc.ivar('graphicsContainer')
    graphicsKeyPath   = objc.ivar('graphicsKeyPath')

    selectionIndexesContainer = objc.ivar('selectionIndexesContainer') # GraphicsArrayController
    selectionIndexesKeyPath   = objc.ivar('selectionIndexesKeyPath')

    oldGraphics = objc.ivar('oldGraphics')

    def exposedBindings(self):
        return ["graphics", "selectedObjects"]

    def initWithFrame_(self, frameRect):
        return super(GraphicsView, self).initWithFrame_(frameRect)

    def graphics(self):
        if not self.graphicsContainer: return None
        return self.graphicsContainer.valueForKeyPath_(self.graphicsKeyPath)

    def selectionIndexes(self):
        if not self.selectionIndexesContainer: return None
        return self.selectionIndexesContainer.valueForKeyPath_(self.selectionIndexesKeyPath)

    def startObservingGraphics_(self, graphics):
        if not graphics: return
        # Register to observe each of the new graphics, and
        # each of their observable properties -- we need old and new
        # values for drawingBounds to figure out what our dirty rect
        for newGraphic in graphics:
            # Register as observer for all the drawing-related properties
            newGraphic.addObserver_forKeyPath_options_context_(
                self, "drawingBounds", (NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld),
                PropertyObservationContext)
            keys = Circle.keysForNonBoundsProperties()
            for key in keys:
                newGraphic.addObserver_forKeyPath_options_context_(
                    self, key, 0, PropertyObservationContext)

    def stopObservingGraphics_(self, graphics):
        if graphics is None: return
        for graphic in graphics:
            for key in graphic.class__().keysForNonBoundsProperties():
                graphic.removeObserver_forKeyPath_(self, key)
            graphic.removeObserver_forKeyPath_(self, "drawingBounds")

    def bind_toObject_withKeyPath_options_(self, bindingName, observableObject, observableKeyPath, options):
        if bindingName == "graphics":
            self.graphicsContainer = observableObject
            self.graphicsKeyPath = observableKeyPath
            self.graphicsContainer.addObserver_forKeyPath_options_context_(
                    self, self.graphicsKeyPath, (NSKeyValueObservingOptionNew |
                    NSKeyValueObservingOptionOld), GraphicsObservationContext)
            self.startObservingGraphics_(self.graphics())

        elif bindingName == "selectionIndexes":
            self.selectionIndexesContainer = observableObject
            self.selectionIndexesKeyPath = observableKeyPath
            self.selectionIndexesContainer.addObserver_forKeyPath_options_context_(
                self, self.selectionIndexesKeyPath, 0, SelectionIndexesObservationContext)
        self.setNeedsDisplay_(True)

    def unbind_(self, bindingName):
        if bindingName == "graphics":
            self.graphicsContainer.removeObserver_forKeyPath_(self, self.graphicsKeyPath)
            self.graphicsContainer = None
            self.graphicsKeyPath = None
        if bindingName == "selectionIndexes":
            self.selectionIndexesContainer.removeObserver_forKeyPath_(self, self.selectionIndexesKeyPath)
            self.seletionIndexesContainer = None
            self.selectionIndexesKeyPath = None
        self.setNeedsDisplay_(True)

    def observeValueForKeyPath_ofObject_change_context_(self, keyPath, object, change, context):
        if context == GraphicsObservationContext:
            # Should be able to use
            # NSArray *oldGraphics = [change objectForKey:NSKeyValueChangeOldKey];
            # etc. but the dictionary doesn't contain old and new arrays...??
            newGraphics = set(object.valueForKeyPath_(self.graphicsKeyPath))
            onlyNew = newGraphics - set(self.oldGraphics or [])
            self.startObservingGraphics_(onlyNew)

            if self.oldGraphics:
                removed = set(self.oldGraphics) - newGraphics
                self.stopObservingGraphics_(removed)

            self.oldGraphics = newGraphics

            # could check drawingBounds of old and new, but...
            self.setNeedsDisplay_(True)
            return

        if context == PropertyObservationContext:
            updateRect = (0,)
            # Note: for Circle, drawingBounds is a dependent key of all the other
            # property keys except color, so we'll get this anyway...
            if keyPath == "drawingBounds":
                newBounds = change.objectForKey_(NSKeyValueChangeNewKey)
                oldBounds = change.objectForKey_(NSKeyValueChangeOldKey)
                updateRect = NSUnionRect(newBounds, oldBounds)
            else:
                updateRect = object.drawingBounds()
            updateRect = NSMakeRect(updateRect.origin.x-1.0,
                                updateRect.origin.y-1.0,
                                updateRect.size.width+2.0,
                                updateRect.size.height+2.0)
            self.setNeedsDisplay_(True)
            return

        if context == SelectionIndexesObservationContext:
            self.setNeedsDisplay_(True)
            return

    def drawRect_(self, rect):
        myBounds = self.bounds()
        NSDrawLightBezel(myBounds, myBounds) # AppKit Function
        clipRect = NSBezierPath.bezierPathWithRect_(NSInsetRect(myBounds, 2.0, 2.0))
        clipRect.addClip()

        # Draw graphics
        graphicsArray = self.graphics()
        if graphicsArray:
            for graphic in graphicsArray:
                graphicDrawingBounds = graphic.drawingBounds()
                if NSIntersectsRect(rect, graphicDrawingBounds):
                    graphic.drawInView_(self)

        # Draw a red box around items in the current selection.
        # Selection should be handled by the graphic, but this is a
        # shortcut simply for display.

        currentSelectionIndexes = self.selectionIndexes() # ist das wir ein Array im Indezes?
        if currentSelectionIndexes != None:
            path = NSBezierPath.bezierPath()
            index = currentSelectionIndexes.firstIndex()
            while index != NSNotFound:
                graphicDrawingBounds = graphicsArray[index].drawingBounds()
                if NSIntersectsRect(rect, graphicDrawingBounds):
                    path.appendBezierPathWithRect_(graphicDrawingBounds)
                index = currentSelectionIndexes.indexGreaterThanIndex_(index)

            NSColor.redColor().set()
            path.setLineWidth_(1.5)
            path.stroke()


        # Fairly simple just to illustrate the point
    def mouseDown_(self, event):
        # find out if we hit anything
        p = self.convertPoint_fromView_(event.locationInWindow(), None)
        for aGraphic in self.graphics():
            if aGraphic.hitTest_isSelected_(p, False):
                break; # aGraphic soll spaeter einen Wert haben, falls es getroffene gibt!
        else:
            aGraphic = None

        # if no graphic hit, then if extending selection do nothing
        # else set selection to nil
        if aGraphic is None:
            if not event.modifierFlags() & NSShiftKeyMask:
                self.selectionIndexesContainer.setValue_forKeyPath_(None, self.selectionIndexesKeyPath)
            return

        # graphic hit
        # if not extending selection (Shift key down) then set
        # selection to this graphic
        # if extending selection, then:
        # - if graphic in selection remove it
        # - if not in selection add it
        graphicIndex = self.graphics().index(aGraphic)
        if not event.modifierFlags() & NSShiftKeyMask:
            selection = NSIndexSet.indexSetWithIndex_(graphicIndex)
        else:
            if  self.selectionIndexes().containsIndex_(graphicIndex):
                selection = self.selectionIndexes().mutableCopy()
                selection.removeIndex_(graphicIndex)
            else:
                selection = self.selectionIndexes().mutableCopy()
                selection.addIndex_(graphicIndex)

        self.selectionIndexesContainer.setValue_forKeyPath_(selection, self.selectionIndexesKeyPath)


GraphicsView.exposeBinding_("graphics")
GraphicsView.exposeBinding_("selectionIndexes")
