#  Gnome15 - Suite of tools for the Logitech G series keyboards and headsets
#  Copyright (C) 2010 Brett Smith <tanktarta@blueyonder.co.uk>
#
#  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 3 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, see <http://www.gnu.org/licenses/>.
 
'''
A top level GTK windows that draws on the LCD
'''
import gtk
import gobject
import g15driver
import util.g15cairo as g15cairo
import util.g15pythonlang as g15pythonlang
from threading import Lock
from threading import Semaphore
import g15theme
import g15screen
import cairo
import ctypes


_initialized = False
def create_cairo_font_face_for_file (filename, faceindex=0, loadoptions=0):
    global _initialized
    global _freetype_so
    global _cairo_so
    global _ft_lib
    global _surface

    CAIRO_STATUS_SUCCESS = 0
    FT_Err_Ok = 0

    if not _initialized:

        # find shared objects
        _freetype_so = ctypes.CDLL ("libfreetype.so.6")
        _cairo_so = ctypes.CDLL ("libcairo.so.2")

        _cairo_so.cairo_ft_font_face_create_for_ft_face.restype = ctypes.c_void_p
        _cairo_so.cairo_ft_font_face_create_for_ft_face.argtypes = [ ctypes.c_void_p, ctypes.c_int ]
        _cairo_so.cairo_set_font_face.argtypes = [ ctypes.c_void_p, ctypes.c_void_p ]
        _cairo_so.cairo_font_face_status.argtypes = [ ctypes.c_void_p ]
        _cairo_so.cairo_status.argtypes = [ ctypes.c_void_p ]

        # initialize freetype
        _ft_lib = ctypes.c_void_p ()
        if FT_Err_Ok != _freetype_so.FT_Init_FreeType (ctypes.byref (_ft_lib)):
            raise "Error initialising FreeType library."

        class PycairoContext(ctypes.Structure):
            _fields_ = [("PyObject_HEAD", ctypes.c_byte * object.__basicsize__),
                ("ctx", ctypes.c_void_p),
                ("base", ctypes.c_void_p)]

        _surface = cairo.ImageSurface (cairo.FORMAT_A8, 0, 0)

        _initialized = True

    # create freetype face
    ft_face = ctypes.c_void_p()
    cairo_ctx = cairo.Context (_surface)
    cairo_t = PycairoContext.from_address(id(cairo_ctx)).ctx

    if FT_Err_Ok != _freetype_so.FT_New_Face (_ft_lib, filename, faceindex, ctypes.byref(ft_face)):
        raise Exception("Error creating FreeType font face for " + filename)

    # create cairo font face for freetype face
    cr_face = _cairo_so.cairo_ft_font_face_create_for_ft_face (ft_face, loadoptions)
    if CAIRO_STATUS_SUCCESS != _cairo_so.cairo_font_face_status (cr_face):
        raise Exception("Error creating cairo font face for " + filename)

    _cairo_so.cairo_set_font_face (cairo_t, cr_face)
    if CAIRO_STATUS_SUCCESS != _cairo_so.cairo_status (cairo_t):
        raise Exception("Error creating cairo font face for " + filename)

    face = cairo_ctx.get_font_face ()

    return face


class G15OffscreenWindow(g15theme.Component):
    
    def __init__(self, component_id):
        g15theme.Component.__init__(self, component_id)
        self.window = None
        self.content = None
        
    def on_configure(self):
        g15theme.Component.on_configure(self)        
        gobject.idle_add(self._create_window)
        self.get_screen().key_handler.action_listeners.append(self)
        
    def notify_remove(self):
        g15theme.Component.notify_remove(self)
        self.get_screen().key_handler.action_listeners.remove(self)
        
    def set_content(self, content):
        self.content = content
        if self.window is not None:
            gobject.idle_add(self._do_set_content)
            
    def action_performed(self, binding):
        if self.is_visible():
            if binding.action == g15driver.NEXT_SELECTION:
                gobject.idle_add(self.window.focus_next)
            elif binding.action == g15driver.PREVIOUS_SELECTION:
                gobject.idle_add(self.window.focus_previous)
            if binding.action == g15driver.NEXT_PAGE:
                gobject.idle_add(self.window.change_widget)
            elif binding.action == g15driver.PREVIOUS_PAGE:
                gobject.idle_add(self.window.change_widget, None, True)
            elif binding.action == g15driver.SELECT:
                pass
            
    def paint(self, canvas):
        g15theme.Component.paint(self, canvas)
        if self.window is not None:
            self.window.paint(canvas)
            
        
    """
    Private
    """
            
    def _do_set_content(self):
        self.window.set_content(self.content)
        
    def _create_window(self):     
        screen = self.get_screen()   
        window = G15Window(screen, self.get_root(), self.view_bounds[0], self.view_bounds[1], \
                           self.view_bounds[2], self.view_bounds[3])
        if self.content is not None:
            self.window.set_content(self.content)
        self.window = window
        screen.redraw(self.get_root())
        
class G15Window(gtk.OffscreenWindow):
    
    def __init__(self, screen, page, area_x, area_y, area_width, area_height):
        gtk.OffscreenWindow.__init__(self)
        self.pixbuf = None
        self.scroller = None
        self.screen = screen
        self.page = page
        self.lock = None      
        self.area_x = int(area_x)
        self.area_y = int(area_y)
        self.area_width = int(area_width)
        self.area_height = int(area_height)
        self.surface = None
        self.content = gtk.EventBox()
        self.set_app_paintable(True)
        self.content.set_app_paintable(True)        
        self.connect("screen-changed", self.screen_changed)
        self.content.connect("expose-event", self._transparent_expose)
        self.content.set_size_request(self.area_width, self.area_height)
        self.add(self.content)
        self.connect("damage_event", self._damage)
        self.connect("expose_event", self._expose)
        self.screen_changed(None, None)
        self.lock = Semaphore()
        
    def set_content(self, content):
        self.content.add(content)
        self.show_all()
        
        # If the content window is a scroller, we send focus events to it
        # moving the scroller position to the focussed component
        if isinstance(content, gtk.ScrolledWindow):
            self.scroller = content
        
    def paint(self, canvas):
        if g15pythonlang.is_gobject_thread():
            raise Exception("Painting on mainloop")
        self.start_for_capture()
        gobject.idle_add(self._do_capture)
        self.lock.acquire()
        canvas.save()
        canvas.translate(self.area_x, self.area_y)
        canvas.set_source_surface(self.surface)
        canvas.paint()
        canvas.restore()
            
    def focus_next(self):
        self.content.get_toplevel().child_focus(gtk.DIR_TAB_FORWARD)
        self.scroll_to_focussed()
        self.screen.redraw(self.page)
        
    def focus_previous(self):
        self.content.get_toplevel().child_focus(gtk.DIR_TAB_BACKWARD)
        self.screen.redraw(self.page)
        self.scroll_to_focussed()
        
    def change_widget(self, amount = None, reverse = False):
        focussed = self.get_focus()
        if focussed != None:
            if isinstance(focussed, gtk.HScale):
                adj = focussed.get_adjustment()
                ps = adj.get_page_size() if amount is None else amount
                if ps == 0:
                    ps = 10
                if reverse:
                    adj.set_value(adj.get_value() - ps)
                else:
                    adj.set_value(adj.get_value() + ps)
                self.screen.redraw(self.page)
        
    def show_all(self):
        gtk.OffscreenWindow.show_all(self)
        
    def scroll_to_focussed(self):
        if self.scroller is not None:
            hadj = self.scroller.get_hadjustment()
            vadj = self.scroller.get_vadjustment()
            x, y = self.get_focus().translate_coordinates(self.scroller.get_children()[0], 0, 0)
            max_x = hadj.upper - hadj.page_size
            max_y = vadj.upper - vadj.page_size
            hadj.set_value(min(x, max_x))
            vadj.set_value(min(y, max_y))

    def screen_changed(self, widget, old_screen=None):
        global supports_alpha
        
        # To check if the display supports alpha channels, get the colormap
        screen = self.get_screen()
        colormap = screen.get_rgba_colormap()
        if colormap == None:
            colormap = screen.get_rgb_colormap()
            supports_alpha = False
        else:
            supports_alpha = True
            
        # Now we have a colormap appropriate for the screen, use it
        self.set_colormap(colormap)
    
        return False
        
    def _transparent_expose(self, widget, event = None):
        """
        To overcome inability to set a container component as transparent. I 
        cannot get compositing working (perhaps it just doesn't because we are
        going to an offscreen window). So, to get pseudo-transparency,
        we repaint the background the screen would normally paint, offset by
        the position of this component
        """
        cr = widget.window.cairo_create()
        self.screen.clear_canvas(cr)
        cr.save()
        cr.translate(-self.area_x, -self.area_y)
        for s in self.screen.painters:
            if s.place == g15screen.BACKGROUND_PAINTER:
                s.paint(cr)
        cr.restore()
        return False
        
    def _expose(self, widget, event):
        self.screen.redraw(self.page)
        return False
        
    def _damage(self, widget, event):
#        print "Damage"
#        self.screen.redraw(self.page)
        return False
    
    def start_for_capture(self):
        self.lock = Lock()
        self.lock.acquire()
    
    def _do_capture(self):
        self.content.window.invalidate_rect((0,0,self.area_width,self.area_height), True)
        self.content.window.process_updates(True)
        pixbuf = gtk.gdk.Pixbuf( gtk.gdk.COLORSPACE_RGB, False, 8, self.area_width, self.area_height)
        pixbuf.get_from_drawable(self.content.window, self.content.get_colormap(), 0, 0, 0, 0, self.area_width, self.area_height)
        self.surface = g15cairo.pixbuf_to_surface(pixbuf)
        self.pixbuf = pixbuf        
        self.lock.release()