#!/usr/bin/env python3

""" Helper classes for apps in the dock

    Provide a base class and descendants which will allow various types
    of indicators to be drawn onto a Cairo canvas provided by an app
    in the dock

    Provide a base class and descendants which will allow various types
    of backgrounds (e.g. gradient fill) to be drawn onto a Cairo canvas
    provided by an app in the dock

"""

# Copyright (C) 1997-2003 Free Software Foundation, Inc.
#
# 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.
#
# Author:
#     Robin Thompson

# do not change the value of this variable - it will be set during build
# according to the value of the --with-gtk3 option used with .configure
build_gtk2 = False

import gi

if build_gtk2:
    gi.require_version("Gtk", "2.0")
    gi.require_version("Wnck", "1.0")
else:
    gi.require_version("Gtk", "3.0")

gi.require_version("MatePanelApplet", "4.0")

from gi.repository import Gtk
from gi.repository import MatePanelApplet
from gi.repository import GdkPixbuf
from gi.repository import Gdk
import cairo
import math
import os


class IndicatorType:
    """Class to define the indicator types"""
    LIGHT = 0    # Light Circle
    DARK = 1     # Dark Circle
    NONE = 2
    TBAR = 3     # Theme Bar
    TCIRC = 4    # Circle drawn in theme colour
    TSQUARE = 5  # Square drawn in theme colour
    TTRI = 6     # Triangle drawn in theme colour
    TDIA = 7     # Diamond drawn in theme colour
    SUBWAY = 8   # Metro type


class IconBgType:
    """Class to define the icon background types"""
    GRADIENT = 0
    ALPHAFILL = 1
    UNITY = 2
    UNITY_FLAT = 3


# static list to hold the rgb colour elements to use when drawing
# and indicator and we can't get the theme highlight colour i.e. when
# using gtk2
fallback_ind_col = [0.9, 0.9, 0.9]


def get_theme_highlight_col(applet):
    """
        get the current theme's highlight colour (Gtk3) or the fallback colour
        (Gtk2)

    Args:
        applet : the dock applet
    :return:
        a tuple containing the r,g,b values (0-1.0) of the colors

    """

    if build_gtk2:
        return fallback_ind_col
    else:

        context = applet.get_style_context()

        sel_bg = context.lookup_color("theme_selected_bg_color")
        if sel_bg[0]:
            hcol = sel_bg[1]
            return [hcol.red, hcol.green, hcol.blue]
        else:
            # assume what is hopefully a decent looking highlight
            # colour - something a bit brighter (or maybe a lot darker)
            # than the background
            c_info = context.lookup_color("dark_bg_color")
            if c_info[0]:
                bgcol = c_info[1]
                return [(bgcol.red + 0.25) % 1,
                        (bgcol.green + 0.25) % 1,
                        (bgcol.blue + 0.25) % 1]
            else:
                # we don't even have a background colour, so....
                return fallback_ind_col


class IndicatorDrawer(object):
    """ Base class for drawing indicators

     Provide a base class which can be used for drawing various type of
     app indicators onto Cairo surfaces

     This class must not be instantiated and it will be an error to try
     and use this to draw indicators. Descendant classes will be implement
     their own drawing functions

     Attributes:
        _context : the cairo context to draw onto
        _size    : the size (width and height - assumes is square...) of the docked app
        _orient  : the orientation of the dock applet e.g. MatePanelApplet.AppletOrient.RIGHT
        _num_ind : the number of indicators to draw, may not be applicable to all indicators
        extra_s  : (static) amount of extra size (in pixels) the indicator requires of the docked_app

    Descendant classes can implement their own properties as necessary if they need more control
    over the drawing process (e.g. to set a specific indicator colour)

    """

    extra_s = 0  # default value for most indicators - those which require more must override this

    def __init__(self, context, size, orient, num_ind=0):
        """ Constructor

        Set attributes according to the constructor parameters

        """

        super().__init__()

        self._context = context
        self._size = size
        self._orient = orient
        self._num_ind = num_ind

    def draw(self):
        """
            Abstract method - descendants will implement this as required and return
                              _surface when drawing is completed
        """

        raise NotImplementedError("Must be implemented by Indicator subclasses")


class DefaultInd(IndicatorDrawer):
    """
        Base class for the two variants (light and dark) of the default indicator
    """

    def __init__(self, context, size, orient, num_ind):
        """
            Constructor - call the inherited constructor and do additional
            bits of setup
        """
        super().__init__(context, size, orient, num_ind)

        # set the cairo colour values of the inner and outer areas of the indicator
        self._col1 = [0.0, 0.0, 0.0]
        self._col2 = [0.0, 0.0, 0.0]

    def draw(self):
        """
                  Draw up to 4 indicators

              """

        if self._orient == MatePanelApplet.AppletOrient.RIGHT:
            ind_x = 2
            ind_y = (self._size - 4) / (self._num_ind + 1) + 2

        elif self._orient == MatePanelApplet.AppletOrient.LEFT:
            ind_x = self._size - 1
            ind_y = (self._size - 4) / (self._num_ind + 1) + 2

        elif self._orient == MatePanelApplet.AppletOrient.DOWN:
            ind_x = (self._size - 4) / (self._num_ind + 1) + 2
            ind_y = 2
        else:
            ind_x = (self._size - 4) / (self._num_ind + 1) + 2
            ind_y = self._size - 1

        this_ind = 1
        while this_ind <= self._num_ind:
            rad_patt = cairo.RadialGradient(ind_x, ind_y, 2,
                                            ind_x, ind_y, 4)

            rad_patt.add_color_stop_rgba(0, self._col1[0], self._col1[1],
                                         self._col1[2], 1)
            rad_patt.add_color_stop_rgba(1, self._col2[0], self._col2[1],
                                         self._col2[2], 0)

            self._context.set_source(rad_patt)
            self._context.arc(ind_x, ind_y, 6, 0, 2 * math.pi)

            if self._num_ind > 1:
                if (self._orient == MatePanelApplet.AppletOrient.RIGHT) or \
                        (self._orient == MatePanelApplet.AppletOrient.LEFT):
                    ind_y += (self._size - 6) / (self._num_ind + 1)
                else:
                    ind_x += (self._size - 6) / (self._num_ind + 1)

            this_ind += 1

            self._context.fill()


class DefaultLightInd(DefaultInd):
    """
        Class to draw the dock applet's default light indicator

    """

    def __init__(self, context, size, orient, num_ind):
        """
                 Constructor - call the inherited constructor and do additional
                 bits of setup
        """

        super().__init__(context, size, orient, num_ind)

        # cairo colour values of the inner and outer areas of the indicator
        self._col1 = [0.9, 0.9, 0.9]
        self._col2 = [0.0, 0.0, 0.0]


class DefaultDarkInd(DefaultInd):
    """
        Class to draw the dock applet's default dark indicator
    """

    def __init__(self, context, size, orient, num_ind):
        """
                 Constructor - call the inherited constructor and do additional
                 bits of setup
        """

        super().__init__(context, size, orient, num_ind)

        # cairo colour values of the inner and outer areas of the indicator
        self._col1 = [0.0, 0.0, 0.0]
        self._col2 = [0.9, 0.9, 0.9]


class BarInd(IndicatorDrawer):
    """
        Base class for the bar indicator, a filled rectangle

        Multiple indicators are not supported - the bar runs the full width
        (or height, depending on the applet orientation) of the context
        and merely indicates that an app is running, not how many windows
        it has open...

    """

    def __init__(self, context, size, orient):
        """

        """
        super().__init__(context, size, orient)

        # cairo colour values of the bar, will be overridden by descendant classes
        self._barcol = [0.0, 0.0, 0.0]

    def draw(self):
        """
            Draw the bar along the edge of the panel adjoining the screen

        """

        line_size = 1
        self._context.set_line_cap(cairo.LINE_CAP_SQUARE)
        self._context.set_line_width(line_size)

        self._context.set_source_rgb(self._barcol[0], self._barcol[1], self._barcol[2])
        if self._orient == MatePanelApplet.AppletOrient.RIGHT:
            rect = [[0.5, 0.5], [2.5, 0.5],
                    [2.5, self._size - 0.5], [0.5, self._size - 0.5]]

        elif self._orient == MatePanelApplet.AppletOrient.LEFT:
            rect = [[self._size - 2.5, 0.5], [self._size - 0.5, 0, 5],
                    [self._size - 0.5, self._size - 0.5], [self._size - 2.5, self._size - 0.5]]

        elif self._orient == MatePanelApplet.AppletOrient.DOWN:
            rect = [[0.5, 0.5], [self._size - 0.5, 0.5],
                    [self._size - 0.5, 2.5], [0.5, 2.5]]

        else:
            rect = [[0.5, self._size - 2.5], [self._size - 0.5, self._size - 2.5],
                    [self._size - 0.5, self._size - 0.5], [0.5, self._size - 0.5]]

        self._context.set_line_width(1)
        self._context.move_to(rect[0][0], rect[0][1])

        for point in rect:
            self._context.line_to(point[0], point[1])

        self._context.line_to(rect[0][0], rect[0][1])

        self._context.stroke_preserve()
        self._context.fill()


class ThemeBarInd(BarInd):
    """
        A bar indicator in which the bar is drawn in the current theme's
        highlight colour (Gtk3) or using a fallback colour (Gtk2)
    """

    def __init__(self, context, size, orient, applet):
        """
            Constructor - call the inherited constructor and do additional
            bits of setup

        Args (in addition to those specified in the base class)
            applet : the applet

        """
        super().__init__(context, size, orient)

        # set the bar color
        self._barcol = get_theme_highlight_col(applet)


class ThemeCircleInd(IndicatorDrawer):
    """
        Draws round indicators (up to 4) with the current theme's highlight colour
    """

    def __init__(self, context, size, orient, applet, num_ind=0):
        """
            Constructor - call the inherited constructor and do additional
            bits of setup

        Args (in addition to those specified in the base class)
            applet : the applet

        """
        super().__init__(context, size, orient, num_ind)

        # set the indicator color
        self._indcol = get_theme_highlight_col(applet)

    def draw(self):
        """
                  Draw up to 4 indicators

              """

        if self._orient == MatePanelApplet.AppletOrient.RIGHT:
            ind_x = 2
            ind_y = (self._size - 4) / (self._num_ind + 1) + 2

        elif self._orient == MatePanelApplet.AppletOrient.LEFT:
            ind_x = self._size - 2
            ind_y = (self._size - 4) / (self._num_ind + 1) + 2

        elif self._orient == MatePanelApplet.AppletOrient.DOWN:
            ind_x = (self._size - 4) / (self._num_ind + 1) + 2
            ind_y = 2
        else:
            ind_x = (self._size - 4) / (self._num_ind + 1) + 2
            ind_y = self._size - 2

        this_ind = 1
        while this_ind <= self._num_ind:

            self._context.set_source_rgb(self._indcol[0], self._indcol[1], self._indcol[2])
            self._context.set_line_width(1)
            self._context.arc(ind_x, ind_y, 2, 0, 2 * math.pi)
            self._context.close_path()
            self._context.fill()

            if self._num_ind > 1:
                if (self._orient == MatePanelApplet.AppletOrient.RIGHT) or \
                        (self._orient == MatePanelApplet.AppletOrient.LEFT):
                    ind_y += (self._size - 6) / (self._num_ind + 1)
                else:
                    ind_x += (self._size - 6) / (self._num_ind + 1)

            this_ind += 1


class ThemeSquareInd(IndicatorDrawer):
    """
        Draws square indicators (up to 4) with the current theme's highlight colour
    """

    def __init__(self, context, size, orient, applet, num_ind=0):
        """
            Constructor - call the inherited constructor and do additional
            bits of setup

        Args (in addition to those specified in the base class)
            applet : the applet

        """
        super().__init__(context, size, orient, num_ind)

        # set the indicator color
        self._indcol = get_theme_highlight_col(applet)

    def draw(self):
        """
                  Draw up to 4 indicators

              """

        line_size = 1
        ind_size = 3

        self._context.set_line_width(line_size)

        # work out the x&y coords of the top left of the first indicator
        if self._orient == MatePanelApplet.AppletOrient.RIGHT:
            ind_x = 1.5
            ind_y = (self._size - ind_size) / (self._num_ind + 1) + 0.5

        elif self._orient == MatePanelApplet.AppletOrient.LEFT:
            ind_x = self._size - ind_size - 0.5
            ind_y = (self._size - ind_size) / (self._num_ind + 1) + 0.5

        elif self._orient == MatePanelApplet.AppletOrient.DOWN:
            ind_x = (self._size - ind_size) / (self._num_ind + 1) + 0.5
            ind_y = 1.5
        else:
            ind_x = (self._size - ind_size) / (self._num_ind + 1) + 0.5
            ind_y = self._size - ind_size - 0.5

        self._context.set_line_width(line_size)

        this_ind = 1
        while this_ind <= self._num_ind:

            self._context.set_source_rgb(self._indcol[0], self._indcol[1], self._indcol[2])

            self._context.rectangle(ind_x, ind_y, ind_size, ind_size)
            self._context.stroke_preserve()
            self._context.fill()

            if self._num_ind > 1:
                if (self._orient == MatePanelApplet.AppletOrient.RIGHT) or \
                        (self._orient == MatePanelApplet.AppletOrient.LEFT):
                    ind_y += (self._size - 6) / (self._num_ind + 1)
                else:
                    ind_x += (self._size - 6) / (self._num_ind + 1)

            this_ind += 1


class ThemeDiaInd(IndicatorDrawer):
    """
        Draws diamond indicators (up to 4) with the current theme's highlight colour
    """

    def __init__(self, context, size, orient, applet, num_ind=0):
        """
            Constructor - call the inherited constructor and do additional
            bits of setup

        Args (in addition to those specified in the base class)
            applet : the applet

        """
        super().__init__(context, size, orient, num_ind)

        # set the indicator color
        self._indcol = get_theme_highlight_col(applet)

    def draw(self):
        """
                  Draw up to 4 indicators

              """

        line_size = 1
        ind_size = 4.0

        # self._context.set_line_cap(cairo.LINE_CAP_ROUND)
        self._context.set_line_width(line_size)

        # work out the x&y coords of the top centre of the first indicator
        if self._orient == MatePanelApplet.AppletOrient.RIGHT:
            ind_x = (ind_size / 2) + 0.5
            ind_y = (self._size - ind_size) / (self._num_ind + 1) + 0.5

        elif self._orient == MatePanelApplet.AppletOrient.LEFT:
            ind_x = self._size - (ind_size / 2) - 0.5
            ind_y = (self._size - ind_size) / (self._num_ind + 1)

        elif self._orient == MatePanelApplet.AppletOrient.DOWN:
            ind_x = (self._size - ind_size) / (self._num_ind + 1) + (ind_size / 2) + 0.5
            ind_y = 1.5
        else:
            ind_x = (self._size - ind_size) / (self._num_ind + 1) + (ind_size / 2) + 0.5
            ind_y = self._size - ind_size - 0.5

        this_ind = 1
        while this_ind <= self._num_ind:

            self._context.set_source_rgb(self._indcol[0], self._indcol[1], self._indcol[2])
            self._context.move_to(ind_x, ind_y)
            self._context.line_to(ind_x + (ind_size / 2), ind_y + (ind_size / 2))
            self._context.line_to(ind_x, ind_y + ind_size)
            self._context.line_to(ind_x - (ind_size / 2), ind_y + (ind_size / 2))
            self._context.line_to(ind_x, ind_y)
            self._context.stroke_preserve()
            self._context.fill()

            if self._num_ind > 1:
                if (self._orient == MatePanelApplet.AppletOrient.RIGHT) or \
                        (self._orient == MatePanelApplet.AppletOrient.LEFT):
                    ind_y += (self._size - 6) / (self._num_ind + 1)
                else:
                    ind_x += (self._size - 6) / (self._num_ind + 1)

            this_ind += 1


class ThemeTriInd(IndicatorDrawer):
    """
        Draws triangle indicators (up to 4) with the current theme's highlight colour
    """

    def __init__(self, context, size, orient, applet, num_ind=0):
        """
            Constructor - call the inherited constructor and do additional
            bits of setup

        Args (in addition to those specified in the base class)
            applet : the applet

        """
        super().__init__(context, size, orient, num_ind)

        # set the bar color
        self._indcol = get_theme_highlight_col(applet)

    def draw(self):
        """
                  Draw up to 4 indicators

              """

        line_size = 1
        ind_width = 4
        ind_height = 3

        self._context.set_line_width(line_size)
        self._context.set_source_rgb(self._indcol[0], self._indcol[1], self._indcol[2])

        # for each panel orientation we need to make sure the triangle is drawn with its
        # point facing the icon...
        if self._orient == MatePanelApplet.AppletOrient.RIGHT:
            ind_x = 0.5
            ind_y = (self._size - ind_width) / (self._num_ind + 1) + 0.5
            point1 = [ind_x, ind_y]
            point2 = [ind_x + ind_height, ind_y + ind_width / 2]
            point3 = [ind_x, ind_y + ind_width]

        elif self._orient == MatePanelApplet.AppletOrient.LEFT:
            ind_x = self._size - 0.5
            ind_y = (self._size - ind_width) / (self._num_ind + 1) + 0.5
            point1 = [ind_x, ind_y]
            point2 = [ind_x - ind_height, ind_y + ind_height / 2]
            point3 = [ind_x, ind_y + ind_width]

        elif self._orient == MatePanelApplet.AppletOrient.DOWN:
            ind_x = (self._size - ind_width) / (self._num_ind + 1) + 0.5
            ind_y = 0.5
            point1 = [ind_x, ind_y]
            point2 = [ind_x + ind_width / 2, ind_y + ind_height]
            point3 = [ind_x + ind_width, ind_y]
        else:

            ind_x = (self._size - ind_width) / (self._num_ind + 1) + 0.5
            ind_y = self._size - 0.5
            point1 = [ind_x, ind_y]
            point2 = [ind_x + ind_width / 2, ind_y - ind_height]
            point3 = [ind_x + ind_width, ind_y]

        this_ind = 1

        while this_ind <= self._num_ind:

            self._context.move_to(point1[0], point1[1])
            self._context.line_to(point2[0], point2[1])
            self._context.line_to(point3[0], point3[1])
            self._context.line_to(point1[0], point1[1])
            self._context.stroke_preserve()
            self._context.fill()

            if self._num_ind > 1:
                if (self._orient == MatePanelApplet.AppletOrient.RIGHT) or \
                        (self._orient == MatePanelApplet.AppletOrient.LEFT):
                    point1[1] += (self._size - 6) / (self._num_ind + 1)
                    point2[1] += (self._size - 6) / (self._num_ind + 1)
                    point3[1] += (self._size - 6) / (self._num_ind + 1)

                else:
                    point1[0] += (self._size - 6) / (self._num_ind + 1)
                    point2[0] += (self._size - 6) / (self._num_ind + 1)
                    point3[0] += (self._size - 6) / (self._num_ind + 1)

            this_ind += 1


class SubwayInd(IndicatorDrawer):
    """
        Indicator which mimics the Metro look

    """

    extra_s = 4  # this type of indicator requires extra space

    def __init__(self, context, size, orient, applet, num_ind, surface, active):
        """
        Args (additional):
            surface : the cairo surface the indicators are being drawn on
            active  : bool - whether or not the app is active

        """
        super().__init__(context, size, orient, num_ind)

        # set the color of the bar indicator for the first indicator
        self._barcol = get_theme_highlight_col(applet)
        self._surface = surface
        self._active = active

    def draw(self):
        """
        The first open window of an app is drawn as for ThemeBarInd.

        If more than one indicator is required, the way it is drawn depends
        on whether or not the app active. If so, the last three columns (or
        rows, depending on the applet orientation) of the cairo surface
        are copied and drawn to the right (or below) the app icon. If not
        active, the rightmost part of the bar is drawn in a darker colour

        """

        b_o = 0.5  # orgin of the bar in cairo units
        b_w = 2.0  # width of the bar in cairo units

        # draw the bar
        line_size = 1
        self._context.set_line_cap(cairo.LINE_CAP_SQUARE)
        self._context.set_line_width(line_size)

        self._context.set_source_rgb(self._barcol[0], self._barcol[1], self._barcol[2])

        # define the four points to the bar - the order of the points is
        # top left, top right, bottom right, bottom left
        if self._orient == MatePanelApplet.AppletOrient.RIGHT:
            rect = [[b_o, b_o], [b_w, b_o],
                    [b_w, self._size - b_o], [b_o, self._size - b_o]]

        elif self._orient == MatePanelApplet.AppletOrient.LEFT:
            rect = [[self._size - b_w, b_o], [self._size - b_o, b_o],
                    [self._size - b_o, self._size - b_o], [self._size - b_w, self._size - b_o]]

        elif self._orient == MatePanelApplet.AppletOrient.DOWN:
            rect = [[b_o, b_o], [self._size - b_o, b_o],
                    [self._size - b_o, b_w], [b_o, b_w]]

        else:
            rect = [[b_o, self._size - b_w], [self._size - b_o, self._size - b_w],
                    [self._size - b_o, self._size - b_o], [b_o, self._size - b_o]]

        self._context.move_to(rect[0][0], rect[0][1])
        for point in rect:
            self._context.line_to(point[0], point[1])

        self._context.line_to(rect[0][0], rect[0][1])

        self._context.stroke_preserve()
        self._context.fill()

        if self._num_ind > 1:

            if self._active:
                # copy the required area from the surface to other parts of the surface...
                if self._orient == MatePanelApplet.AppletOrient.DOWN or \
                   self._orient == MatePanelApplet.AppletOrient.UP:
                    sx = self._size - 4

                    dx = self._size + 1
                    dy = 0
                    dw = 3
                    dh = self._size + 1

                else:
                    sx = 0

                    dx = 0
                    dy = self._size + 1
                    dw = self._size + 1
                    dh = 3

                # copy the relevant part of the source surface to a new surface
                copy_surf = cairo.ImageSurface(cairo.FORMAT_ARGB32, dw, dh)
                copy_ctx = cairo.Context(copy_surf)
                copy_ctx.set_source_surface(self._surface, -sx, 0)
                copy_ctx.rectangle(0, 0, dw, dh)
                copy_ctx.fill()

                # now draw onto the main surface
                if self._orient == MatePanelApplet.AppletOrient.DOWN or \
                   self._orient == MatePanelApplet.AppletOrient.UP:
                    self._context.set_source_surface(copy_surf, dx, 0)
                else:
                    self._context.set_source_surface(copy_surf, 0, dy)

                self._context.rectangle(dx, dy, dw, dh)
                self._context.fill()
            else:
                # darken part of the bar and draw a seperator between both parts

                darken_size = 5
                self._context.set_operator(cairo.OPERATOR_CLEAR)
                self._context.set_source_rgba(0, 0, 0, 1)
                if self._orient == MatePanelApplet.AppletOrient.DOWN or \
                   self._orient == MatePanelApplet.AppletOrient.UP:
                    self._context.move_to(rect[1][0] - darken_size, rect[1][1])
                    self._context.line_to(rect[1][0] - darken_size, rect[2][1])
                else:
                    self._context.move_to(rect[2][0], rect[2][1] - darken_size)
                    self._context.line_to(rect[3][0], rect[2][1] - darken_size)

                self._context.stroke()
                self._context.set_operator(cairo.OPERATOR_OVER)

                # darken with semi transparent black rectangle
                self._context.set_source_rgba(0, 0, 0, 0.20)
                if self._orient == MatePanelApplet.AppletOrient.DOWN or \
                   self._orient == MatePanelApplet.AppletOrient.UP:
                    self._context.move_to(rect[1][0] - darken_size - 1, rect[0][1])
                    self._context.line_to(rect[1][0], rect[1][1])
                    self._context.line_to(rect[2][0], rect[2][1])
                    self._context.line_to(rect[1][0] - darken_size - 1, rect[3][1])
                    self._context.line_to(rect[1][0] - darken_size - 1, rect[0][1])
                else:
                    self._context.move_to(rect[2][0], rect[2][1] - darken_size - 1)
                    self._context.line_to(rect[2][0], rect[2][1])
                    self._context.line_to(rect[3][0], rect[2][1])
                    self._context.line_to(rect[3][0], rect[2][1] - darken_size - 1)
                    self._context.line_to(rect[2][0], rect[2][1] - darken_size - 1)

                self._context.stroke_preserve()
                self._context.fill()


def ind_extra_s(indtype):
    """ Convenience function for returning the extra space required by an indicator

    Args: IndicatorType : The type of indicator e.g. ThemeTriInd

    Returns : int

    """

    if indtype == IndicatorType.LIGHT:
        return DefaultLightInd.extra_s
    elif indtype == IndicatorType.DARK:
        return DefaultDarkInd.extra_s
    elif indtype == IndicatorType.TBAR:
        return ThemeBarInd.extra_s
    elif indtype == IndicatorType.TCIRC:
        return ThemeCircleInd.extra_s
    elif indtype == IndicatorType.TSQUARE:
        return ThemeSquareInd.extra_s
    elif indtype == IndicatorType.TTRI:
        return ThemeTriInd.extra_s
    elif indtype == IndicatorType.TDIA:
        return ThemeDiaInd.extra_s
    elif indtype == IndicatorType.SUBWAY:
        return SubwayInd.extra_s
    else:
        return 0


###########################################################################################


class BackgroundDrawer(object):
    """ Base class for drawing the background of dock apps

     Provide a base class which can be used for drawing various type of
     backgrounds onto Cairo surfaces

     This class must not be instantiated and it will be an error to try
     and use this to draw backgrounds. Descendant classes will implement
     their own drawing functions

     Attributes:
        _context : the cairo context to draw onto
        _size    : the size (width and height - assumes is square...) of the size
        _orient  : the orientation of the dock applet e.g. MatePanelApplet.AppletOrient.RIGHT


    Descendant classes can implement their own properties as necessary if they need more control
    over the drawing process (e.g. to set a specific indicator colour)

    """

    def __init__(self, context, size, orient):
        """ Constructor

        Set attributes according to the constructor parameters

        """

        super().__init__()

        self._context = context
        self._size = size
        self._orient = orient

    def draw(self):
        """
            Abstract method - descendants will implement this as required and return
                              _surface when drawing is completed
        """

        raise NotImplementedError("Must be implemented by ActiveBackgroundDrawer descendents")


class DefaultBackgroundDrawer(BackgroundDrawer):
    """
        Class to draw the default active background, a colour gradient based on the
        the average colour of the app's icon
    """
    def __init__(self, context, size, orient, r, g, b):
        """ Constructor ...

        Args (in addition to those of the base class):
            r : the red component of the average colour
            g : the green component of the average colour
            b : the blue component of the average colour

        """
        super().__init__(context, size, orient)

        self._red = r
        self._green = g
        self._blue = b

    def draw(self):
        """
            Do the actual drawing, based on the panel orientation
        """

        # draw a background gradient according to the applet orientation
        if self._orient == MatePanelApplet.AppletOrient.RIGHT:
            pattern = cairo.LinearGradient(0, 0, self._size, 0)
            pattern.add_color_stop_rgba(0.0, self._red, self._green, self._blue, 1)
            pattern.add_color_stop_rgba(1.0, self._red, self._green, self._blue, 0)
        elif self._orient == MatePanelApplet.AppletOrient.LEFT:
            pattern = cairo.LinearGradient(self._size, 0, 0, 0)
            pattern.add_color_stop_rgba(0.0, self._red, self._green, self._blue, 1)
            pattern.add_color_stop_rgba(1.0, self._red, self._green, self._blue, 0)
        elif self._orient == MatePanelApplet.AppletOrient.DOWN:
            pattern = cairo.LinearGradient(0, 0, 0, self._size)
            pattern.add_color_stop_rgba(0.0, self._red, self._green, self._blue, 1)
            pattern.add_color_stop_rgba(1.0, self._red, self._green, self._blue, 0)
        else:
            pattern = cairo.LinearGradient(0, self._size, 0, 0)
            pattern.add_color_stop_rgba(0.0, self._red, self._green, self._blue, 1)
            pattern.add_color_stop_rgba(1.0, self._red, self._green, self._blue, 0)

        self._context.rectangle(0, 0, self._size, self._size)
        self._context.set_source(pattern)
        self._context.fill()


class AlphaFillBackgroundDrawer(BackgroundDrawer):
    """
        Fills the background with a specified colour and opacity
    """

    def __init__(self, context, size, orient, r, g, b, a):
        """ Constructor ...

        Args (in addition to those of the base class):
            r : the red component of the bg colour
            g : the green component of the bg colour
            b : the blue component of the bg colour
            a : the alpha component

        """
        super().__init__(context, size, orient)

        self._red = r
        self._green = g
        self._blue = b
        self.alpha = a

    def draw(self):
        """
            Do the actual drawing
        """

        self._context.rectangle(0, 0, self._size, self._size)
        self._context.set_source_rgba(self._red, self._green, self._blue, self.alpha)
        self._context.fill()


class UnityFlatBackgroundDrawer(BackgroundDrawer):
    """
        A Unity type flat backgroud (i.e. without the 'button' look)

        Running apps have their background filled with a specified colour
        (which will be based on the icon)

        Contains an extra method to be called after the app's icon
        has been drawn which will add a highlight
    """

    bg_surf = None
    shine_surf = None
    surf_size = 0

    def __init__(self, context, size, orient, r, g, b, running, scale_factor):
        """ Constructor ...

        Args (in addition to those of the base class):
            r : the red component of the average colour
            g : the green component of the average colour
            b : the blue component of the average colour
            running : whether or not the app is running
            scale_factor : 1 = standard def, higher values means HiDpi
        """

        super().__init__(context, size, orient)

        self._red = r
        self._green = g
        self._blue = b

        self._running = running
        self._scale_factor = scale_factor

        # load the .svgs used to draw the background and the shine_surf if
        # they haven't already been loaded, or they need to be reloaded
        # at a different size
        if (UnityFlatBackgroundDrawer.bg_surf is None) or (UnityFlatBackgroundDrawer.surf_size != size):
            self.load_bg()
            self.load_shine()
            UnityFlatBackgroundDrawer.surf_size = size

    def load_shine(self):
        """
            Load the background shine and convert to a cairo surface
        """

        d, f = os.path.split(os.path.abspath(__file__))

        if self._scale_factor == 1:
            fn = "assets/unity_dock_shine_56.svg"
        else:
            fn = "assets/unity_dock_shine_168.svg"  # load hipi version
        pb = GdkPixbuf.Pixbuf.new_from_file_at_size("%s/%s" % (d, fn),
                                                    self._size * 0.875, self._size * 0.875)

        UnityFlatBackgroundDrawer.shine_surf = Gdk.cairo_surface_create_from_pixbuf(pb, 1, None)

    def load_bg(self):
        """
            Load the background and convert to a cairo surface
        """

        d, f = os.path.split(os.path.abspath(__file__))
        if self._scale_factor == 1:
            fn = "assets/unity_dock_bg_56.svg"
        else:
            fn = "assets/unity_dock_bg_168.svg"  # load hipi version
        pb = GdkPixbuf.Pixbuf.new_from_file_at_size("%s/%s" % (d, fn),
                                                    self._size * 0.875, self._size * 0.875)

        UnityFlatBackgroundDrawer.bg_surf = Gdk.cairo_surface_create_from_pixbuf(pb, 1, None)

    def draw(self):
        """
            Draw the background
        """

        offset = self._size / 16

        if self._running:
            self._context.set_source_rgb(self._red, self._green, self._blue)
            self._context.mask_surface(UnityFlatBackgroundDrawer.bg_surf, offset, offset)
            self._context.fill()

    def draw_shine(self):
        """
            Draw the shine
        """
        offset = self._size / 16
        self._context.set_source_surface(UnityFlatBackgroundDrawer.shine_surf, 4, 4)
        self._context.paint()


class UnityBackgroundDrawer(UnityFlatBackgroundDrawer):
    """
        A Unity type backgroud (i.e. Unity Flat bur with the 'button' look)

        Running apps have their background filled with a specified colour
        (which will be based on the icon)

    """

    edge_surf = None
    edge_size = 0

    def __init__(self, context, size, orient, r, g, b, running, scale_factor):
        """ Constructor ...

        Args (in addition to those of the base class):
            r : the red component of the average colour
            g : the green component of the average colour
            b : the blue component of the average colour
            running : whether or not the app is running
        """

        super().__init__(context, size, orient, r, g, b, running, scale_factor)

        if (UnityBackgroundDrawer.edge_surf is None) or (UnityBackgroundDrawer.edge_size != size):
            self.load_edge()
            UnityBackgroundDrawer.edge_size = size

    def load_edge(self):
        """
            Load the background shine and convert to a cairo surface
        """

        d, f = os.path.split(os.path.abspath(__file__))
        if self._scale_factor == 1:
            fn = "assets/unity_dock_edge_56.svg"
        else:
            fn = "assets/unity_dock_edge_168.svg"  # load hipi version

        pb = GdkPixbuf.Pixbuf.new_from_file_at_size("%s/%s" % (d, fn),
                                                    self._size * 0.875, self._size * 0.875)

        UnityBackgroundDrawer.edge_surf = Gdk.cairo_surface_create_from_pixbuf(pb, 1, None)

    def draw(self):
        """
            Draw the background
        """

        offset = self._size / 16

        if self._running:
            self._context.set_source_rgb(self._red, self._green, self._blue)
            self._context.mask_surface(UnityFlatBackgroundDrawer.bg_surf, offset, offset)
            self._context.fill()
        else:
            self._context.set_source_surface(UnityBackgroundDrawer.edge_surf, 4, 4)
            self._context.paint()
