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