#!/usr/bin/env python
# -*- Mode: Python; py-indent-offset: 4 -*-
# vim: tabstop=4 shiftwidth=4 expandtab
#
# Copyright (C) 2010 Red Hat, Inc., John (J5) Palmieri <johnp@redhat.com>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 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
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
# USA

title = "Effects"
description = """
Offscreen windows can be used to render elements multiple times to achieve various effects.
"""

from gi.repository import Gtk, Gdk
import cairo

class MirrorBin(Gtk.Container):
    __gtype_name__ = 'MirrorBin'

    def __init__(self):
        super(MirrorBin, self).__init__()
        self.child = None
        self.offscreen_window = None
        self.set_has_window(True)

    def do_realize(self):
        print "realize"
        self.set_realized(True)

        allocation = self.get_allocation()
        border_width = self.get_border_width()

        attributes = Gdk.WindowAttr()
        attributes.x = allocation.x + border_width
        attributes.y = allocation.y + border_width
        attributes.width = allocation.width - 2 * border_width
        attributes.height = allocation.height - 2 * border_width
        attributes.window_type = Gdk.WindowType.CHILD
        attributes.event_mask = self.get_events()           \
                        | Gdk.EventMask.EXPOSURE_MASK       \
                        | Gdk.EventMask.POINTER_MOTION_MASK \
                        | Gdk.EventMask.BUTTON_PRESS_MASK   \
                        | Gdk.EventMask.BUTTON_RELEASE_MASK \
                        | Gdk.EventMask.SCROLL_MASK         \
                        | Gdk.EventMask.ENTER_NOTIFY_MASK   \
                        | Gdk.EventMask.LEAVE_NOTIFY_MASK

        attributes.visual = self.get_visual()
        attributes.wclass = Gdk.WindowWindowClass.OUTPUT

        attributes_mask = Gdk.WindowAttributesType.X        \
                        | Gdk.WindowAttributesType.Y        \
                        | Gdk.WindowAttributesType.VISUAL

        window = Gdk.Window.new(self.get_parent_window(),
                                attributes, 
                                attributes_mask)
        self.set_window(window)
        self.own_window(window)
        window.connect("pick-embedded-child",
                       self.pick_offscreen_child)

        attributes.window_type = Gdk.WindowType.OFFSCREEN

        child_requisition = Gtk.Requisition()
        child_requisition.width = child_requisition.height = 0

        if self.child and self.child.get_visible():
            child_allocation = self.child.get_allocation()
            attributes.width = child_allocation.width
            attributes.height = child_allocation.height

        self.offscreen_window = Gdk.Window.new(self.get_root_window(),
                                               attributes, attributes_mask)
        self.own_window(self.offscreen_window)

        if self.child:
            self.child.set_parent_window(self.offscreen_window)
            Gdk.offscreen_window_set_embedder(self.offscreen_window, window)
            #self.offscreen_window.connect('to-embedder',
            #                              self.offscreen_window_to_parent)
            #self.offscreen_window.connect('from-embedder',
            #                              self.offscreen_window_from_parent)

            context = self.get_style_context()
            context.set_background(window)
            context.set_background(self.offscreen_window)
            self.offscreen_window.show()

    def do_unrealize(self):
        print "unrealize"
        self.offscreen_window.set_user_data(None)
        self.offscreen_window = None

    def do_add(self, widget):
        if self.child is None:
            widget.set_parent_window(self.offscreen_window)
            widget.set_parent(self)
            self.child = widget

    def do_child_type(self):
        print "child type"
        if self.child is None:
            return GObject.TYPE_NONE
        return Gtk.Widget

    def do_remove(self, widget):
        print "remove"
        was_visible = widget.get_visible()

        if self.child == widget:
            widget.unparent()
            self.child = None

            if was_visible and self.get_visible():
                self.queue_resize()

    def do_size_request(self):
        print "size request"
        requisition = Gtk.Requisition()
        requisition.height = 0
        requisition.width = 0

        if self.child and self.child.get_visible():
            (child_requisition, unused) = self.child.get_preferred_size()

            border_width = self.get_border_width()
            requisition.width = border_width * 2 + child_requisition.width + 10
            requisition.height = border_width * 2 + child_requisition.height * 2 + 10

        print requisition.width, requisition.height
        return requisition

    def do_get_preferred_width(self):
        print "get width"
        requisition = self.do_size_request()
        return (requisition.width, requisition.width)

    def do_get_preferred_height(self):
        print "get height"
        requisition = self.do_size_request()
        return (requisition.width, requisition.width)

    def do_size_allocate(self, allocation):
        print "size allocate"
        self.set_allocation(allocation)
        print allocation.height
        print allocation.width

        border_width = self.get_border_width()

        w = allocation.width - border_width * 2
        h = allocation.height - border_width * 2

        if self.get_realized():
            self.get_window().move_resize(allocation.x + border_width,
                                          allocation.y + border_width,
                                          w, h)

        if self.child and self.child.get_visible():
            # FIXME: allow construction of aliases (this should be Gtk.Allocation)
            child_allocation = Gdk.Rectangle()

            (child_requisition, unused) = self.child.get_preferred_size()
            child_allocation.x = 0
            child_allocation.y = 0
            child_allocation.height = child_requisition.height
            child_allocation.width = child_requisition.width
            print child_allocation.height, child_allocation.width

            if self.get_realized():
                self.offscreen_window.move_resize(
                    allocation.x + border_width,
                    allocation.y + border_width,
                    child_allocation.width, child_allocation.height)

            self.child.size_allocate(child_allocation)

    def do_damage_event(self, event):
        print 'damage'
        self.get_window().invalidate_rect(None, False)
        return True

    def do_forall(self, include_internals, callback, data):
        print "for all"

    def do_show_all(self):
        self.show()
        if self.child is not None:
            self.child.show_all()
            child.map()

    def do_draw(self, cr):
        print "draw"
        window = self.get_window()
        if Gtk.cairo_should_draw_window(cr, window):
            if self.child and self.child.get_visible():
                surface = Gdk.offscreen_window_get_surface(self.offscreen_window)
                height = self.offscreen_window.get_height()

                # paint the offscreen child
                cr.set_source_surface(surface, 0, 0)
                cr.paint()

                matrix = cairo.Matrix(1.0, 0.0, 0.3, 1.0, 0.0, 0.0)
                matrix.scale(1.0, -1.0)
                matrix.translate(-10, - 3 * height - 10)
                cr.transform (matrix)

                cr.set_source_surface(surface, 0, height)

                # create linear gradient as mask-pattern to fade out the source
                mask = cairo.LinearGradient(0.0, height, 0.0, 2 * height)
                mask.add_color_stop_rgba(0.0,  0.0, 0.0, 0.0, 0.0)
                mask.add_color_stop_rgba(0.25, 0.0, 0.0, 0.0, 0.01)
                mask.add_color_stop_rgba(0.5,  0.0, 0.0, 0.0, 0.25)
                mask.add_color_stop_rgba(0.75, 0.0, 0.0, 0.0, 0.5)
                mask.add_color_stop_rgba(1.0,  0.0, 0.0, 0.0, 1.0)

                # paint the reflection
                cr.mask(mask)
        if Gtk.cairo_should_draw_window(cr, self.offscreen_window):
            Gtk.render_background(self.get_style_context(),
                                  cr,
                                  0, 0,
                                  self.offscreen_window.get_width(),
                                  self.offscreen_window.get_height())

            if self.child:
                self.propagate_draw(self.child,
                                    cr)

        return False

    def to_child(self, widget_x, widget_y):
        x_out = widget_x;
        y_out = widget_y;

        return (x_out, y_out)

    def to_parent(self, offscreen_x, offscreen_y):
        x_out = offscreen_x
        y_out = offscreen_y

        return (x_out, y_out)

    def pick_offscreen_child(self, offscreen_window, widget_x, widget_y):
        print "pick off"
        if self.child and self.child.get_visible():
            (x, y) = self.to_child(widget_x, widget_y)
            child_area = self.child.get_allocation()

            if (x >= 0 and x < child_area.width and 
                y >= 0 and y < child_area.height):
                return self.offscreen_window

        return None


    def offscreen_window_to_parent(self, 
                                   offscreen_window,
                                   offscreen_x,
                                   offscreen_y):
        return self.to_parent(offscreen_x, offscreen_y)

    def offscreen_window_from_parent(self,
                                     window,
                                     parent_x,
                                     parent_y):
        if args:
            print args
        return self.to_child(parent_x, parent_y)

class EffectsApp:
    def __init__(self):
        self.window = Gtk.Window(title='Effects')
        self.window.connect('destroy', Gtk.main_quit)
        self.window.set_border_width(10)

        vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
        bin = MirrorBin()
        group = Gtk.SizeGroup(mode=Gtk.SizeGroupMode.VERTICAL)
        hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=6)


        backbutton = Gtk.Button()
        backbutton.add(Gtk.Image(stock=Gtk.STOCK_GO_BACK, icon_size=4))

        group.add_widget(backbutton)
        entry = Gtk.Entry()

        group.add_widget(entry)
        applybutton = Gtk.Button()
        group.add_widget(applybutton)
        applybutton.add(Gtk.Image(stock=Gtk.STOCK_APPLY, icon_size=4))

        self.window.add(vbox)
        vbox.pack_start(bin, True, True, 0)
        bin.add(hbox)
        hbox.pack_start(backbutton, False, False, 0)
        hbox.pack_start(entry, True, True, 0)
        hbox.pack_start(applybutton, False, False, 0)

        self.window.show_all()

def main(demoapp=None):
    app = EffectsApp()
    Gtk.main()

if __name__ == '__main__':
    main()
