#!/usr/bin/env python3
"""
    Provide a window showing a list of an app's open windows
    and also allow the user to select a window from the list and
    switch to it.

    Each item in the list will include the app's icon, an
    indicator to show if the window is currently active,
    the window's title, and a close icon allowing the window to
    be closed.

    The window will function in a similar way to a tooltip i.e.
    it will appear when the mouse hovers over a dock icon and
    will disappear if the mouse moves away from the window or
    the dock applet. It will also disappear when if  dock icon
    is clicked of an item in the window is clicked
"""

#
# 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("Wnck", "3.0")

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

from gi.repository import Gtk
from gi.repository import Wnck
from gi.repository import GdkPixbuf
from gi.repository import Gio
from gi.repository import Gdk
from gi.repository import GObject
from gi.repository import Pango
from gi.repository import MatePanelApplet

import os
import cairo
import tempfile
from time import sleep

import docked_app
from log_it import log_it as log_it

CONST_TIMER_DELAY = 500
CONST_CLOSE_ICON_SIZE = 16
CONST_SEP = "--------------------"  # text which denotes tree view item is a
                                    # separator
CONST_MAX_TITLE_WIDTH = 300         # max width of the title column in pixles
CONST__ACTIVE_TEXT = "•"


class DockWinList(Gtk.Window):

    """

        Attributes : (aside from ui elements)
            __mouse_areas : a list containing Gdk.Rectangle objects - used when
                            the window has been shown and defines the on screen
                            areas in which the mouse pointer must stay,
                            otherwise the window list will be hidden. The
                            rectangles should therefore include the he applet,
                            the area between the window and the applet, and the
                            window itself with a suitable buffer area around it
            __timer_id : a ref to a timer used for priodically checking the
                         mouse cursor position to see if it is within the areas
                         specified in __mouse_areas
            __the_app : the docked_app to which the window list relates
            __icontheme : used for drawing app icons in the window list. This
                          is set from the Gtk.IconIcon used by the dock, and
                          will therefore track changes to the icon theme whilst
                          the dock is running

            __win_w : the width of the window
            __win_h : the height of the window
            __bgr, __bgg, bgb : the r,g,b panel colour components
            __fgr, __fgg, fgb : the r,b,g foreground colour components

            __app_pb : the app's icon scaled to the size required for the
                       list

            The atrributes below are used for positioning this window relative
            to the applet and it's panel:
            __app_x : the x position of the docked app in root coordinates
            __app_y : the y position of the docked app in root coordinates
            __applet_x : the x position of the applet in root coordinates
            __applet_y : the y position of the applet in root coordinates
            __applet_w : the width of the applet in pixels
            __applet_h : the height of the applet in pixels
            __panel_orient : the orienation of the MATE panel the applet is on

    """

    def __init__(self, wnck_screen):
        """
        create the window and its contents

        Args:
            wnck_screen: the wnck_screen of the applet
        """

        def create_drawing_area(width, height, draw_event):
            # convenience func to create a drawing area with a specified
            # width, height and draw event
            da = Gtk.DrawingArea()
            da.set_size_request(width, height)
            if build_gtk2:
                da.connect("expose-event", draw_event)
            else:
                da.connect("draw", draw_event)

            return da

        super().__init__(title="", type=Gtk.WindowType.POPUP)

        self.wnck_screen = wnck_screen
        self.set_name("gtk-tooltips")   # allows the window to inherit tooltip
                                        # colours
        self.set_decorated(False)  # we don't want a titlebar..
        self.set_skip_taskbar_hint(True)  # we don't want to be in the taskbar

        self.__icon_size = 16  # size of icons in the window

        self.__the_app = None
        self.__icontheme = None
        self.__timer_id = None
        self.__dismissed = False

        self.__app_pb = None

        self.__win_w = 0
        self.__win_h = 0

        self.__app_x = 0
        self.__app_y = 0
        self.__panel_orient = MatePanelApplet.AppletOrient.LEFT

        # create ui
        if build_gtk2:
            self.__grid = Gtk.VBox()
            self.__grid.set_spacing(0)
            self.__grid = Gtk.Table(rows=3, columns=3)
            self.__grid.set_row_spacings(0)
            self.__grid.set_col_spacings(0)
        else:
            self.__grid = Gtk.Grid()
            self.__grid.set_orientation(Gtk.Orientation.VERTICAL)
            self.__grid.hexpand = True
            self.__grid.vexpand = True
            self.hexpand = True
            self.vexpand = True

        # set vars used when drawing the window border
        self.__border_width = 15
        self.__border_line = 4.5
        self.__line_width = 2
        self.__line_curve = 4

        # add drawing areas to all outsides of the 3x3 grid
        self.__da_top = create_drawing_area(self.__border_width,
                                            self.__border_width,
                                            self.draw_top_border)
        self.__da_left = create_drawing_area(self.__border_width,
                                             self.__border_width,
                                             self.draw_left_border)
        self.__da_right = create_drawing_area(self.__border_width,
                                              self.__border_width,
                                              self.draw_right_border)
        self.__da_bottom = create_drawing_area(self.__border_width,
                                               self.__border_width,
                                               self.draw_bottom_border)

        # we use a treeview to list each window, so initialise it and its
        # liststore
        self.__tree_view = Gtk.TreeView()

        if not build_gtk2:
            self.__tree_view.set_valign(Gtk.Align.START)
            self.__tree_view.set_halign(Gtk.Align.START)
            self.__tree_view.hexpand = True
            self.__tree_view.vexpand = True

        self.__tree_view.set_headers_visible(False)

        # turn grid lines off, although they still seem to appear in some
        # themes e.g. Menta
        self.__tree_view.set_grid_lines(Gtk.TreeViewGridLines.NONE)

        self.__tree_view.set_hover_selection(True)

        # the list consists of open windows (click to select the window)
        # and items which work as menu items (e.g. pin/unpin app to dock)
        # and a Gtk.Action from which we can invoke the activate method
        # the liststore therefore needs to contain an active indictor,
        # window title/item text, Wnck window (if this is a window),
        # GdxPixbuf (an icon for the user to click to close the window,  and
        # a Gtk.Action (if this is something we want to call back)
        self.__list_store = Gtk.ListStore(str, str, GdkPixbuf.Pixbuf,
                                          Wnck.Window, Gtk.Action,
                                          GdkPixbuf.Pixbuf)

        self.__active_renderer = Gtk.CellRendererText()
        self.__icon_renderer = Gtk.CellRendererPixbuf()
        self.__title_renderer = Gtk.CellRendererText()
        self.__close_renderer = Gtk.CellRendererPixbuf()
        self.__close_renderer.set_alignment(1, 0.0)   # align to to topright of
                                                      # the cell

        # set default cell colours and padding
        self.__title_renderer.set_padding(2, 6)
        self.set_bg_col(32, 32, 32)

        # create columns for the treeview
        self.__col_icon = Gtk.TreeViewColumn("",
                                             self.__icon_renderer,
                                             pixbuf=5)

        self.__col_active = Gtk.TreeViewColumn("",
                                               self.__active_renderer,
                                               text=0)

        self.__col_active.set_sizing(Gtk.TreeViewColumnSizing.GROW_ONLY)
        self.__col_active.set_min_width(0)

        self.__col_title = Gtk.TreeViewColumn("",
                                              self.__title_renderer,
                                              text=1)
        self.__col_title.set_sizing(Gtk.TreeViewColumnSizing.GROW_ONLY)
        self.__col_title.set_expand(True)

        self.__col_title.set_max_width(CONST_MAX_TITLE_WIDTH)
        # assign an event handler to the title column so that we can use it
        # to display the active window title in bold text
        self.__col_title.set_cell_data_func(self.__title_renderer,
                                            self.draw_title)

        self.__col_title.set_sizing(Gtk.TreeViewColumnSizing.GROW_ONLY)

        self.__col_close = Gtk.TreeViewColumn("",
                                              self.__close_renderer,
                                              pixbuf=2)
        self.__col_close.set_sizing(Gtk.TreeViewColumnSizing.GROW_ONLY)
        self.__col_close.set_max_width(32)

        # add the columns
        self.__tree_view.set_model(self.__list_store)
        self.__tree_view.append_column(self.__col_icon)
        self.__tree_view.append_column(self.__col_title)
        self.__tree_view.append_column(self.__col_close)

        self.__tree_view.set_row_separator_func(self.check_sep)

        if build_gtk2:
            self.__grid.attach(self.__da_top, 0, 3, 0, 1, xpadding=0, ypadding=0)
            self.__grid.attach(self.__da_left, 0, 1, 1, 2)
            self.__grid.attach(self.__da_right, 2, 3, 1, 2)
            self.__grid.attach(self.__da_bottom, 0, 3, 2, 3)
            self.__grid.attach(self.__tree_view, 1, 2, 1, 2)
        else:
            self.__grid.attach(self.__da_top, 0, 0, 3, 1)
            self.__grid.attach(self.__da_left, 0, 1, 1, 1)
            self.__grid.attach(self.__da_right, 2, 1, 1, 1)
            self.__grid.attach(self.__da_bottom, 0, 2, 3, 1)
            self.__grid.attach(self.__tree_view, 1, 1, 1, 1)

        self.__grid.show_all()
        self.add(self.__grid)

        self.__mouse_areas = []

        self.__tree_view.set_has_tooltip(True)
        self.__tree_view.connect("query-tooltip", self.query_tooltip)
        self.__tree_view.connect("button-release-event", self.button_release)
        self.__tree_view.connect("size-allocate", self.treeview_allocate)

        # connect handlers for the show and hide events
        self.connect("show", self.win_shown)
        self.connect("hide", self.win_hidden)

        self.connect("configure-event", self.win_configure)
        self.connect("size-allocate", self.size_allocate)

        self.__pb_close = None
        self.__pb_active = None

    def create_close_pixbuf(self):
        """ Create a 'close' icon (based on the stock close icon) for use
            in the treeview
        """

        # create a pixbuf for holding the 'close' icon
        pb_close = self.render_icon(Gtk.STOCK_CLOSE, Gtk.IconSize.MENU, None)

        surface = cairo.ImageSurface(cairo.FORMAT_ARGB32,
                                     CONST_CLOSE_ICON_SIZE,
                                     CONST_CLOSE_ICON_SIZE)
        ctx = cairo.Context(surface)
        Gdk.cairo_set_source_pixbuf(ctx, pb_close, 0, 0)
        ctx.paint()

        # we now need to copy the cairo surface to a pixbuf. The best way to do
        # this would be by calling GdkPixbuf.Pixbuf.new_from_data as in these
        #                                                 64)
        # comments. Unfortunately this function does not seem to be
        # introspectable (Gtk2) or not implemented yet (Gtk3) and therefore
        # doesn't work.
        #
        # self.__pb_close = GdkPixbuf.Pixbuf.new_from_data(surface.get_data(),
        #                                                 GdkPixbuf.Colorspace.RGB,
        #                                                 True, 8, pb_close.get_width(),
        #                                                 pb_close.get_height(),

        # Therefore we have to resort to writing the surface to a temporary
        # .png file and then loading it into our pixbuf ...

        handle, tempfn = tempfile.mkstemp()
        surface.write_to_png(tempfn)
        self.__pb_close = GdkPixbuf.Pixbuf.new_from_file(tempfn)
        os.remove(tempfn)

    def create_active_pixbuf(self):
        """ Create an active window icon (based on the stock forward icon) for
            use in the treeview

            See the comments in create-close-pixbuf
        """

        # create a pixbuf for holding the icon
        pb_active = self.render_icon(Gtk.STOCK_GO_FORWARD, Gtk.IconSize.MENU, None)

        surface = cairo.ImageSurface(cairo.FORMAT_ARGB32,
                                     CONST_CLOSE_ICON_SIZE,
                                     CONST_CLOSE_ICON_SIZE)
        ctx = cairo.Context(surface)
        Gdk.cairo_set_source_pixbuf(ctx, pb_active, 0, 0)
        ctx.paint()

        handle, tempfn = tempfile.mkstemp()
        surface.write_to_png(tempfn)
        self.__pb_active = GdkPixbuf.Pixbuf.new_from_file(tempfn)
        os.remove(tempfn)

    def treeview_allocate(self, widget, allocation):
        """ Event handler for the tree view size-allocate event

        If the title column has expanded to its maximum width, ellipsize the
        title text...
        """

        if self.__col_title.get_width() == CONST_MAX_TITLE_WIDTH:
            self.__col_title.set_min_width(CONST_MAX_TITLE_WIDTH)
            self.__title_renderer.set_property("ellipsize",
                                               Pango.EllipsizeMode.END)

        self.__tree_view.get_selection().unselect_all()


    def set_bg_col (self, bgr, bgg, bgb):
        """ Sets the background colour of the window

        Also, set a foreground colour that will contrast with the background
        colour (so we can read text etc...)

        Args:
            bgr, bgg, bgb : the background rgb colour components

        """

        self.__bgr = bgr
        self.__bgg = bgg
        self.__bgb = bgb

        # set foreground colour according to the backgrond colour
        if (bgr + bgg + bgb) > 384:  # 384 equates to average rgb values of 128
                                     # per colour components and therefore
                                     # represents a mid values
            self.__fgr = self.__fgg = self.__fgb = 0  # dark fg colour
        else:
            self.__fgr = self.__fgg = self.__fgb = 255  # light fg color

        # set strings used to setting widget colours
        self.__bg_str = "#%.2x%.2x%.2x" % (self.__bgr, self.__bgg, self.__bgb)
        self.__fg_str = "#%.2x%.2x%.2x" % (self.__fgr, self.__fgg, self.__fgb)

        # now set the treeview colours
        self.__title_renderer.set_property("cell-background", self.__bg_str)
        self.__title_renderer.set_property("foreground", self.__fg_str)

        self.__active_renderer.set_property("cell-background", self.__bg_str)
        self.__active_renderer.set_property("foreground", self.__fg_str)

        self.__close_renderer.set_property("cell-background", self.__bg_str)
        self.__icon_renderer.set_property("cell-background", self.__bg_str)

    def win_shown(self, widget):
        """ Event handler for the window's show event

            Get the window's size so that its position can be set and mouse
            areas created
        """

        if build_gtk2:
            self.set_win_position()
        else:
            if (self.__win_w == 0) or (self.__win_h == 0):
                self.__win_w, self.__win_h = self.get_size()

            # gtk3 - if the list store contains any items we'll get configure
            # events firing after this event, so we don't actually know the
            # correct window size yet. Therefore, don't call set_win_position
            # here, just let the configure event do it. If there aren't any
            # list store items then we can set the window position...
            if len(self.__list_store) == 0:
                self.set_win_position()

    def set_win_position(self):
        """
            Move the window so that it appears near the panel and centrered on
            the app (has to be done here for Gtk3 reasons

            Create mouse areas as required so we can check when the mouse
            leaves the window

            Instantiate a timer to periodically check the mouse cursor position

        """

        def create_rect(x, y, w, h):
            """ Convenience function to create and return a Gdk.Rectangle
                (needed with Gtk3)
            """

            if build_gtk2:
                rect = Gdk.Rectangle(0, 0, 0, 0)
            else:
                rect = Gdk.Rectangle()

            rect.x = x
            rect.y = y
            rect.width = w
            rect.height = h

            return rect

        panel_space = 10 # how many pixels away from the panel the window list
                         # will appear

        win_border = 15  # size of the border (in pixels) around the window
                         # list where the mouse must remain, outside of which
                         # the window list will hide

        screen = self.get_screen()
        screen_w = screen.get_width()
        screen_h = screen.get_height()

        # if the size of the window hasnt been set (because the configure-event
        # doesn't always fire if the window list is empty) use an alternative
        # method to get the window width and height
        # work out where to place the window - adjacent to the panel and
        #  centered on the highlighted dock app and add appropriate mouse areas

        # first, a mouse area to cover the entire applet
        self.add_mouse_area(create_rect(self.__applet_x, self.__applet_y,
                                        self.__applet_w, self.__applet_h))

        app_alloc = self.__the_app.drawing_area.get_allocation()

        if self.__panel_orient == MatePanelApplet.AppletOrient.RIGHT:
            centre_pos = self.__app_y + app_alloc.height / 2
            win_x = self.__applet_w + panel_space
            win_y = centre_pos - (self.__win_h/2)

            # adjust win_y in case we're off the top the screen...
            if win_y < panel_space:
                win_y = panel_space

            # adjust win_y if case the window list extends beyound the end of
            # the panel ..
            if (win_y + self.__win_h) > screen_h:
                win_y = screen_h - self.__win_h - panel_space

            # setup a new mouse area covering the window (minus a border) and
            # extending to the panel
            self.add_mouse_area(create_rect(self.__applet_x,
                                            win_y - win_border,
                                            win_x + self.__win_w + win_border,
                                            self.__win_h + (2*win_border)))

        elif self.__panel_orient == MatePanelApplet.AppletOrient.LEFT:
            centre_pos = self.__app_y + app_alloc.height / 2
            win_x = self.__applet_x - panel_space - self.__win_w
            win_y = centre_pos - (self.__win_h/2)

            # adjust win_y in case we're off the top the screen...
            if win_y < panel_space:
                win_y = panel_space

            # adjust win_y if case the window list extends beyound the end of
            # the panel ..
            if (win_y + self.__win_h) > screen_h:
                win_y = screen_h - win_h - panel_space

            # setup a new mouse area covering the window (minus a border) and
            # extending to the panel
            self.add_mouse_area(create_rect(win_x - win_border,
                                            win_y - win_border,
                                            (self.__win_w + win_border +
                                             panel_space + app_alloc.width),
                                            self.__win_h + (2 * win_border)))

        elif self.__panel_orient == MatePanelApplet.AppletOrient.DOWN:
            centre_pos = self.__app_x + app_alloc.width / 2
            win_x = centre_pos - (self.__win_w / 2)
            win_y = self.__applet_h + panel_space

            # adjust win_x in case we're off the left of the screen...
            if win_x < panel_space:
                win_x = panel_space

            # adjust win_x if case the window list extends beyond the end of
            # the panel ..
            if (win_x + self.__win_w) > screen_w:
                win_x = screen_w - self.__win_w - panel_space

            # setup a new mouse area covering the window (minus a border) and
            # extending to the panel
            self.add_mouse_area(create_rect(win_x - win_border,
                                            self.__applet_y,
                                            self.__win_w + (2*win_border),
                                            win_y + self.__win_h + win_border))
        else:
            centre_pos = self.__app_x + app_alloc.width / 2
            win_x = centre_pos - (self.__win_w / 2)
            win_y = self.__applet_y - panel_space - self.__win_h

            # adjust win_x in case we're off the left of the screen...
            if win_x < panel_space:
                win_x = panel_space

            # adjust win_x if case the window list extends beyond the end of
            # the panel ..
            if (win_x + self.__win_w) > screen_w:
                win_x = screen_w - self.__win_w - panel_space

            # setup a new mouse area covering the window (minus a border) and
            # extendingto the panel
            self.add_mouse_area(create_rect(win_x - win_border,
                                            win_y - win_border,
                                            self.__win_w + (2*win_border),
                                            self.__win_h + win_border +
                                            panel_space + app_alloc.height))

        self.move(win_x, win_y)

        if self.__timer_id is not None:
            GObject.source_remove(self.__timer_id)

        self.__timer_id = GObject.timeout_add(CONST_TIMER_DELAY, self.do_timer)

    def win_configure(self, widget, event):
        """ Event handler for the window's configure event

        Stores the new width and height of the window

        Args:
            widget : the widget that caused the event (i.e. self)
            event  : the event parameters
        """

        # if the new size of the window isn't the same as the old one, we need
        # to recaclulate the window position and mouse areas

        return
        if (event.width != self.__win_w) or (event.height != self.__win_h):
            self.__win_w = event.width
            self.__win_h = event.height

            self.set_win_position()

    def size_allocate(self, widget, event):
        if (event.width != self.__win_w) or (event.height != self.__win_h):
            self.__win_w = event.width
            self.__win_h = event.height

            self.set_win_position()

    # TODO: investigate reason for shape_combine_region not working
    #def size_allocate(self, widget, allocation):
    #    def rounded_rectangle(cr, x, y, w, h, r=20):
    #        # Attribution:
    #        # https://stackoverflow.com/questions/2384374/rounded-rectangle-in-pygtk
    #        # This is just one of the samples from
    #        # http://www.cairographics.org/cookbook/roundedrectangles/
    #        #   A****BQ
    #        #  H      C
    #        #  *      *
    #        #  G      D
    #        #   F****E
    #
    #            cr.move_to(x + r, y)                      # Move to A
    #        cr.line_to(x + w - r, y)                    # Straight line to B
    #        cr.curve_to(x + w, y, x + w, y ,x + w, y + r)
    #        # Curve to C, Control points are both at Q
    #        cr.line_to(x + w, y + h - r)                  # Move to D
    #        cr.curve_to(x + w,y + h, x + w,y + h, x + w - r, y +h ) # Curve to E
    #        cr.line_to(x + r, y + h)                    # Line to F
    #        cr.curve_to(x, y + h, x, y + h, x, y + h - r) # Curve to G
    #        cr.line_to(x,y+r)                      # Line to H
    #        cr.curve_to(x,y,x,y,x+r,y)             # Curve to A
    #
    #    print("size allocated %d %d %d %d" % (allocation.x, allocation.y,
    #                                           allocation.width, allocation.height))
    #
    #    surface = cairo.ImageSurface(cairo.FORMAT_ARGB32,
    #                                   allocation.width,
    #                                   allocation.height)
    #
    #    ctx = cairo.Context(surface)
    #    rounded_rectangle(ctx, allocation.x + 5 , allocation.y + 5,
    #                      allocation.width - 10, allocation.height - 10)
    #    ctx.set_source_rgba(1, 1, 1, 1)
    #
    #    ctx.fill()
    #
    #    region = Gdk.cairo_region_create_from_surface(surface)
    #    self.props.window.shape_combine_region(region, 0, 0)

    def draw_top_border(self, drawing_area, event):
        """
            Draw the top of a rectangle with rounded corners to provide
            a border for the window
        """
        # in gtk3 the last param is a cairo context, in gtk2 we need to
        # create one
        if build_gtk2:
            ctx = drawing_area.window.cairo_create()
            ctx.rectangle(event.area.x, event.area.y,
                          event.area.width, event.area.height)
            ctx.clip()
        else:
            ctx = event

        alloc = drawing_area.get_allocation()

        # fill with background the background colour first
        ctx.rectangle(0, 0, alloc.width, alloc.height)
        ctx.set_source_rgb(self.__bgr / 255, self.__bgg / 255,
                           self.__bgb / 255)
        ctx.fill()

        # do the actual drawing
        ctx.set_operator(cairo.OPERATOR_OVER)
        ctx.set_line_width(self.__line_width)
        ctx.set_line_join(cairo.LINE_JOIN_ROUND)
        ctx.set_source_rgb(self.__fgr / 255, self.__fgg / 255,
                           self.__fgb / 255)
        ctx.move_to(self.__border_line, alloc.height)
        ctx.line_to(self.__border_line,
                    self.__border_line + self.__line_curve)
        ctx.curve_to(self.__border_line,
                     self.__border_line + self.__line_curve,
                     self.__border_line, self.__border_line,
                     self.__border_line + self.__line_curve,
                     self.__border_line)
        ctx.line_to(alloc.width - self.__border_line - self.__line_curve,
                    self.__border_line)
        ctx.curve_to(alloc.width - self.__border_line - self.__line_curve,
                     self.__border_line,
                     alloc.width - self.__border_line, self.__border_line,
                     alloc.width - self.__border_line,
                     self.__border_line + self.__line_curve)
        ctx.line_to(alloc.width - self.__border_line, alloc.height)
        ctx.stroke()

    def draw_left_border(self, drawing_area, event):
        """
            Draw the left hand side of the window border
        """

        if build_gtk2:
            ctx = drawing_area.window.cairo_create()
            ctx.rectangle(event.area.x, event.area.y,
                          event.area.width, event.area.height)
            ctx.clip()
        else:
            ctx = event

        alloc = drawing_area.get_allocation()

        # fill with background colour
        ctx.rectangle(0, 0, alloc.width, alloc.height)
        ctx.set_source_rgb(self.__bgr / 255, self.__bgg / 255,
                           self.__bgb / 255)
        ctx.fill()

        ctx.set_operator(cairo.OPERATOR_OVER)
        ctx.set_line_width(self.__line_width)
        ctx.set_line_join(cairo.LINE_JOIN_ROUND)
        ctx.set_source_rgb(self.__fgr / 255, self.__fgg / 255,
                           self.__fgb / 255)

        ctx.move_to(self.__border_line, 0)
        ctx.line_to(self.__border_line, alloc.height)
        ctx.stroke()

    def draw_right_border(self, drawing_area, event):
        """
            Draw the right hand side of the window border
        """

        if build_gtk2:
            ctx = drawing_area.window.cairo_create()
            ctx.rectangle(event.area.x, event.area.y,
                          event.area.width, event.area.height)
            ctx.clip()
        else:
            ctx = event
        alloc = drawing_area.get_allocation()

        ctx.rectangle(0, 0, alloc.width, alloc.height)
        ctx.set_source_rgb(self.__bgr / 255, self.__bgg / 255,
                           self.__bgb / 255)
        ctx.fill()
        ctx.set_operator(cairo.OPERATOR_OVER)
        ctx.set_line_width(self.__line_width)
        ctx.set_line_join(cairo.LINE_JOIN_ROUND)
        ctx.set_source_rgb(self.__fgr / 255, self.__fgg / 255,
                           self.__fgb / 255)

        ctx.move_to(alloc.width - self.__border_line, 0)
        ctx.line_to(alloc.width - self.__border_line, alloc.height)
        ctx.stroke()

    def draw_bottom_border(self, drawing_area, event):
        """
            Draw the bottom of the window border with rounded corners
        """

        if build_gtk2:
            ctx = drawing_area.window.cairo_create()
            ctx.rectangle(event.area.x, event.area.y,
                          event.area.width, event.area.height)
            ctx.clip()
        else:
            ctx = event

        alloc = drawing_area.get_allocation()
        ctx.rectangle(0, 0, alloc.width, alloc.height)
        ctx.set_source_rgb(self.__bgr / 255, self.__bgg / 255,
                           self.__bgb / 255)
        ctx.fill()

        ctx.set_operator(cairo.OPERATOR_OVER)
        ctx.set_line_width(self.__line_width)
        ctx.set_line_join(cairo.LINE_JOIN_ROUND)
        ctx.set_source_rgb(self.__fgr / 255, self.__fgg / 255,
                           self.__fgb / 255)
        ctx.move_to(self.__border_line, 0)
        ctx.line_to(self.__border_line,
                           alloc.height - self.__border_line - self.__line_curve)
        ctx.curve_to(self.__border_line,
                     alloc.height - self.__border_line - self.__line_curve,
                     self.__border_line, alloc.height - self.__border_line,
                     self.__border_line + self.__line_curve,
                     alloc.height - self.__border_line)

        ctx.line_to(alloc.width - self.__border_line - self.__line_curve,
                    alloc.height - self.__border_line)
        ctx.curve_to(alloc.width - self.__border_line - self.__line_curve,
                     alloc.height - self.__border_line,
                     alloc.width - self.__border_line,
                     alloc.height - self.__border_line,
                     alloc.width - self.__border_line,
                     alloc.height - self.__border_line - self.__line_curve)
        ctx.line_to(alloc.width - self.__border_line, 0)
        ctx.stroke()

    def win_hidden(self, widget):
        """ Event handler for the window's hide event

            Delete the timer object

        """

        if self.__timer_id is not None:
            GObject.source_remove(self.__timer_id)
            self.__timer_id = None

    def query_tooltip(self, widget, x, y, keyboard_mode, tooltip):
        """ Handler for the query-tooltip event to determine whether or not
            to show the 'Close window' tooltip

            If the tooltip was triggered by the keyboard, don't do anything

            Get the mouse coords, and if the mouse if located over the close
            icon show the tooltip

        Args:
            widget - the widget that received the event i.e. our treeview
            x - mouse x coord
            y - mouse y coord
            keyboard_mode - was the tooltip triggered by the keyboard?
            tooltip - the tooltip object that will be displayed

        Returns:
            True to display the tooltip, False otherwise
        """

        if keyboard_mode:
            return False

        return False
#        path, col, xrel, yrel = self.__tree_view.get_path_at_pos(x, y)

#        if col == self.__col_close:
#            cell_area = self.__tree_view.get_cell_area(path, col)
#            if (x >= cell_area.x + cell_area.width  - CONST_CLOSE_ICON_SIZE) and \
#               (y <= cell_area.y + CONST_CLOSE_ICON_SIZE):
#                tooltip.set_text("Close window")
#                return True
#            else:
#                return False
#        else:
#            return False

    def button_release(self, widget, event):
        """ Handler for the button release event

        If the middle or right mouse button was pressed, do nothing

        If the mouse button was released over the close icon, close the
        associated window

        If the mouse button was released over any other part of the tree view
        item, activate the associated  window

        Finally, hide the window list

        Args:

            widget : the widget the received the signal i.e. our treeview
            event  : the event parameters

        Returns:
            True: to stop any other handlers from being invoked
        """

        if event.button != 1:
            return False  # let other handlers run

        path, col, xrel, yrel = self.__tree_view.get_path_at_pos(event.x, event.y)
        sel_iter = self.__list_store.get_iter(path)

        win = self.__list_store.get_value(sel_iter, 3)
        action = self.__list_store.get_value(sel_iter, 4)
        if (win is None) and (action is None):
            # this will allow e.g. an item to be added which just contains
            # the name of app, which can then be clicked to launch the
            # so launch the app
            self.hide()
            self.the_app.start_app()
            return True

        if col == self.__col_close:
            cell_area = self.__tree_view.get_cell_area(path, col)
            if (event.x >= cell_area.x + cell_area.width  - CONST_CLOSE_ICON_SIZE) and \
               (event.y <= cell_area.y + CONST_CLOSE_ICON_SIZE):
                win.close(event.time)
                self.hide()
                return True

        if win is not None:
            # if the window to be activated is not on the current workspace,
            # switchto that workspace
            wnck_aws = self.wnck_screen.get_active_workspace()
            wnck_ws = win.get_workspace()

            # the windows's current workspace can be None if it is pinned to all
            # workspaces or it is not on any at all...
            # (fix for https://bugs.launchpad.net/ubuntu/+source/mate-dock-applet/+bug/1550392 and
            # https://bugs.launchpad.net/ubuntu-mate/+bug/1555336 (regarding software updater))
            if (wnck_aws is not None) and (wnck_ws is not None) and \
               (wnck_aws != wnck_ws):
                    wnck_ws.activate(0)
                    sleep(0.01)
            win.activate(0)

            # set the active indicator on the newly activated window
            # first, reset the current active indicator in the list store
            for list_item in self.__list_store:
                list_item[0] = ""
                list_item[5] = None

            # now set active indicator on the current item
            self.__list_store.set_value(sel_iter, 0, CONST__ACTIVE_TEXT)
            self.__list_store.set_value(sel_iter, 5, self.__pb_active)

            return True

        if action is not None:
            action.activate()
            self.hide()

        return True

    def do_timer(self):
        """
            Check the current mouse position and if it is not within any of the
            rectangles in self.__mouse_area hide the window
        """

        # get the mouse x y
        root_win, x, y, mask = self.get_screen().get_root_window().get_pointer()
        if not self.point_is_in_mouse_areas(x, y):
            self.hide()
            self.__timer_id = None
            return False

        return True

    def add_separator(self):
        """ Convenience method to add a separator to the list

        If there are no items currently in the list then the separator
        won't be added
        """

        if len(self.__list_store) > 0:
            self.add_to_list(False, CONST_SEP, None, None, None)

    def add_to_list(self, is_active, title, window, action, show_icon):
        """ Add an item to the window list

        Args:
            is_active - True if this is a window and it is active,
                         False otherwise
            title - the title of the window or the action
            window - the wnck window relating to the app (can be None)
            action - a GTK Action to be activated if the item is clicked
                    (can be None)
            show_icon - if True the app's icon will be shown alongside the
                        item in the list
        """

        # set the active indicator
        if is_active:
            active_text = CONST__ACTIVE_TEXT
        else:
            active_text = ""

#        if icon is None:
#            icon_pb = self.render_icon(Gtk.STOCK_EXECUTE,
#                                       Gtk.IconSize.MENU, None)
#        else:
#            icon_pb = icon

        if show_icon:
            app_icon = self.__app_pb
        else:
            app_icon = None

        if active_text != "":
            app_icon = self.__pb_active

        if window is None:
            close_icon = None
        else:
            close_icon = self.__pb_close

        self.__list_store.append([active_text, title,
                                  close_icon, window, action,
                                  app_icon])

    def clear_win_list(self):
        """ Clear the list of open windows """

        self.__list_store.clear()

    def win_button_press(self, widget, event):
        """ this is for debug puposes only"""
        Gtk.main_quit()

    def setup_list(self, win_on_cur_ws_only):
        """ Setup the app list

        Set the app name

        Re-create the close icon in case the icon theme has changed

        For every window the app has open add an entry containing the app icon,
        window title, an indicator if the window is the active window, and a
        close icon

        Args:
            win_on_cur_ws_only : boolean - whether to show only windows which
                                 are on the current workspace, or show windows
                                 for all workspaces

        """

        self.create_close_pixbuf()
        self.create_active_pixbuf()

        # reduce the size of the window - it will autosize to fit the contents
        if build_gtk2:
            self.__col_title.set_sizing(Gtk.TreeViewColumnSizing.FIXED)
            self.__col_title.set_fixed_width(150)
            self.resize(100, 10)
            self.__col_title.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE)

        win_list = self.__the_app.get_wnck_windows()

        wnck_aws = self.wnck_screen.get_active_workspace()
        add_to_list = False
        for win in win_list:
            if win_on_cur_ws_only:
                # only add if the window is on the current workspace
                add_to_list = win.is_on_workspace(wnck_aws)
            else:
                add_to_list = True

            if add_to_list:
                is_active = (len(win_list) == 1) or \
                            (win == self.__the_app.last_active_win)

                self.add_to_list(is_active, win.get_name(), win, None, False)

    def mbutton_press(self, widget, event):
        """ this is for debug purposes only and demonstrates that menu.popup does
            not work with Gtk 2
        """

        self.menu = Gtk.Menu()
        menu_item = Gtk.MenuItem("A menu item")
        self.menu.append(menu_item)
        menu_item.show()

        self.menu.popup(None, None, None, event.button, event.time)

    def clear_mouse_areas(self):
        """ Clear the mouse areas list """
        self.__mouse_areas = []

    def add_mouse_area(self, rect):
        """ Add a rectangle to the __mouse_area_list

            Args: rect - a Gdk.Rectangle
        """
        self.__mouse_areas.append(rect)

    def point_is_in_mouse_areas(self, x, y):
        """ Checks to see if a specified position on the screen is within any of
            the self.__mouse_areas rectangle list

            Args:
                x : the x position
                y : the y position

            Returns:
                True if the position is within one of the rectangles in
                self.__mouse_areas, False otherwise
        """

        for rect in self.__mouse_areas:
            if ((x >= rect.x) and (x <= rect.x + rect.width)) and \
               ((y >= rect.y) and (y <= rect.y + rect.height)):
                return True

        return False

    def get_app(self):
        """ Return the docked app the window list refers to

            Returns:
                A docked_app
        """

        return self.__the_app

    def set_app(self, app):
        """ Set the docked app the window list refers to

            Draw the app icon at an appropriate size for the list

            Args : app - a docked_app
        """

        self.__the_app = app

        if self.__icontheme.has_icon(self.__the_app.icon_name):

            # draw the app icon at the size we want
            icon_info = self.__icontheme.choose_icon([self.__the_app.icon_name, None],
                                                     self.__icon_size, 0)

            try:
                pixbuf = icon_info.load_icon()
            except GLib.GError:
                # default to a stock icon if we couldn't load the app
                # icon
                pixbuf = self.render_icon(Gtk.STOCK_EXECUTE,
                                          Gtk.IconSize.DND, None)
        else:
            pixbuf = self.the_app.app_pb.scale_simple(self.__icon_size,
                                                self.__icon_size,
                                                GdkPixbuf.InterpType.BILINEAR)

        self.__app_pb = pixbuf

    the_app = property(get_app, set_app)

    def get_icontheme(self):
        """ Return the icontheme

            Returns:
                A Gtk.Icontheme
        """

        return self.__icontheme

    def set_icontheme(self, the_icontheme):
        """ Sets the icontheme currently being used

            Args : the_icontheme
        """

        self.__icontheme = the_icontheme

    icontheme = property(get_icontheme, set_icontheme)

    def set_panel_orient(self, panel_orient):
        """ Set the variable which records the orientation of the panel the
            applet is on

        Args:
                panel_orient : the panel  orientation
        """

        self.__panel_orient = panel_orient

    def set_app_root_coords(self, x, y):
        """ Sets the x and y root coords of the app
        """

        self.__app_x = x
        self.__app_y = y

    def set_applet_details(self, applet_x, applet_y, applet_w, applet_h):
        """ Sets the variables which record the root coords and size of the
            applet

            Args:
                applet_x : the x position of the top left of the applet
                           (root coords)
                applet_y : the y position of the top left of the applet
                           (root coords)
                applet_w : the width of the applet
                applet_h : the height of the applet

        """

        self.__applet_x = applet_x
        self.__applet_y = applet_y
        self.__applet_w = applet_w
        self.__applet_h = applet_h

    def draw_title(self, column, cell_renderer, tree_model, tree_iter, data):
        title = tree_model.get_value(tree_iter, 1)
        if tree_model.get_value(tree_iter, 0) == CONST__ACTIVE_TEXT:
            cell_renderer.set_property('markup', "<b><i>%s</i></b>" % title)
        else:
            # if there is no window of action associatied with this item
            # it must be the name of a non running app. Format the title
            # differently if so...
            if (tree_model.get_value(tree_iter, 3) is None) and \
               (tree_model.get_value(tree_iter, 4) is None):
                cell_renderer.set_property('markup', "<b>%s</b>" % title)
            else:
                cell_renderer.set_property('markup', title)

    def check_sep(self, model, iter, data=None):
        """ Check to see if the current row is to be displayed as a separator

            Args :
                model : the treeview model (will be self.__list_store)
                iter : the roow in the model we're interested in

            Returns:
                Bool
        """

        title = model.get_value(iter, 1)
        return (title == CONST_SEP)


def main():
    """
    main function - debugging code goes here
    """

#    thewin = DockWinList()
#    thewin.set_app_name("Testing....")
#    thewin.add_to_list(None, False, "Win 1")
#    thewin.add_to_list(None, True, "Win 2 is active")
#    thewin.add_to_list(None, False, "Win 3")
#    thewin.show_all()

#    thewin.move(100, 110)
#    pos = thewin.get_position()
#    size = thewin.get_size()
#    print("pos %d %d" %(pos[0], pos[1]))
#    print("size %d %d" %(size[0], size[1]))
#    thewin.add_mouse_area(Gdk.Rectangle(pos[0]-15, pos[1]-15, size[0]+30, size[1]+30))
#    thewin.add_mouse_area(Gdk.Rectangle(0, 0, 48, 500))
#    thewin.add_mouse_area(Gdk.Rectangle(48, 110, 100, size[1]))
#    Gtk.main()
    return

if __name__ == "__main__":
    main()
