# -*- coding: utf-8 -*-
#
#       pointer.py
#
#       Copyright 2017 Cimbali <me@cimba.li>
#
#       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 Street, Fifth Floor, Boston,
#       MA 02110-1301, USA.

"""
:mod:`pympress.pointer` -- Manage when and where to draw a software-emulated laser pointer on screen
----------------------------------------------------------------------------------------------------
"""

from __future__ import print_function, unicode_literals

import logging
logger = logging.getLogger(__name__)

import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gdk, GdkPixbuf

from pympress import util, extras


#: Pointer disabled, will not be drawn
POINTER_OFF = -1
#: Pointer enabled but hidden, will be drawn on ctrl + click
POINTER_HIDE = 0
#: Draw the pointer on the current slide
POINTER_SHOW = 1


class Pointer(object):
    #: :class:`~GdkPixbuf.Pixbuf` to read XML descriptions of GUIs and load them.
    pointer = GdkPixbuf.Pixbuf()
    #: `(float, float)` of position relative to slide, where the pointer should appear
    pointer_pos = (.5, .5)
    #: `bool` indicating whether we should show the pointer
    show_pointer = POINTER_OFF
    #: A reference to the UI's :class:`~pympress.config.Config`, to update the pointer preference
    config = None

    #: callback, to be connected to :func:`~pympress.ui.UI.redraw_current_slide`
    redraw_current_slide = lambda: None

    def __init__(self, config, builder):
        """ Setup the pointer management, and load the default pointer

        Args:
            config (:class:`~pympress.config.Config`): A config object containing preferences
            builder (:class:`~pympress.builder.Builder`): A builder from which to load widgets
        """
        super(Pointer, self).__init__()
        self.config = config

        self.redraw_current_slide = builder.get_callback_handler('redraw_current_slide')

        default = 'pointer_' + config.get('presenter', 'pointer')
        self.load_pointer(default)

        for radio_name in ['pointer_red', 'pointer_blue', 'pointer_green', 'pointer_none']:
            radio = builder.get_object(radio_name)
            radio.set_name(radio_name)

            radio.set_active(radio_name == default)


    def load_pointer(self, name):
        """ Perform the change of pointer using its name

        Args:
            name (`str`): Name of the pointer to load
        """
        if name in ['pointer_red', 'pointer_green', 'pointer_blue']:
            self.show_pointer = POINTER_HIDE
            self.pointer = GdkPixbuf.Pixbuf.new_from_file(util.get_icon_path(name + '.png'))
        else:
            self.show_pointer = POINTER_OFF


    def change_pointer(self, widget):
        """ Callback for a radio item selection as pointer color

        Args:
            widget (:class:`~Gtk.RadioMenuItem`): the selected radio item in the pointer type selection menu
        """
        if widget.get_active():
            assert(widget.get_name().startswith('pointer_'))
            self.load_pointer(widget.get_name())
            self.config.set('presenter', 'pointer', widget.get_name()[len('pointer_'):])


    def render_pointer(self, cairo_context, ww, wh):
        """ Draw the laser pointer on screen

        Args:
            cairo_context (:class:`~cairo.Context`): The canvas on which to render the pointer
            ww (`int`): The widget width
            wh (`int`): The widget height
        """
        if self.show_pointer == POINTER_SHOW:
            x = ww * self.pointer_pos[0] - self.pointer.get_width() / 2
            y = wh * self.pointer_pos[1] - self.pointer.get_height() / 2
            Gdk.cairo_set_source_pixbuf(cairo_context, self.pointer, x, y)
            cairo_context.paint()


    def track_pointer(self, widget, event):
        """ Move the laser pointer at the mouse location.

        Args:
            widget (:class:`~Gtk.Widget`):  the widget which has received the event.
            event (:class:`~Gdk.Event`):  the GTK event.

        Returns:
            `bool`: whether the event was consumed
        """
        if self.show_pointer == POINTER_SHOW:
            ww, wh = widget.get_allocated_width(), widget.get_allocated_height()
            ex, ey = event.get_coords()
            self.pointer_pos = (ex / ww, ey / wh)
            self.redraw_current_slide()
            return True

        else:
            return False


    def toggle_pointer(self, widget, event):
        """ Track events defining when the laser is pointing.

        Args:
            widget (:class:`~Gtk.Widget`):  the widget which has received the event.
            event (:class:`~Gdk.Event`):  the GTK event.

        Returns:
            `bool`: whether the event was consumed
        """
        if self.show_pointer == POINTER_OFF:
            return False

        ctrl_pressed = event.get_state() & Gdk.ModifierType.CONTROL_MASK

        if ctrl_pressed and event.type == Gdk.EventType.BUTTON_PRESS:
            self.show_pointer = POINTER_SHOW
            extras.Cursor.set_cursor(widget, 'invisible')

            # Immediately place & draw the pointer
            return self.track_pointer(widget, event)

        elif self.show_pointer == POINTER_SHOW and event.type == Gdk.EventType.BUTTON_RELEASE:
            self.show_pointer = POINTER_HIDE
            extras.Cursor.set_cursor(widget, 'parent')
            self.redraw_current_slide()
            return True

        else:
            return False


