#
# Copyright 2008 Sun Microsystems Inc.
#
# 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., Franklin Street, Fifth Floor,
# Boston MA  02110-1301 USA.

"""Provides a simple utility to draw outlines on the screen.
[[[TODO: WDW - The Wedge support is experimental and not perfect.]]]"""

__id__        = "$Id$"
__version__   = "$Revision$"
__date__      = "$Date$"
__copyright__ = "Copyright (c) 2008 Sun Microsystems Inc."
__license__   = "LGPL"

import cairo
from gi.repository import Gtk
from gi.repository import Gdk

import orca_state
import settings

display = Gdk.Display.get_default()
screen = display.get_default_screen()
screen_width = screen.get_width()
screen_height = screen.get_height()
try:
    window_manager = screen.get_window_manager_name()
except:
    window_manager = None
else:
    if window_manager == 'unknown':
        window_manager = None

def _adjustToScreen(x, y, width, height):
    if x < 0:
        width = width + x
        x = 0
    elif x >= screen_width:
        width -= (x - screen_width)
        x = screen_width
    if y < 0:
        height = height + y
        y = 0
    elif y >= screen_height:
        height -= (y - screen_height)
        y = screen_height
    if (x + width) >= screen_width:
        width = screen_width - x
    if (y + height) >= screen_height:
        height = max(1, screen_height - y)
    width = max(1, width)
    height = max(1, height)
    return [x, y, width, height]

# Our known windows that we've put on the screen.
#
_outlineWindows = []

class Line(Gtk.Window):
    """Draws a simple filled box on the display using
    settings.outlineColor for the color.

    Arguments:
    - x, y, width, height: the dimensions of the box
    """
    def __init__(self, x, y, width, height):
        Gtk.Window.__init__(self)
        self.modify_bg(Gtk.StateType.NORMAL,
                       Gdk.Color(settings.outlineColor[0],
                                 settings.outlineColor[1],
                                 settings.outlineColor[2]))

        self.set_property("accept-focus", False)
        self.set_decorated(False)
        self.set_keep_above(True)
        self.set_skip_taskbar_hint(True)

        self.move(x, y)
        self.set_default_size(width, height)
        self.resize(width, height)
        self.realize()

class Wedge(Line):
    """Draws a filled isosceles triangle on the display
    using settings.outlineColor for the color.

    Arguments:
    - x, y, width, height - the bounding box of the triangle
    - top - if true, the triangle is flat on the bottom,
            if false, the triangle is pointy on the bottom
    """
    def __init__(self, x, y, width, height, top):
        self.top = top
        Line.__init__(self, x, y, width, height)
        self.connect('size-allocate', self._on_size_allocate)

    def _on_size_allocate(self, win, allocation):
        """Sets the shape of the window to a triangle."""
        # Create a bitmap to draw into.  This will be the mask
        # for the shape of the window.
        #
        width, height = win.get_size()
        surface = cairo.ImageSurface(cairo.FORMAT_A1, width, height)
        cr = cairo.Context(surface)

        # Clear the bitmap
        #
        cr.set_source_rgb(0, 0, 0)
        cr.set_operator(cairo.OPERATOR_DEST_OUT)
        cr.paint()

        # Draw our shape into the bitmap using cairo
        #
        cr.set_operator(cairo.OPERATOR_OVER)
        if self.top:
            # In this case, height is the same as thickness
            #
            cr.move_to(width/2.0, 0.0)
            cr.line_to(width/2.0 + height/2.0, height)
            cr.line_to(width/2.0 - height/2.0, height)
        else:
            cr.move_to(width/2.0, height)
            cr.line_to(width/2.0 + height/2.0, 0.0)
            cr.line_to(width/2.0 - height/2.0, 0.0)
        cr.close_path()
        cr.fill()

        # Set the window shape
        #
        win.shape_combine_mask(bitmap, 0, 0)

class Box(Line):
    """Draws the outline of a rectangle on the display
    using settings.outlineColor for the color.

    Arguments:
    - x, y, width, height - the bounding box of the rectangle
    - thickness - the thickness of the border
    """
    def __init__(self, x, y, width, height, thickness):
        self.thickness = thickness
        Line.__init__(self, x, y, width, height)
        self.connect('size-allocate', self._on_size_allocate)

    def _on_size_allocate(self, win, allocation):
        """Sets the shape of the window to a hollow rectangle."""

        # Create a bitmap to draw into.  This will be the mask
        # for the shape of the window.
        #
        width, height = win.get_size()
        surface = cairo.ImageSurface(cairo.FORMAT_A1, width, height)
        cr = cairo.Context(surface)

        # Clear the bitmap
        #
        cr.set_source_rgb(0, 0, 0)
        cr.set_operator(cairo.OPERATOR_DEST_OUT)
        cr.paint()

        # Draw our shape into the bitmap using cairo
        #
        cr.set_line_width(self.thickness)
        cr.set_operator(cairo.OPERATOR_OVER)
        offset = self.thickness / 2.0
        cr.rectangle(0.0 + offset, 0.0 + offset,
                     width - self.thickness, height - self.thickness)
        cr.stroke()

        # Set the window shape
        #
        #window.shape_combine_region()

def reset():
    """Destroys all windows we have put on the screen."""
    global _outlineWindows
    for window in _outlineWindows:
        window.destroy()
    _outlineWindows = []
   
def erase():
    """Erases all windows we have put on the screen."""
    for window in _outlineWindows:
        window.hide()

def draw(x, y, width, height):
    """Draws an outline for the given rectangle.  This might
    be composed of multiple windows depending upon the
    settings.outlineStyle."""

    if settings.outlineStyle == settings.OUTLINE_NONE or not window_manager:
        pass
    # Temporary solution for the fact that the flat review rectangle is now
    # solid. We are working on this, but it may or may not be done in time
    # for the 3.1.90 release and the reality is that flatreview indication
    # is for people who can see. Thus underlining the object being reviewed
    # shall have to suffice for now.
    elif settings.outlineStyle in [settings.OUTLINE_LINE, settings.OUTLINE_BOX]:
        y = y + height + settings.outlineMargin
        height = settings.outlineThickness
        [x, y, width, height] = _adjustToScreen(x, y, width, height)
        if not _outlineWindows:
            line = \
                Line(x, y, width, height)
            _outlineWindows.append(line)
        else:
            _outlineWindows[0].resize(width, height)
            _outlineWindows[0].move(x, y)
    elif settings.outlineStyle == settings.OUTLINE_BOX:
        extra = settings.outlineThickness + settings.outlineMargin
        x = x - extra
        y = y - extra
        width = width + (2 * extra)
        height = height + (2 * extra)
        [x, y, width, height] = _adjustToScreen(x, y, width, height)
        if not _outlineWindows:
            box = \
                Box(x, y, width, height,
                    settings.outlineThickness)
            _outlineWindows.append(box)
        else:
            _outlineWindows[0].resize(width, height)
            _outlineWindows[0].move(x, y)
    elif settings.outlineStyle == settings.OUTLINE_WEDGES:
        wedgeSize = 4 * settings.outlineThickness
        if width < wedgeSize:
            diff = wedgeSize - width
            width = wedgeSize
            x -= (diff/2)
        topy = y - (4 * settings.outlineThickness)
        bottomy = y + height + settings.outlineMargin
        [topx, topy, topwidth, topheight] = \
           _adjustToScreen(x, topy, width, wedgeSize)
        [bottomx, bottomy, bottomwidth, bottomheight] = \
           _adjustToScreen(x, bottomy, width, wedgeSize)
        if not _outlineWindows:
            top = \
                Wedge(topx, topy, topwidth, topheight, True)
            _outlineWindows.append(top)
            bottom = \
                Wedge(bottomx, bottomy, bottomwidth, bottomheight, False)
            _outlineWindows.append(bottom)
        else:
            _outlineWindows[0].resize(topwidth, topheight)
            _outlineWindows[0].move(topx, topy)
            _outlineWindows[1].resize(bottomwidth, bottomheight)
            _outlineWindows[1].move(bottomx, bottomy)

    ts = orca_state.lastInputEventTimestamp
    if ts == 0:
        ts = Gtk.get_current_event_time()

    # Make sure the windows stay on top.
    #
    for window in _outlineWindows:
        window.present_with_time(ts)
