# Miro - an RSS based video player application
# Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011
# Participatory Culture Foundation
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
#
# In addition, as a special exception, the copyright holders give
# permission to link the code of portions of this program with the OpenSSL
# library.
#
# You must obey the GNU General Public License in all respects for all of
# the code used other than OpenSSL. If you modify file(s) with this
# exception, you may extend this exception to your version of the file(s),
# but you are not obligated to do so. If you do not wish to do so, delete
# this exception statement from your version. If you delete this exception
# statement from all source files in the program, then also delete it here.

"""miro.plat.frontend.widgets.drawing -- Draw on Views."""

import math

from Foundation import *
from AppKit import *
from Quartz import *
from objc import YES, NO, nil

class ImageSurface:
    """See https://develop.participatoryculture.org/index.php/WidgetAPI for a description of the API for this class."""
    def __init__(self, image):
        """Create a new ImageSurface."""
        self.image = image.nsimage.copy()
        self.width = image.width
        self.height = image.height

    def get_size(self):
        return self.width, self.height

    def draw(self, context, x, y, width, height, fraction=1.0):
        if self.width == 0 or self.height == 0:
            return
        current_context = NSGraphicsContext.currentContext()
        current_context.setShouldAntialias_(YES)
        current_context.setImageInterpolation_(NSImageInterpolationHigh)
        current_context.saveGraphicsState()
        flip_context(y + height)
        dest_rect = NSMakeRect(x, 0, width, height)
        if self.width >= width and self.height >= height:
            # drawing to area smaller than our image
            source_rect = NSMakeRect(0, 0, width, height)
            self.image.drawInRect_fromRect_operation_fraction_(
                dest_rect, source_rect, NSCompositeSourceOver, fraction)
        else:
            # drawing to area larger than our image.  Need to tile it.
            NSColor.colorWithPatternImage_(self.image).set()
            current_context.setPatternPhase_(
                    self._calc_pattern_phase(context, x, y))
            NSBezierPath.fillRect_(dest_rect)
        current_context.restoreGraphicsState()

    def draw_rect(self, context, dest_x, dest_y, source_x, source_y, width,
            height, fraction=1.0):
        if width == 0 or height == 0:
            return
        current_context = NSGraphicsContext.currentContext()
        current_context.setShouldAntialias_(YES)
        current_context.setImageInterpolation_(NSImageInterpolationHigh)
        current_context.saveGraphicsState()
        flip_context(dest_y + height)
        dest_y = 0
        dest_rect = NSMakeRect(dest_x, dest_y, width, height)
        source_rect = NSMakeRect(source_x, self.height-source_y-height,
                                 width, height)
        self.image.drawInRect_fromRect_operation_fraction_(
                dest_rect, source_rect, NSCompositeSourceOver, fraction)
        current_context.restoreGraphicsState()

    def _calc_pattern_phase(self, context, x, y):
        """Calculate the pattern phase to draw tiled images.

        When we draw with a pattern, we want the image in the pattern to start
        at the top-left of where we're drawing to.  This function does the
        dirty work necessary.

        :returns: NSPoint to send to setPatternPhase_
        """
        # convert to view coords
        view_point = NSPoint(context.origin.x + x, context.origin.y + y)
        # convert to window coords, which is setPatternPhase_ uses
        return context.view.convertPoint_toView_(view_point, nil)

def convert_cocoa_color(color):
    rgb = color.colorUsingColorSpaceName_(NSDeviceRGBColorSpace)
    return (rgb.redComponent(), rgb.greenComponent(), rgb.blueComponent())

def convert_widget_color(color, alpha=1.0):
    return NSColor.colorWithDeviceRed_green_blue_alpha_(color[0], color[1], 
                                                        color[2], alpha)
def flip_context(height):
    """Make the current context's coordinates flipped.

    This is useful for drawing images, since they use the normal cocoa
    coordinates and we use flipped versions.

    :param height: height of the current area we are drawing to.
    """
    xform = NSAffineTransform.transform()
    xform.translateXBy_yBy_(0, height)
    xform.scaleXBy_yBy_(1.0, -1.0)
    xform.concat()

class DrawingStyle(object):
    """See https://develop.participatoryculture.org/index.php/WidgetAPI for a description of the API for this class."""
    def __init__(self, bg_color=None, text_color=None):
        self.use_custom_style = True
        if text_color is None:
            self.text_color = self.default_text_color
        else:
            self.text_color = convert_cocoa_color(text_color)
        if bg_color is None:
            self.bg_color = self.default_bg_color
        else:
            self.bg_color = convert_cocoa_color(bg_color)

    default_text_color = convert_cocoa_color(NSColor.textColor())
    default_bg_color = convert_cocoa_color(NSColor.textBackgroundColor())

class DrawingContext:
    """See https://develop.participatoryculture.org/index.php/WidgetAPI for a description of the API for this class."""
    def __init__(self, view, drawing_area, rect):
        self.view = view
        self.path = NSBezierPath.bezierPath()
        self.color = NSColor.blackColor()
        self.width = drawing_area.size.width
        self.height = drawing_area.size.height
        self.origin = drawing_area.origin
        if drawing_area.origin != NSZeroPoint:
            xform = NSAffineTransform.transform()
            xform.translateXBy_yBy_(drawing_area.origin.x, 
                    drawing_area.origin.y)
            xform.concat()

    def move_to(self, x, y):
        self.path.moveToPoint_(NSPoint(x, y))

    def rel_move_to(self, dx, dy):
        self.path.relativeMoveToPoint_(NSPoint(dx, dy))

    def line_to(self, x, y):
        self.path.lineToPoint_(NSPoint(x, y))

    def rel_line_to(self, dx, dy):
        self.path.relativeLineToPoint_(NSPoint(dx, dy))

    def curve_to(self, x1, y1, x2, y2, x3, y3):
        self.path.curveToPoint_controlPoint1_controlPoint2_(
                NSPoint(x3, y3), NSPoint(x1, y1), NSPoint(x2, y2))

    def rel_curve_to(self, dx1, dy1, dx2, dy2, dx3, dy3):
        self.path.relativeCurveToPoint_controlPoint1_controlPoint2_(
                NSPoint(dx3, dy3), NSPoint(dx1, dy1), NSPoint(dx2, dy2))

    def arc(self, x, y, radius, angle1, angle2):
        angle1 = (angle1 * 360) / (2 * math.pi)
        angle2 = (angle2 * 360) / (2 * math.pi)
        center = NSPoint(x, y)
        self.path.appendBezierPathWithArcWithCenter_radius_startAngle_endAngle_(center, radius, angle1, angle2)

    def arc_negative(self, x, y, radius, angle1, angle2):
        angle1 = (angle1 * 360) / (2 * math.pi)
        angle2 = (angle2 * 360) / (2 * math.pi)
        center = NSPoint(x, y)
        self.path.appendBezierPathWithArcWithCenter_radius_startAngle_endAngle_clockwise_(center, radius, angle1, angle2, YES)

    def rectangle(self, x, y, width, height):
        rect = NSMakeRect(x, y, width, height)
        self.path.appendBezierPathWithRect_(rect)

    def set_color(self, color, alpha=1.0):
        self.color = convert_widget_color(color, alpha)
        self.color.set()
        
    def set_shadow(self, color, opacity, offset, blur_radius):
        shadow = NSShadow.alloc().init()
        # shadow offset is always in the cocoa coordinates, so we need to
        # reverse the y part
        shadow.setShadowOffset_(NSPoint(offset[0], -offset[1]))
        shadow.setShadowBlurRadius_(blur_radius)
        shadow.setShadowColor_(convert_widget_color(color, opacity))
        shadow.set()

    def set_line_width(self, width):
        self.path.setLineWidth_(width)

    def stroke(self):
        self.path.stroke()
        self.path.removeAllPoints()

    def stroke_preserve(self):
        self.path.stroke()

    def fill(self):
        self.path.fill()
        self.path.removeAllPoints()

    def fill_preserve(self):
        self.path.fill()

    def clip(self):
        self.path.addClip()
        self.path.removeAllPoints()

    def save(self):
        NSGraphicsContext.currentContext().saveGraphicsState()

    def restore(self):
        NSGraphicsContext.currentContext().restoreGraphicsState()

    def gradient_fill(self, gradient):
        self.gradient_fill_preserve(gradient)
        self.path.removeAllPoints()

    def gradient_fill_preserve(self, gradient):
        context = NSGraphicsContext.currentContext()
        context.saveGraphicsState()
        self.path.addClip()
        gradient.draw()
        context.restoreGraphicsState()

class Gradient(object):
    """See https://develop.participatoryculture.org/index.php/WidgetAPI for a description of the API for this class."""
    def __init__(self, x1, y1, x2, y2):
        self.x1, self.y1, self.x2, self.y2 = x1, y1, x2, y2
        self.start_color = None
        self.end_color = None

    def set_start_color(self, (red, green, blue)):
        self.start_color = (red, green, blue)

    def set_end_color(self, (red, green, blue)):
        self.end_color = (red, green, blue)
        
    def draw(self):
        start_color = convert_widget_color(self.start_color)
        end_color = convert_widget_color(self.end_color)
        nsgradient = NSGradient.alloc().initWithStartingColor_endingColor_(start_color, end_color)
        start_point = NSPoint(self.x1, self.y1)
        end_point = NSPoint(self.x2, self.y2)
        nsgradient.drawFromPoint_toPoint_options_(start_point, end_point, 0)

class DrawingMixin(object):
    def calc_size_request(self):
        return self.size_request(self.view.layout_manager)

    # squish width / squish height only make sense on GTK
    def set_squish_width(self, setting):
        pass

    def set_squish_height(self, setting):
        pass

    # Default implementations for methods that subclasses override.

    def is_opaque(self):
        return False

    def size_request(self, layout_manager):
        return 0, 0

    def draw(self, context, layout_manager):
        pass

    def viewport_repositioned(self):
        # since this is a Mixin class, we want to make sure that our other
        # classes see the viewport_repositioned() call.
        super(DrawingMixin, self).viewport_repositioned()
        self.queue_redraw()

