1#!/usr/local/bin/python3.8
2"""
3    Provide a base class for the dock's popup windows.
4
5    Such a window will function in a similar way to a tooltip i.e.
6    it will appear when the mouse hovers over a dock icon and
7    will disappear if the mouse moves away from the window or
8    the dock applet.
9
10    The window's foreground/background colours will be set from the current
11    theme or if the dock applet is setting the panel colour, the panel colours
12
13    The will use a grid/table to display a border around the window contents,
14    and descendant classes will need to create and set the window's
15    main widget/container
16
17"""
18
19#
20# Copyright (C) 1997-2003 Free Software Foundation, Inc.
21#
22# This program is free software; you can redistribute it and/or
23# modify it under the terms of the GNU General Public License as
24# published by the Free Software Foundation; either version 2 of the
25# License, or (at your option) any later version.
26#
27# This program is distributed in the hope that it will be useful, but
28# WITHOUT ANY WARRANTY; without even the implied warranty of
29# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
30# General Public License for more details.
31#
32# You should have received a copy of the GNU General Public License
33# along with this program; if not, write to the Free Software
34# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
35# 02110-1301, USA.
36#
37# Author:
38#     Robin Thompson
39
40# do not change the value of this variable - it will be set during build
41# according to the value of the --with-gtk3 option used with .configure
42
43build_gtk2 = False
44
45import gi
46if build_gtk2:
47    gi.require_version("Gtk", "2.0")
48    gi.require_version("Wnck", "1.0")
49else:
50    gi.require_version("Gtk", "3.0")
51    gi.require_version("Wnck", "3.0")
52
53gi.require_version("MatePanelApplet", "4.0")
54
55from gi.repository import Gtk
56from gi.repository import GdkPixbuf
57from gi.repository import Gio
58from gi.repository import Gdk
59from gi.repository import GObject
60from gi.repository import MatePanelApplet
61
62import cairo
63from time import sleep
64
65import docked_app
66from math import pi
67
68
69CONST_TIMER_DELAY = 1000
70CONST_ICON_SIZE = 16
71
72
73class DockPopup(Gtk.Window):
74    """
75
76        Attributes : __mouse_areas : a list containing Gdk.Rectangle objects -
77                     used when the window has been shown and defines the on
78                     screen areas in which the mouse pointer must stay,
79                     otherwise the window list will be hidden. The rectangles
80                     should therefore include the applet, the area between
81                     the window and the applet, and the window itself with a
82                     suitable buffer area around it
83            __timer_id : a ref to a timer used for periodically checking the
84                         mouse cursor position to see if it is within the areas
85                         specified in __mouse_areas
86            __the_app : the docked_app to which the window list relates
87            __icontheme : used for drawing app icons in the popup. This
88                          is set from the Gtk.Icon used by the dock, and
89                          will therefore track changes to the icon theme whilst
90                          the dock is running
91            __icon_size : the size in pixels at which app icons will be drawn
92
93            __win_w : the width of the window
94            __win_h : the height of the window
95            __bgr, __bgg, __bgb : the r,g,b panel colour components (0-255)
96            __fgr, __fgg, __fgb : the r,g,b foreground colour components
97            __hlr, __hlg, __hlb : the r,g,b highlight colour components
98
99            The attributes below are used for positioning this window relative
100            to the applet and it's panel:
101            __app_x : the x position of the docked app in root coordinates
102            __app_y : the y position of the docked app in root coordinates
103            __applet_x : the x position of the applet in root coordinates
104            __applet_y : the y position of the applet in root coordinates
105            __applet_w : the width of the applet in pixels
106            __applet_h : the height of the applet in pixels
107            __panel_orient : the orienation of the MATE panel the applet is on
108
109            __do_window_shaping : whether or not the window can be shaped,
110                                  e.g. have rounded corners. Depends on
111                                  Gtk3 and gi module >= 3.26.0
112    """
113
114    def __init__(self, wnck_screen, panel_orient, scroll_adj):
115        """
116        create the window and its contents
117
118        Args:
119            wnck_screen: the wnck_screen of the applet
120            panel_orient : the orientation of the panel
121            scroll_adj   : an adjustment to be applied to the window position
122                           because the dock has scrolling enabled
123        """
124
125        def create_drawing_area(width, height, draw_event):
126            # convenience func to create a drawing area with a specified
127            # width, height and draw event
128            da = Gtk.DrawingArea()
129            da.set_size_request(width, height)
130            if build_gtk2:
131                da.connect("expose-event", draw_event)
132            else:
133                da.connect("draw", draw_event)
134
135            return da
136
137        super().__init__(title="")
138        self.wnck_screen = wnck_screen
139        self.set_decorated(False)  # we don't want a titlebar..
140        self.set_skip_taskbar_hint(True)  # we don't want to be in the taskbar
141        self.set_accept_focus(False)
142
143        self.set_keep_above(True)
144
145        self.__scroll_adj = scroll_adj
146        self.__icontheme = None
147        self.__icon_size = 16  # small default icon size
148        self.__timer_id = None
149        self.__dismissed = False
150
151        self.__the_app = None
152        self.__app_pb = None
153
154        self.__win_w = 0
155        self.__win_h = 0
156
157        self.__app_x = 0
158        self.__app_y = 0
159        self.__panel_orient = panel_orient
160
161        self.__bgr = 0
162        self.__bgg = 0
163        self.__bgb = 0
164        self.__fgr = 0
165        self.__fgg = 0
166        self.__fgb = 0
167        self.__hlr = 0
168        self.__hlg = 0
169        self.__hlb = 0
170        self.__applet_x = 0
171        self.__applet_y = 0
172        self.__applet_w = 0
173        self.__applet_y = 0
174        self.__applet_h = 0
175
176        # create ui
177        if build_gtk2:
178            self.__grid = Gtk.VBox()
179            self.__grid.set_spacing(0)
180            self.__grid = Gtk.Table(rows=3, columns=3)
181            self.__grid.set_row_spacings(0)
182            self.__grid.set_col_spacings(0)
183        else:
184            self.__grid = Gtk.Grid()
185            self.__grid.set_orientation(Gtk.Orientation.VERTICAL)
186            self.__grid.hexpand = True
187            self.__grid.vexpand = True
188            self.hexpand = True
189            self.vexpand = True
190
191        # set vars used when drawing the window border
192        self.__border_width = 15
193        self.__border_line = 4
194        self.__line_width = 2
195        self.__line_curve = 5.0
196        self.__pointer_size = 16
197
198        # add drawing areas to all outsides of the 3x3 grid
199        # if we're showing shaped windows then the drawing area nearest the panel
200        # needs to be expanded so that that portion of the window it can be shaped
201        #  into a pointer to the app icon
202
203        if build_gtk2:
204            self.__do_window_shaping = False
205        else:
206            gi_ver = GObject.pygobject_version
207            self.__do_window_shaping = gi_ver[0] > 3 or ((gi_ver[0] == 3) and (gi_ver[1] >= 26))
208
209        da_height = self.__border_width
210        if self.__do_window_shaping and self.__panel_orient == MatePanelApplet.AppletOrient.DOWN:
211            da_height += self.__pointer_size
212        self.__da_top = create_drawing_area(self.__border_width,
213                                            da_height,
214                                            self.draw_top_border)
215
216        da_width = self.__border_width
217        if self.__do_window_shaping and self.__panel_orient == MatePanelApplet.AppletOrient.RIGHT:
218            da_width += self.__pointer_size
219        self.__da_left = create_drawing_area(da_width,
220                                             self.__border_width,
221                                             self.draw_left_border)
222
223        da_width = self.__border_width
224        if self.__do_window_shaping and self.__panel_orient == MatePanelApplet.AppletOrient.LEFT:
225            da_width += self.__pointer_size
226        self.__da_right = create_drawing_area(da_width,
227                                              self.__border_width,
228                                              self.draw_right_border)
229
230        da_height = self.__border_width
231        if self.__do_window_shaping and self.__panel_orient == MatePanelApplet.AppletOrient.UP:
232            da_height += self.__pointer_size
233        self.__da_bottom = create_drawing_area(self.__border_width,
234                                               da_height,
235                                               self.draw_bottom_border)
236
237        if build_gtk2:
238            self.__grid.attach(self.__da_top, 0, 3, 0, 1, xpadding=0,
239                               ypadding=0)
240            self.__grid.attach(self.__da_left, 0, 1, 1, 2)
241            self.__grid.attach(self.__da_right, 2, 3, 1, 2)
242            self.__grid.attach(self.__da_bottom, 0, 3, 2, 3)
243        else:
244            self.__grid.attach(self.__da_top, 0, 0, 3, 1)
245            self.__grid.attach(self.__da_left, 0, 1, 1, 1)
246            self.__grid.attach(self.__da_right, 2, 1, 1, 1)
247            self.__grid.attach(self.__da_bottom, 0, 2, 3, 1)
248
249        self.add(self.__grid)
250
251        self.__mouse_areas = []
252
253        # connect handlers for the show and hide events
254        self.connect("show", self.win_shown)
255        self.connect("hide", self.win_hidden)
256
257        self.connect("configure-event", self.win_configure)
258        self.connect("size-allocate", self.size_allocate)
259
260    def set_main_widget(self, widget):
261        """ Attaches the main component (a widget or container) to the center
262            position of the grid
263
264        Args:
265            widget : the widget or container to add
266
267        """
268        if build_gtk2:
269            self.__grid.attach(widget, 1, 2, 1, 2)
270        else:
271            self.__grid.attach(widget, 1, 1, 1, 1)
272
273    def set_colours(self, panel_colour):
274        """ Sets the window background, foreground and highlight colours
275            to default values
276
277        The background colour will match the panel containing the applet.
278
279        If a custom colour is set for the panel, use that for the background
280        and set the foreground colour to be either full white or black
281        (depending on the background colour). The highlight colour will Also
282        be set depending on the background colour.
283
284        For Gtk3, if the panel is set to use the theme's colours, the
285        background, foreground and  highilight colours will all be set from
286        the current theme
287
288        For Gtk2, where we can't access the styles associated with the current
289        theme because of introspection errors, set the background to black,
290        foreground to white and the highlight colour to a dark grey
291
292
293        Args:
294            panel_colour : If a custom panel colour has been set, this will
295                           be a tuple of 3 x int - the r, g, b colour
296                           components. Otherwise it will be None
297
298        """
299
300        if panel_colour is None:
301            if build_gtk2:
302                self.__bgr = self.__bgg = self.__bgb = 0
303                self.__fgr = self.__fgg = self.__fgb = 255
304                self.__hlr = self.__hlg = self.__hlb = 64
305            else:
306
307                context = self.get_style_context()
308                state = Gtk.StateType.NORMAL
309                # we want the colors for the MATE panel (preferably), or the
310                # Gnome menu bar
311                # context.add_class("gnome-panel-menu-bar")
312                # context.add_class("mate-panel-menu-bar")
313
314                # background
315                c_info = context.lookup_color("dark_bg_color")
316                if c_info[0]:
317                    bgcol = c_info[1]
318                    self.__bgr = int(bgcol.red * 255)
319                    self.__bgg = int(bgcol.green * 255)
320                    self.__bgb = int(bgcol.blue * 255)
321
322                c_info = context.lookup_color("dark_fg_color")
323                if c_info[0]:
324                    fcol = c_info[1]
325                    self.__fgr = int(fcol.red * 255)
326                    self.__fgg = int(fcol.green * 255)
327                    self.__fgb = int(fcol.blue * 255)
328
329                sel_bg = context.lookup_color("theme_selected_bg_color")
330                if sel_bg[0]:
331                    hcol = sel_bg[1]
332                    self.__hlr = int(hcol.red * 255)
333                    self.__hlg = int(hcol.green * 255)
334                    self.__hlb = int(hcol.blue * 255)
335                else:
336                    # assume what is hopefully a decent looking highlight
337                    # colour
338                    self.__hlr = (self.__bgr + 64) % 256
339                    self.__hlg = (self.__bgg + 64) % 256
340                    self.__hlb = (self.__bgb + 64) % 256
341        else:
342            # custom panel colour...
343            self.__bgr = panel_colour[0]
344            self.__bgg = panel_colour[1]
345            self.__bgb = panel_colour[2]
346
347            # set foreground colour according to the background colour
348            # 384 equates to average rgb values of 128 per colour component and
349            # therefore represents a mid value
350            if (self.__bgr + self.__bgg + self.__bgb) > 384:
351
352                self.__fgr = self.__fgg = self.__fgb = 0  # dark fg colour
353            else:
354                self.__fgr = self.__fgg = self.__fgb = 255  # light fg color
355
356            # highlight colour
357            self.__hlr = (self.__bgr + 64) % 256
358            self.__hlg = (self.__bgg + 64) % 256
359            self.__hlb = (self.__bgb + 64) % 256
360
361    def set_bg_col(self, bgr, bgg, bgb):
362        """ Sets the background colour of the window
363
364        Also, set a foreground colour that will contrast with the background
365        colour (so we can read text etc...)
366
367        Args:
368            bgr, bgg, bgb : the background rgb colour components
369
370        """
371
372        self.__bgr = bgr
373        self.__bgg = bgg
374        self.__bgb = bgb
375
376        # set foreground colour according to the background colour
377        if (bgr + bgg + bgb) > 384:     # 384 equates to average rgb values of 128
378                                        # per colour component and therefore
379                                        # represents a mid value
380            self.__fgr = self.__fgg = self.__fgb = 0  # dark fg colour
381        else:
382            self.__fgr = self.__fgg = self.__fgb = 255  # light fg color
383
384    def set_fg_col(self, fgr, fgg, fgb):
385        """
386        Put some stuff here...
387        """
388
389        self.__fgr = fgr
390        self.__fgg = fgg
391        self.__fgb = fgb
392
393    def win_shown(self, widget):
394        """ Event handler for the window's show event
395
396            Get the window's size so that its position can be set and mouse
397            areas created
398        """
399
400        if build_gtk2:
401            self.set_win_position()
402        else:
403            if (self.__win_w == 0) or (self.__win_h == 0):
404                self.__win_w, self.__win_h = self.get_size()
405
406        self.start_mouse_area_timer()
407
408    def set_win_position(self):
409        """
410            Move the window so that it appears near the panel and centered on
411            the app (has to be done here for Gtk3 reasons)
412
413            Create mouse areas as required so we can check when the mouse
414            leaves the window
415
416            Instantiate a timer to periodically check the mouse cursor position
417
418        """
419
420        def create_rect(x, y, w, h):
421            """ Convenience function to create and return a Gdk.Rectangle
422                (needed with Gtk3)
423            """
424
425            if build_gtk2:
426                rect = Gdk.Rectangle(0, 0, 0, 0)
427            else:
428                rect = Gdk.Rectangle()
429
430            rect.x = x
431            rect.y = y
432            rect.width = w
433            rect.height = h
434
435            return rect
436
437        # set how many pixels away from the panel the window list will appear
438        if self.__do_window_shaping:
439            panel_space = 5
440        else:
441            panel_space = 10
442
443        # size of the border (in pixels) around the window
444        # list where the mouse must remain, outside of which
445        # the window list will hide
446        win_border = 15
447
448        screen = self.get_screen()
449
450        # get the monitor that the applet is on
451        # Note: we can't rely on the panel's dconf settings for this
452        # as the monitor setting there doesn't seem to work reliably
453        monitor = screen.get_monitor_at_point(self.__applet_x, self.__applet_y)
454        if build_gtk2:
455            mon_geom = create_rect(0, 0, 0, 0)
456            screen.get_monitor_geometry(monitor, mon_geom)
457        else:
458            mon_geom = screen.get_monitor_geometry(monitor)
459
460        # if the size of the window hasnt been set (because the configure-event
461        # doesn't always fire if the window list is empty) use an alternative
462        # method to get the window width and height
463        # work out where to place the window - adjacent to the panel and
464        #  centered on the highlighted dock app and add appropriate mouse areas
465
466        # first, a mouse area to cover the entire applet
467        self.add_mouse_area(create_rect(self.__applet_x, self.__applet_y,
468                                        self.__applet_w, self.__applet_h))
469
470        app_alloc = self.__the_app.drawing_area.get_allocation()
471
472        if self.__panel_orient == MatePanelApplet.AppletOrient.RIGHT:
473            centre_pos = self.__app_y + (app_alloc.height / 2) - self.__scroll_adj
474            win_x = self.__applet_x + self.__applet_w + panel_space
475            win_y = centre_pos - (self.__win_h / 2)
476
477            # adjust win_y in case we're off the top the screen, or the
478            # monitor ...
479            if win_y < mon_geom.y + panel_space:
480                win_y = panel_space
481
482            # adjust win_y if case the window list extends beyound the end of
483            # the panel ..
484            if (win_y + self.__win_h) > mon_geom.y + mon_geom.height:
485                win_y = mon_geom.y + mon_geom.height - self.__win_h - panel_space
486
487            # setup a new mouse area covering the window (minus a border) and
488            # extending to the panel
489            self.add_mouse_area(create_rect(self.__applet_x,
490                                            win_y - win_border,
491                                            win_x + self.__win_w + win_border,
492                                            self.__win_h + (2 * win_border)))
493
494        elif self.__panel_orient == MatePanelApplet.AppletOrient.LEFT:
495            centre_pos = self.__app_y + (app_alloc.height / 2) - self.__scroll_adj
496            win_x = self.__applet_x - panel_space - self.__win_w
497            win_y = centre_pos - (self.__win_h / 2)
498
499            # adjust win_y in case we're off the top the screen...
500            if win_y < mon_geom.y + panel_space:
501                win_y = mon_geom.y + panel_space
502
503            # adjust win_y if case the window list extends beyound the end of
504            # the panel ..
505            if (win_y + self.__win_h) > mon_geom.y + mon_geom.height:
506                win_y = mon_geom.y + mon_geom.height - self.__win_h - panel_space
507
508            # setup a new mouse area covering the window (minus a border) and
509            # extending to the panel
510            self.add_mouse_area(create_rect(win_x - win_border,
511                                            win_y - win_border,
512                                            (self.__win_w + win_border +
513                                             panel_space + app_alloc.width),
514                                            self.__win_h + (2 * win_border)))
515
516        elif self.__panel_orient == MatePanelApplet.AppletOrient.DOWN:
517            centre_pos = (self.__app_x + app_alloc.width / 2) - self.__scroll_adj
518            win_x = centre_pos - (self.__win_w / 2)
519            win_y = self.__applet_y + self.__applet_h + panel_space
520
521            # adjust win_x in case we're off the left of the screen...
522            if win_x < mon_geom.x + panel_space:
523                win_x = mon_geom.x + panel_space
524
525            # adjust win_x if case the window list extends beyond the end of
526            # the panel ..
527            if (win_x + self.__win_w) > mon_geom.x + mon_geom.width:
528                win_x = mon_geom.x + mon_geom.width - self.__win_w - panel_space
529
530            # setup a new mouse area covering the window (minus a border) and
531            # extending to the panel
532            self.add_mouse_area(create_rect(win_x - win_border,
533                                            self.__applet_y,
534                                            self.__win_w + (2 * win_border),
535                                            win_y + self.__win_h + win_border))
536        else:
537            centre_pos = (self.__app_x + app_alloc.width / 2) - self.__scroll_adj
538            win_x = centre_pos - (self.__win_w / 2)
539            win_y = self.__applet_y - panel_space - self.__win_h
540
541            # adjust win_x in case we're off the left of the screen...
542            if win_x < mon_geom.x + panel_space:
543                win_x = mon_geom.x + panel_space
544
545            # adjust win_x if case the window list extends beyond the end of
546            # the panel ..
547            if (win_x + self.__win_w) > mon_geom.x + mon_geom.width:
548                win_x = mon_geom.x + mon_geom.width - self.__win_w - panel_space
549
550            # setup a new mouse area covering the window (minus a border) and
551            # extendingto the panel
552            self.add_mouse_area(create_rect(win_x - win_border,
553                                            win_y - win_border,
554                                            self.__win_w + (2 * win_border),
555                                            self.__win_h + win_border +
556                                            panel_space + app_alloc.height))
557
558        self.move(win_x, win_y)
559
560    def start_mouse_area_timer(self):
561        """ Start the timer that that monitors the mouse position
562        """
563
564        # remove any old timer...
565        self.stop_mouse_area_timer()
566
567        self.__timer_id = GObject.timeout_add(CONST_TIMER_DELAY, self.do_timer)
568
569    def stop_mouse_area_timer(self):
570        """ Stop the timer that monitors the mouse position
571        """
572        #
573        if self.__timer_id is not None:
574            GObject.source_remove(self.__timer_id)
575            self.__timer_id = None
576
577    def win_configure(self, widget, event):
578        """ Event handler for the window's configure event
579
580        Stores the new width and height of the window
581
582        Args:
583            widget : the widget that caused the event (i.e. self)
584            event  : the event parameters
585        """
586
587        # if the new size of the window isn't the same as the old one, we need
588        # to recaclulate the window position and mouse areas
589
590        return
591
592    def size_allocate(self, widget, event):
593
594        def draw_rounded(cr, area, radius):
595            """ draws rectangles with rounded (circular arc) corners """
596            # Attribution: https://gist.github.com/kamiller/3013605
597
598            a, b, c, d = area
599            cr.arc(a + radius, c + radius, radius, 2 * (pi / 2), 3 * (pi / 2))
600            cr.arc(b - radius, c + radius, radius, 3 * (pi / 2), 4 * (pi / 2))
601            cr.arc(b - radius, d - radius, radius, 0 * (pi / 2), 1 * (pi / 2))  # ;o)
602            cr.arc(a + radius, d - radius, radius, 1 * (pi / 2), 2 * (pi / 2))
603            cr.close_path()
604
605            cr.stroke_preserve()
606
607        if (event.width != self.__win_w) or (event.height != self.__win_h):
608            self.__win_w = event.width
609            self.__win_h = event.height
610
611            self.set_win_position()
612
613        if not self.__do_window_shaping:
614            return
615
616        # round the corners of the portion of the window containing the widget and border
617        allocation = self.get_allocation()
618
619        surface = cairo.ImageSurface(cairo.FORMAT_ARGB32,
620                                     allocation.width,
621                                     allocation.height)
622
623        ctx = cairo.Context(surface)
624
625        if self.__panel_orient == MatePanelApplet.AppletOrient.UP:
626            draw_rounded(ctx, [allocation.x, allocation.width,
627                               allocation.y, allocation.height - self.__pointer_size], 10)
628        elif self.__panel_orient == MatePanelApplet.AppletOrient.DOWN:
629            draw_rounded(ctx, [allocation.x, allocation.width,
630                               allocation.y + self.__pointer_size,
631                               allocation.height], 10)
632        elif self.__panel_orient == MatePanelApplet.AppletOrient.RIGHT:
633            draw_rounded(ctx, [self.__pointer_size + 1, allocation.width,
634                               allocation.y, allocation.height], 10)
635        else:
636            draw_rounded(ctx, [allocation.x, allocation.width - self.__pointer_size,
637                               allocation.y, allocation.height], 10)
638
639        ctx.set_source_rgba(1, 1, 1, 1)
640
641        ctx.fill()
642
643        # now create a pointer to the app's icon in the dock
644        if self.__panel_orient == MatePanelApplet.AppletOrient.UP:
645            ctx.move_to(allocation.width / 2 - self.__pointer_size,
646                        allocation.height - self.__pointer_size)
647            ctx.line_to(allocation.width / 2, allocation.height)
648            ctx.line_to(allocation.width / 2 + self.__pointer_size,
649                        allocation.height - self.__pointer_size)
650        elif self.__panel_orient == MatePanelApplet.AppletOrient.DOWN:
651            ctx.move_to(allocation.width / 2 - self.__pointer_size,
652                        self.__pointer_size)
653            ctx.line_to(allocation.width / 2, 0)
654            ctx.line_to(allocation.width / 2 + self.__pointer_size,
655                        self.__pointer_size)
656        elif self.__panel_orient == MatePanelApplet.AppletOrient.RIGHT:
657            ctx.move_to(self.__pointer_size,
658                        allocation.height / 2 - self.__pointer_size)
659            ctx.line_to(0, allocation.height / 2)
660            ctx.line_to(self.__pointer_size,
661                        allocation.height / 2 + self.__pointer_size)
662        else:
663            ctx.move_to(allocation.width - self.__pointer_size,
664                        allocation.height / 2 - self.__pointer_size)
665            ctx.line_to(allocation.width, allocation.height / 2)
666            ctx.line_to(allocation.width - self.__pointer_size,
667                        allocation.height / 2 + self.__pointer_size)
668
669        ctx.stroke_preserve()
670        ctx.set_source_rgba(1, 1, 1, 1)
671        ctx.fill()
672
673        region = Gdk.cairo_region_create_from_surface(surface)
674        self.shape_combine_region(region)
675
676    def draw_top_border(self, drawing_area, event):
677        """
678            Draw the top of a rectangle with rounded corners to provide
679            a border for the window
680        """
681        # in gtk3 the last param is a cairo context, in gtk2 we need to
682        # create one
683        if build_gtk2:
684            ctx = drawing_area.window.cairo_create()
685            ctx.rectangle(event.area.x, event.area.y,
686                          event.area.width, event.area.height)
687            ctx.clip()
688        else:
689            ctx = event
690
691        alloc = drawing_area.get_allocation()
692
693        # fill with background the background colour first
694        ctx.rectangle(0, 0, alloc.width, alloc.height)
695        ctx.set_source_rgb(self.__bgr / 255, self.__bgg / 255,
696                           self.__bgb / 255)
697        ctx.fill()
698
699        # do the actual drawing
700        ctx.set_operator(cairo.OPERATOR_OVER)
701        ctx.set_line_width(self.__line_width)
702        ctx.set_line_join(cairo.LINE_JOIN_ROUND)
703        ctx.set_source_rgb(self.__fgr / 255, self.__fgg / 255,
704                           self.__fgb / 255)
705
706        # the position of the top, left and right border lines depend on whether or not we need to
707        # shape the window into a pointer to the app icon
708        top_extent = left_extent = self.__border_line
709        right_extent = alloc.width - self.__border_line
710        if self.__do_window_shaping:
711            if self.__panel_orient == MatePanelApplet.AppletOrient.DOWN:
712                top_extent += self.__pointer_size
713            elif self.__panel_orient == MatePanelApplet.AppletOrient.RIGHT:
714                left_extent += self.__pointer_size
715            elif self.__panel_orient == MatePanelApplet.AppletOrient.LEFT:
716                right_extent -= self.__pointer_size
717
718        ctx.move_to(left_extent, alloc.height)
719
720        ctx.line_to(left_extent, top_extent + self.__line_curve)
721
722        ctx.curve_to(left_extent,
723                     top_extent + self.__line_curve,
724                     left_extent, top_extent,
725                     left_extent + self.__line_curve,
726                     top_extent)
727
728        if self.__do_window_shaping and self.__panel_orient == MatePanelApplet.AppletOrient.DOWN:
729            # extend the border line into the pointer
730            ctx.line_to(alloc.width / 2 - self.__pointer_size + 1,
731                        top_extent)
732            ctx.line_to(alloc.width / 2, top_extent - self.__pointer_size + 1)
733            ctx.line_to(alloc.width / 2 + self.__pointer_size - 1,
734                        top_extent)
735
736        ctx.line_to(right_extent - self.__line_curve, top_extent)
737        ctx.curve_to(right_extent - self.__line_curve,
738                     top_extent,
739                     right_extent, top_extent,
740                     right_extent,
741                     top_extent + self.__line_curve)
742        ctx.line_to(right_extent, alloc.height)
743        ctx.stroke()
744
745    def draw_left_border(self, drawing_area, event):
746        """
747            Draw the left hand side of the window border
748        """
749
750        if build_gtk2:
751            ctx = drawing_area.window.cairo_create()
752            ctx.rectangle(event.area.x, event.area.y,
753                          event.area.width, event.area.height)
754            ctx.clip()
755        else:
756            ctx = event
757
758        alloc = drawing_area.get_allocation()
759
760        # fill with background colour
761        ctx.rectangle(0, 0, alloc.width, alloc.height)
762        ctx.set_source_rgb(self.__bgr / 255, self.__bgg / 255,
763                           self.__bgb / 255)
764        ctx.fill()
765
766        ctx.set_operator(cairo.OPERATOR_OVER)
767        ctx.set_line_width(self.__line_width)
768        ctx.set_line_join(cairo.LINE_JOIN_ROUND)
769        ctx.set_source_rgb(self.__fgr / 255, self.__fgg / 255,
770                           self.__fgb / 255)
771
772        left_extent = self.__border_line
773        # the position of the left border depends on whether or not we're doing window shaping
774        if self.__do_window_shaping and self.__panel_orient == MatePanelApplet.AppletOrient.RIGHT:
775            left_extent += self.__pointer_size
776
777        ctx.move_to(left_extent, 0)
778
779        if self.__do_window_shaping and self.__panel_orient == MatePanelApplet.AppletOrient.RIGHT:
780            # extend the border line into the pointer
781            ctx.line_to(left_extent,
782                        alloc.height / 2 - self.__pointer_size + 1)
783            ctx.line_to(self.__border_line, alloc.height / 2)
784
785            ctx.line_to(left_extent, alloc.height / 2 + self.__pointer_size - 1)
786
787        ctx.line_to(left_extent, alloc.height)
788        ctx.stroke()
789
790    def draw_right_border(self, drawing_area, event):
791        """
792            Draw the right hand side of the window border
793        """
794
795        if build_gtk2:
796            ctx = drawing_area.window.cairo_create()
797            ctx.rectangle(event.area.x, event.area.y,
798                          event.area.width, event.area.height)
799            ctx.clip()
800        else:
801            ctx = event
802        alloc = drawing_area.get_allocation()
803
804        ctx.rectangle(0, 0, alloc.width, alloc.height)
805        ctx.set_source_rgb(self.__bgr / 255, self.__bgg / 255,
806                           self.__bgb / 255)
807        ctx.fill()
808        ctx.set_operator(cairo.OPERATOR_OVER)
809        ctx.set_line_width(self.__line_width)
810        ctx.set_line_join(cairo.LINE_JOIN_ROUND)
811        ctx.set_source_rgb(self.__fgr / 255, self.__fgg / 255,
812                           self.__fgb / 255)
813
814        right_extent = alloc.width - self.__border_line
815        if self.__do_window_shaping and self.__panel_orient == MatePanelApplet.AppletOrient.LEFT:
816            right_extent -= self.__pointer_size
817
818        ctx.move_to(right_extent, 0)
819
820        if self.__do_window_shaping and self.__panel_orient == MatePanelApplet.AppletOrient.LEFT:
821            # extend the border line into the pointer
822            ctx.line_to(right_extent,
823                        alloc.height / 2 - self.__pointer_size + 1)
824            ctx.line_to(alloc.width - self.__border_line, alloc.height / 2)
825            ctx.line_to(right_extent, alloc.height / 2 + self.__pointer_size - 1)
826
827        ctx.line_to(right_extent, alloc.height)
828        ctx.stroke()
829
830    def draw_bottom_border(self, drawing_area, event):
831        """
832            Draw the bottom of the window border with rounded corners
833
834        """
835
836        if build_gtk2:
837            ctx = drawing_area.window.cairo_create()
838            ctx.rectangle(event.area.x, event.area.y,
839                          event.area.width, event.area.height)
840            ctx.clip()
841        else:
842            ctx = event
843
844        alloc = drawing_area.get_allocation()
845        ctx.rectangle(0, 0, alloc.width, alloc.height)
846        ctx.set_source_rgb(self.__bgr / 255, self.__bgg / 255,
847                           self.__bgb / 255)
848        ctx.fill()
849
850        ctx.set_operator(cairo.OPERATOR_OVER)
851        ctx.set_line_width(self.__line_width)
852        ctx.set_line_join(cairo.LINE_JOIN_ROUND)
853        ctx.set_source_rgb(self.__fgr / 255, self.__fgg / 255,
854                           self.__fgb / 255)
855
856        # the lower, right and left extents of the border depend on whether or not we need to
857        # shape the window into a pointer to the app icon
858        left_extent = self.__border_line
859        right_extent = alloc.width - self.__border_line
860        lower_extent = alloc.height - self.__border_line
861        if self.__do_window_shaping:
862            if self.__panel_orient == MatePanelApplet.AppletOrient.UP:
863                lower_extent -= self.__pointer_size
864            elif self.__panel_orient == MatePanelApplet.AppletOrient.RIGHT:
865                left_extent += self.__pointer_size
866            elif self.__panel_orient == MatePanelApplet.AppletOrient.LEFT:
867                right_extent -= self.__pointer_size
868
869        ctx.move_to(left_extent, 0)
870        ctx.line_to(left_extent,
871                    lower_extent - self.__line_curve)
872        ctx.curve_to(left_extent,
873                     lower_extent - self.__line_curve,
874                     left_extent, lower_extent,
875                     left_extent + self.__line_curve,
876                     lower_extent)
877
878        if self.__do_window_shaping and self.__panel_orient == MatePanelApplet.AppletOrient.UP:
879            # draw a pointer
880            ctx.line_to(alloc.width / 2 - self.__pointer_size + 1,
881                        lower_extent)
882            ctx.line_to(alloc.width / 2, lower_extent + self.__pointer_size - 1)
883            ctx.line_to(alloc.width / 2 + self.__pointer_size - 1,
884                        lower_extent)
885
886        ctx.line_to(right_extent - self.__line_curve, lower_extent)
887
888        ctx.curve_to(right_extent - self.__line_curve,
889                     lower_extent,
890                     right_extent,
891                     lower_extent,
892                     right_extent,
893                     lower_extent - self.__line_curve)
894        ctx.line_to(right_extent, 0)
895        ctx.stroke()
896
897    def win_hidden(self, widget):
898        """ Event handler for the window's hide event
899
900            Delete the timer object
901
902        """
903
904        self.stop_mouse_area_timer()
905
906    def do_timer(self):
907        """
908            Check the current mouse position and if it is not within any of the
909            rectangles in self.__mouse_area hide the window
910        """
911
912        # get the mouse x y
913        root_win, x, y, mask = self.get_screen().get_root_window().get_pointer()
914        if not self.point_is_in_mouse_areas(x, y):
915            self.hide()
916            self.__timer_id = None
917            return False
918
919        return True
920
921    def clear_mouse_areas(self):
922        """ Clear the mouse areas list """
923        self.__mouse_areas = []
924
925    def add_mouse_area(self, rect):
926        """ Add a rectangle to the __mouse_area_list
927
928            Args: rect - a Gdk.Rectangle
929        """
930        self.__mouse_areas.append(rect)
931
932    def point_is_in_mouse_areas(self, x, y):
933        """ Checks to see if a specified position on the screen is within any of
934            the self.__mouse_areas rectangle list
935
936            Args:
937                x : the x position
938                y : the y position
939
940            Returns:
941                True if the position is within one of the rectangles in
942                self.__mouse_areas, False otherwise
943        """
944
945        for rect in self.__mouse_areas:
946            if ((x >= rect.x) and (x <= rect.x + rect.width)) and \
947               ((y >= rect.y) and (y <= rect.y + rect.height)):
948                return True
949
950        return False
951
952    def get_app(self):
953        """ Return the docked app the window list refers to
954
955            Returns:
956                A docked_app
957        """
958
959        return self.__the_app
960
961    def set_app(self, app):
962        """ Set the docked app the window list refers to
963
964            Draw the app icon at an appropriate size for the list
965
966            Args : app - a docked_app
967        """
968
969        self.__the_app = app
970        if self.__icontheme is not None:
971            self.get_app_icon()
972
973    the_app = property(get_app, set_app)
974
975    def get_app_icon(self):
976        """ Draws the app icon and stores it in self.__app_pb for
977            later use
978
979        self.__the_app and the icon theme and size must have been set before
980        this is called....
981        """
982        if self.__icontheme.has_icon(self.__the_app.icon_name):
983
984            # draw the app icon at the size we want
985            icon_info = self.__icontheme.choose_icon([self.__the_app.icon_name,
986                                                     None],
987                                                     self.__icon_size, 0)
988
989            try:
990                pixbuf = icon_info.load_icon()
991            except GLib.GError:
992                # default to a stock icon if we couldn't load the app
993                # icon
994                pixbuf = self.render_icon(Gtk.STOCK_EXECUTE,
995                                          Gtk.IconSize.DND, None)
996        else:
997            pixbuf = self.the_app.app_pb.scale_simple(self.__icon_size,
998                                                      self.__icon_size,
999                                                      GdkPixbuf.InterpType.BILINEAR)
1000
1001        self.__app_pb = pixbuf
1002
1003    @property
1004    def bg_col(self):
1005        return self.__bgr, self.__bgg, self.__bgb
1006
1007    @property
1008    def fg_col(self):
1009        return self.__fgr, self.__fgg, self.__fgb
1010
1011    @property
1012    def hl_col(self):
1013        return self.__hlr, self.__hlg, self.__hlb
1014
1015    @property
1016    def icon_size(self):
1017        return self.__icon_size
1018
1019    @icon_size.setter
1020    def icon_size(self, size_in_pixels):
1021        self.__icon_size = size_in_pixels
1022        if (self.__the_app is not None) and \
1023           (self.__icontheme is not None):
1024            self.__get_app_icon()
1025
1026    @property
1027    def app_pb(self):
1028        return self.__app_pb
1029
1030    def get_icontheme(self):
1031        """ Return the icontheme
1032
1033            Returns:
1034                A Gtk.Icontheme
1035        """
1036
1037        return self.__icontheme
1038
1039    def set_icontheme(self, the_icontheme):
1040        """ Sets the icontheme currently being used
1041
1042            Args : the_icontheme
1043        """
1044
1045        self.__icontheme = the_icontheme
1046
1047    icontheme = property(get_icontheme, set_icontheme)
1048
1049    def da_pointer_draw(self, drawing_area, event):
1050
1051        ctx = event
1052        alloc = drawing_area.get_allocation()
1053
1054        ctx.rectangle(0, 0, alloc.width, alloc.height)
1055        ctx.set_source_rgb(self.__bgr / 255, self.__bgg / 255,
1056                           self.__bgb / 255)
1057        ctx.fill()
1058
1059    def set_app_root_coords(self, x, y):
1060        """ Sets the x and y root coords of the app
1061        """
1062
1063        self.__app_x = x
1064        self.__app_y = y
1065
1066    def set_applet_details(self, applet_x, applet_y, applet_w, applet_h):
1067        """ Sets the variables which record the root coords and size of the
1068            applet
1069
1070            Args:
1071                applet_x : the x position of the top left of the applet
1072                           (root coords)
1073                applet_y : the y position of the top left of the applet
1074                           (root coords)
1075                applet_w : the width of the applet
1076                applet_h : the height of the applet
1077
1078        """
1079
1080        self.__applet_x = applet_x
1081        self.__applet_y = applet_y
1082        self.__applet_w = applet_w
1083        self.__applet_h = applet_h
1084