Package screenlets
[hide private]
[frames] | no frames]

Source Code for Package screenlets

   1  # This application is released under the GNU General Public License  
   2  # v3 (or, at your option, any later version). You can find the full  
   3  # text of the license under http://www.gnu.org/licenses/gpl.txt.  
   4  # By using, editing and/or distributing this software you agree to  
   5  # the terms and conditions of this license.  
   6  # Thank you for using free software! 
   7   
   8  # Screenlets main module (c) RYX (aka Rico Pfaus) 2007 <ryx@ryxperience.com> 
   9  # 
  10  ##@mainpage 
  11  # 
  12  ##@section intro_sec General Information 
  13  # 
  14  # INFO: 
  15  # - Screenlets are small owner-drawn applications that can be described as 
  16  #  " the virtual representation of things lying/standing around on your desk". 
  17  #   Sticknotes, clocks, rulers, ... the possibilities are endless. The goal of  
  18  #   the Screenlets is to simplify the creation of fully themeable mini-apps that 
  19  #   each solve basic desktop-work-related needs and generally improve the  
  20  #   usability and eye-candy of the modern Linux-desktop. 
  21  # 
  22  # TODO: (possible improvements, not essential) 
  23  # - still more error-handling and maybe custom exceptions!!! 
  24  # - improve xml-based menu (is implemented, but I'm not happy with it) 
  25  # - switching themes slowly increases the memory usage (possible leak) 
  26  # - maybe attributes for dependancies/requirements (e.g. special  
  27  #   python-libs or certain Screenlets) 
  28  # - 
  29  # 
  30   
  31  import pygtk 
  32  pygtk.require('2.0') 
  33  import gtk 
  34  import cairo, pango 
  35  import gobject 
  36  import rsvg 
  37  import os 
  38  import glob 
  39  import gettext 
  40  import math 
  41   
  42  # import screenlet-submodules 
  43  from options import * 
  44  import services 
  45  import utils 
  46   
  47  # TEST 
  48  import XmlMenu 
  49  # /TEST 
  50   
  51   
  52  #------------------------------------------------------------------------------- 
  53  # CONSTANTS 
  54  #------------------------------------------------------------------------------- 
  55   
  56  # the application name 
  57  APP_NAME = "Screenlets" 
  58   
  59  # the version of the Screenlets-baseclass in use 
  60  VERSION = "0.0.13" 
  61   
  62  # the application copyright 
  63  COPYRIGHT = "(c) RYX (Rico Pfaus) <ryx@ryxperience.com>\nWhise (Helder Fraga) <helder.fraga@hotmail.com>" 
  64   
  65  # the application authors 
  66  AUTHORS = ["RYX (Rico Pfaus) <ryx@ryxperience.com>", "Whise (Helder Fraga)<helder.fraga@hotmail.com>","Sorcerer (Hendrik Kaju)"] 
  67   
  68  # the application comments 
  69  COMMENTS = "Screenlets are small owner-drawn applications (written in Python, a very simple object-oriented programming-language) that can be described as 'the virtual representation of things lying/standing around on your desk'. Sticknotes, clocks, rulers, ... the possibilities are endless." 
  70   
  71  # the application website 
  72  WEBSITE = 'http://www.screenlets.org' 
  73   
  74  # the third party screenlets download site 
  75  THIRD_PARTY_DOWNLOAD = "http://screenlets.org/index.php/Category:UserScreenlets" 
  76   
  77  # install prefix (/usr or /usr/local) DO NOT CHANGE YET, WILL CHANGE WITH v0.1.0 
  78  INSTALL_PREFIX = '/usr' 
  79   
  80  # the global PATH where the screenlets are installed  
  81  PATH = INSTALL_PREFIX + '/share/screenlets' 
  82   
  83  # A list containing all the paths to search for screenlet-"packages" 
  84  # (these paths get searched when a new screenlet-instance shall be 
  85  # loaded through the module-loader function or a screenlet needs data 
  86  # from its personal dir) 
  87  SCREENLETS_PATH = [os.environ['HOME'] + '/.screenlets', PATH] 
  88   
  89  # translation stuff 
  90  gettext.textdomain('screenlets') 
  91  gettext.bindtextdomain('screenlets', '/usr/share/locale') 
  92   
93 -def _(s):
94 return gettext.gettext(s)
95 96 97 #------------------------------------------------------------------------------- 98 # CLASSES 99 #------------------------------------------------------------------------------- 100
101 -class DefaultMenuItem:
102 """A container with constants for the default menuitems""" 103 104 # default menuitem constants (is it right to increase like this?) 105 NONE = 0 106 DELETE = 1 107 THEMES = 2 108 INFO = 4 109 SIZE = 8 110 WINDOW_MENU = 16 111 PROPERTIES = 32 112 # EXPERIMENTAL!! If you use this, the file menu.xml in the 113 # Screenlet's data-dir is used for generating the menu ... 114 XML = 512 115 # the default items 116 STANDARD = 1|2|8|16|32
117 118
119 -class ScreenletTheme (dict):
120 """ScreenletThemes are simple storages that allow loading files 121 as svg-handles within a theme-directory. Each Screenlet can have 122 its own theme-directory. It is up to the Screenlet-developer if he 123 wants to let his Screenlet support themes or not. Themes are 124 turned off by default - if your Screenlet uses Themes, just set the 125 attribute 'theme_name' to the name of the theme's dir you want to use. 126 TODO: remove dict-inheritance""" 127 128 # meta-info (set through theme.conf) 129 __name__ = '' 130 __author__ = '' 131 __version__ = '' 132 __info__ = '' 133 134 # attributes 135 path = "" 136 loaded = False 137 width = 0 138 height = 0 139 option_overrides = {} 140 p_fdesc = None 141 p_layout = None 142 tooltip = None 143 notify = None 144 145
146 - def __init__ (self, path):
147 # set theme-path and load all files in path 148 self.path = path 149 self.svgs = {} 150 self.pngs = {} 151 self.option_overrides = {} 152 self.loaded = self.__load_all() 153 if self.loaded == False: 154 raise Exception(_("Error while loading ScreenletTheme in: ") + path)
155
156 - def __getattr__ (self, name):
157 if name in ("width", "height"): 158 if self.loaded and len(self)>0: 159 size=self[0].get_dimension_data() 160 if name=="width": 161 return size[0] 162 else: 163 return size[1] 164 else: 165 return object.__getattr__(self, name)
166
167 - def apply_option_overrides (self, screenlet):
168 """Apply this theme's overridden options to the given Screenlet.""" 169 # disable the canvas-updates in the screenlet 170 screenlet.disable_updates = True 171 # theme_name needs special care (must be applied last) 172 theme_name = '' 173 # loop through overrides and appply them 174 for name in self.option_overrides: 175 print _("Override: ") + name 176 o = screenlet.get_option_by_name(name) 177 if o and not o.protected: 178 if name == 'theme_name': 179 # import/remember theme-name, but not apply yet 180 theme_name = o.on_import(self.option_overrides[name]) 181 else: 182 # set option in screenlet 183 setattr(screenlet, name, 184 o.on_import(self.option_overrides[name])) 185 else: 186 print _("WARNING: Option '%s' not found or protected.") % name 187 # now apply theme 188 if theme_name != '': 189 screenlet.theme_name = theme_name 190 # re-enable updates and call redraw/reshape 191 screenlet.disable_updates = False 192 screenlet.redraw_canvas() 193 screenlet.update_shape()
194
195 - def check_entry (self, filename):
196 """Checks if a file with filename is loaded in this theme.""" 197 try: 198 if self[filename]: 199 return True 200 except: 201 #raise Exception 202 return False
203
204 - def get_text_width(self, ctx, text, font):
205 """Returns the pixel width of a given text""" 206 ctx.save() 207 ctx.move_to(0,0) 208 p_layout = ctx.create_layout() 209 p_fdesc = pango.FontDescription(font) 210 p_layout.set_font_description(p_fdesc) 211 p_layout.set_text(text) 212 extents, lextents = p_layout.get_pixel_extents() 213 ctx.restore() 214 return extents[2]
215
216 - def get_text_extents(self, ctx, text, font):
217 """Returns the pixel extents of a given text""" 218 ctx.save() 219 ctx.move_to(0,0) 220 p_layout = ctx.create_layout() 221 p_fdesc = pango.FontDescription(font) 222 p_layout.set_font_description(p_fdesc) 223 p_layout.set_text(text) 224 extents, lextents = p_layout.get_pixel_extents() 225 ctx.restore() 226 return extents
227
228 - def draw_text(self, ctx, text, x, y, font, size, width, allignment,ellipsize = pango.ELLIPSIZE_NONE):
229 """Draws text""" 230 ctx.save() 231 ctx.translate(x, y) 232 if self.p_layout == None : 233 234 self.p_layout = ctx.create_layout() 235 else: 236 237 ctx.update_layout(self.p_layout) 238 self.p_fdesc = pango.FontDescription() 239 self.p_fdesc.set_family_static(font) 240 self.p_fdesc.set_size(size * pango.SCALE) 241 self.p_layout.set_font_description(self.p_fdesc) 242 self.p_layout.set_width(width * pango.SCALE) 243 self.p_layout.set_alignment(allignment) 244 self.p_layout.set_ellipsize(ellipsize) 245 self.p_layout.set_markup(text) 246 ctx.show_layout(self.p_layout) 247 ctx.restore()
248 249
250 - def draw_circle(self,ctx,x,y,width,height,fill=True):
251 """Draws a circule""" 252 ctx.save() 253 ctx.translate(x, y) 254 ctx.arc(width/2,height/2,min(height,width)/2,0,2*math.pi) 255 if fill:ctx.fill() 256 else: ctx.stroke() 257 ctx.restore()
258
259 - def draw_line(self,ctx,start_x,start_y,end_x,end_y,line_width = 1,close=False,preserve=False):
260 """Draws a line""" 261 ctx.save() 262 ctx.move_to(start_x, start_y) 263 ctx.set_line_width(line_width) 264 ctx.rel_line_to(end_x, end_y) 265 if close : ctx.close_path() 266 if preserve: ctx.stroke_preserve() 267 else: ctx.stroke() 268 ctx.restore()
269
270 - def draw_rectangle(self,ctx,x,y,width,height,fill=True):
271 """Draws a rectangle""" 272 ctx.save() 273 ctx.translate(x, y) 274 ctx.rectangle (0,0,width,height) 275 if fill:ctx.fill() 276 else: ctx.stroke() 277 ctx.restore()
278
279 - def draw_rounded_rectangle(self,ctx,x,y,rounded_angle,width,height,fill=True):
280 """Draws a rounded rectangle""" 281 ctx.save() 282 ctx.translate(x, y) 283 padding=0 # Padding from the edges of the window 284 rounded=rounded_angle # How round to make the edges 20 is ok 285 w = width 286 h = height 287 288 # Move to top corner 289 ctx.move_to(0+padding+rounded, 0+padding) 290 291 # Top right corner and round the edge 292 ctx.line_to(w-padding-rounded, 0+padding) 293 ctx.arc(w-padding-rounded, 0+padding+rounded, rounded, math.pi/2, 0) 294 295 # Bottom right corner and round the edge 296 ctx.line_to(w-padding, h-padding-rounded) 297 ctx.arc(w-padding-rounded, h-padding-rounded, rounded, 0, math.pi/2) 298 299 # Bottom left corner and round the edge. 300 ctx.line_to(0+padding+rounded, h-padding) 301 ctx.arc(0+padding+rounded, h-padding-rounded, rounded, math.pi+math.pi/2, math.pi) 302 303 # Top left corner and round the edge 304 ctx.line_to(0+padding, 0+padding+rounded) 305 ctx.arc(0+padding+rounded, 0+padding+rounded, rounded, math.pi/2, 0) 306 307 # Fill in the shape. 308 if fill:ctx.fill() 309 else: ctx.stroke() 310 ctx.restore()
311
312 - def get_image_size(self,pix):
313 """Gets a picture width and height""" 314 315 pixbuf = gtk.gdk.pixbuf_new_from_file(pix) 316 iw = pixbuf.get_width() 317 ih = pixbuf.get_height() 318 puxbuf = None 319 return iw,ih
320
321 - def draw_image(self,ctx,x,y, pix):
322 """Draws a picture from specified path""" 323 324 ctx.save() 325 ctx.translate(x, y) 326 pixbuf = gtk.gdk.pixbuf_new_from_file(pix) 327 format = cairo.FORMAT_RGB24 328 if pixbuf.get_has_alpha(): 329 format = cairo.FORMAT_ARGB32 330 331 iw = pixbuf.get_width() 332 ih = pixbuf.get_height() 333 image = cairo.ImageSurface(format, iw, ih) 334 image = ctx.set_source_pixbuf(pixbuf, 0, 0) 335 336 ctx.paint() 337 puxbuf = None 338 image = None 339 ctx.restore()
340 341 342
343 - def draw_scaled_image(self,ctx,x,y, pix, w, h):
344 """Draws a picture from specified path with a certain width and height""" 345 346 ctx.save() 347 ctx.translate(x, y) 348 pixbuf = gtk.gdk.pixbuf_new_from_file(pix).scale_simple(w,h,gtk.gdk.INTERP_HYPER) 349 format = cairo.FORMAT_RGB24 350 if pixbuf.get_has_alpha(): 351 format = cairo.FORMAT_ARGB32 352 353 iw = pixbuf.get_width() 354 ih = pixbuf.get_height() 355 image = cairo.ImageSurface(format, iw, ih) 356 357 matrix = cairo.Matrix(xx=iw/w, yy=ih/h) 358 image = ctx.set_source_pixbuf(pixbuf, 0, 0) 359 if image != None :image.set_matrix(matrix) 360 ctx.paint() 361 puxbuf = None 362 image = None 363 ctx.restore()
364
365 - def show_notification (self,text):
366 """Show notification window at current mouse position.""" 367 if self.notify == None: 368 self.notify = Notify() 369 self.notify.text = text 370 self.notify.show()
371
372 - def hide_notification (self):
373 """hide notification window""" 374 if self.notify != None: 375 self.notify.hide() 376 self.notify = None
377
378 - def show_tooltip (self,text,tooltipx,tooltipy):
379 """Show tooltip window at current mouse position.""" 380 if self.tooltip == None: 381 self.tooltip = Tooltip(300, 400) 382 self.tooltip.text = text 383 self.tooltip.x = tooltipx 384 self.tooltip.y = tooltipy 385 self.tooltip.show()
386
387 - def hide_tooltip (self):
388 """hide tooltip window""" 389 if self.tooltip != None: 390 self.tooltip.hide() 391 self.tooltip = None
392
393 - def has_overrides (self):
394 """Check if this theme contains overrides for options.""" 395 return len(self.option_overrides) > 0
396
397 - def load_conf (self, filename):
398 """Load a config-file from this theme's dir and save vars in list.""" 399 ini = utils.IniReader() 400 if ini.load(filename): 401 if ini.has_section('Theme'): 402 self.__name__ = ini.get_option('name', section='Theme') 403 self.__author__ = ini.get_option('author', section='Theme') 404 self.__version__ = ini.get_option('version', section='Theme') 405 self.__info__ = ini.get_option('info', section='Theme') 406 if ini.has_section('Options'): 407 opts = ini.list_options(section='Options') 408 if opts: 409 for o in opts: 410 self.option_overrides[o[0]] = o[1] 411 print _("theme.conf loaded: ") 412 print _("Name: ") + str(self.__name__) 413 print _("Author: ") +str(self.__author__) 414 print _("Version: ") +str(self.__version__) 415 print _("Info: ") +str(self.__info__) 416 else: 417 print _("Failed to load theme.conf")
418 419
420 - def load_svg (self, filename):
421 """Load an SVG-file into this theme and reference it as ref_name.""" 422 if self.has_key(filename): 423 del self[filename] 424 self[filename] = rsvg.Handle(self.path + "/" + filename) 425 self.svgs[filename[:-4]] = self[filename] 426 if self[filename] != None: 427 # set width/height 428 size=self[filename].get_dimension_data() 429 if size: 430 self.width = size[0] 431 self.height = size[1] 432 return True 433 else: 434 return False
435
436 - def load_png (self, filename):
437 """Load a PNG-file into this theme and reference it as ref_name.""" 438 if self.has_key(filename): 439 del self[filename] 440 self[filename] = cairo.ImageSurface.create_from_png(self.path + 441 "/" + filename) 442 self.pngs[filename[:-4]] = self[filename] 443 if self[filename] != None: 444 return True 445 else: 446 return False
447
448 - def __load_all (self):
449 """Load all files in the theme's path. Currently only loads SVGs and 450 PNGs.""" 451 # clear overrides 452 #self.__option_overrides = {} 453 # read dir 454 dirlst = glob.glob(self.path + '/*') 455 if len(dirlst)==0: 456 return False 457 plen = len(self.path) + 1 458 for file in dirlst: 459 fname = file[plen:] 460 if fname.endswith('.svg'): 461 # svg file 462 if self.load_svg(fname) == False: 463 return False 464 elif fname.endswith('.png'): 465 # svg file 466 if self.load_png(fname) == False: 467 return False 468 elif fname == "theme.conf": 469 print _("theme.conf found! Loading option-overrides.") 470 # theme.conf 471 if self.load_conf(file) == False: 472 return False 473 return True
474
475 - def reload (self):
476 """Re-Load all files in the theme's path.""" 477 self.free() 478 self.__load_all()
479 480 # TODO: fix function, rsvg handles are not freed properly
481 - def free (self):
482 """Deletes the Theme's contents and frees all rsvg-handles. 483 TODO: freeing rsvg-handles does NOT work for some reason""" 484 self.option_overrides.clear() 485 for filename in self: 486 #self[filename].close() 487 del filename 488 self.clear()
489 490 # TEST: render-function 491 # should be used like "theme.render(context, 'notes-bg')" and then use 492 # either an svg or png image
493 - def render (self, ctx, name):
494 """Render an image from within this theme to the given context. This 495 function can EITHER use png OR svg images, so it is possible to 496 create themes using both image-formats when a Screenlet uses this 497 function for drawing its images. The image name has to be defined 498 without the extension and the function will automatically select 499 the available one (SVG is prefered over PNG).""" 500 """if self.has_key(name + '.svg'): 501 self[name + '.svg'].render_cairo(ctx) 502 else: 503 ctx.set_source_surface(self[name + '.png'], 0, 0) 504 ctx.paint()""" 505 try: 506 #self[name + '.svg'].render_cairo(ctx) 507 self.svgs[name].render_cairo(ctx) 508 except: 509 #ctx.set_source_surface(self[name + '.png'], 0, 0) 510 ctx.set_source_surface(self.pngs[name], 0, 0) 511 ctx.paint()
512 513 #else: 514 # ctx.set_source_pixbuf(self[name + '.png'], 0, 0) 515 # ctx.paint() 516 517
518 -class Screenlet (gobject.GObject, EditableOptions):
519 """A Screenlet is a (i.e. contains a) shaped gtk-window that is 520 fully invisible by default. Subclasses of Screenlet can render 521 their owner-drawn graphics on fully transparent background.""" 522 523 # default meta-info for Screenlets 524 __name__ = _('No name set for this Screenlet') 525 __version__ = '0.0' 526 __author__ = _('No author defined for this Screenlet') 527 __desc__ = _('No info set for this Screenlet') 528 __requires__ = [] # still unused 529 #__target_version__ = '0.0.0' 530 #__backend_version__ = '0.0.1' 531 532 # attributes (TODO: remove them here and add them to the constructor, 533 # because they only should exist per instance) 534 id = '' # id-attribute for handling instances 535 window = None # the gtk.Window behind the scenes 536 theme = None # the assigned ScreenletTheme 537 uses_theme = True # flag indicating whether Screenlet uses themes 538 draw_buttons = True 539 show_buttons = True 540 menu = None # the right-click gtk.Menu 541 is_dragged = False # TODO: make this work 542 quit_on_close = True # if True, closing this instance quits gtk 543 saving_enabled = True # if False, saving is disabled 544 dragging_over = False # true if something is dragged over 545 disable_updates = False # to temporarily avoid refresh/reshape 546 p_context = None # PangoContext 547 p_layout = None # PangoLayout 548 549 # default editable options, available for all Screenlets 550 x = 0 551 y = 0 552 mousex = 0 553 mousey = 0 554 width = 100 555 height = 100 556 scale = 1.0 557 opacity = 1.0 558 theme_name = "" 559 is_sticky = False 560 is_widget = False 561 keep_above = True 562 keep_below = False 563 skip_pager = True 564 first_run = False 565 skip_taskbar = True 566 lock_position = False 567 allow_option_override = True # if False, overrides are ignored 568 ask_on_option_override = True # if True, overrides need confirmation 569 has_started = False 570 has_focus = False 571 # internals (deprecated? we still don't get the end of a begin_move_drag) 572 __lastx = 0 573 __lasty = 0 574 575 # some menuitems (needed for checking/unchecking) 576 # DEPRECATED: remove - don't really work anyway ... (or fix the menu?) 577 __mi_keep_above = None 578 __mi_keep_below = None 579 __mi_widget = None 580 __mi_sticky = None 581 __mi_lock = None 582 # for custom signals (which aren't acutally used ... yet) 583 __gsignals__ = dict(screenlet_removed=(gobject.SIGNAL_RUN_FIRST, 584 gobject.TYPE_NONE, (gobject.TYPE_OBJECT,))) 585
586 - def __init__ (self, id='', width=100, height=100, parent_window=None, 587 show_window=True, is_widget=False, is_sticky=False, 588 uses_theme=True, draw_buttons=True,path=os.getcwd(), drag_drop=False, session=None, 589 enable_saving=True, service_class=services.ScreenletService, 590 uses_pango=False):
591 """Constructor - should only be subclassed""" 592 # call gobject and EditableOptions superclasses 593 super(Screenlet, self).__init__() 594 EditableOptions.__init__(self) 595 # init properties 596 self.id = id 597 self.session = session 598 self.service = None 599 # if we have an id and a service-class, register our service 600 if self.id and service_class: 601 self.register_service(service_class) 602 # notify service about adding this instance 603 self.service.instance_added(self.id) 604 self.width = width 605 self.height = height 606 self.is_dragged = False 607 self.__path__ = path 608 self.saving_enabled = enable_saving # used by session 609 # set some attributes without calling __setattr__ 610 self.__dict__['theme_name'] = "" 611 self.__dict__['is_widget'] = is_widget 612 self.__dict__['is_sticky'] = is_sticky 613 self.__dict__['x'] = 0 614 self.__dict__['y'] = 0 615 # TEST: set scale relative to theme size (NOT WORKING) 616 #self.__dict__['scale'] = width/100.0 617 # /TEST 618 # shape bitmap 619 self.__shape_bitmap = None 620 self.__shape_bitmap_width = 0 621 self.__shape_bitmap_height = 0 622 # "editable" options, first create a group 623 self.add_options_group('Screenlet', 624 _('The basic settings for this Screenlet-instance.')) 625 # if this Screenlet uses themes, add theme-specific options 626 # (NOTE: this option became hidden with 0.0.9 and doesn't use 627 # get_available_themes anymore for showing the choices) 628 if draw_buttons: self.draw_buttons = True 629 else: self.draw_buttons = False 630 if uses_theme: 631 self.uses_theme = True 632 self.add_option(StringOption('Screenlet', 'theme_name', 633 'default', '', '', hidden=True)) 634 # create/add options 635 self.add_option(IntOption('Screenlet', 'x', 636 0, _('X-Position'), _('The X-position of this Screenlet .os.path.exists(d)..'), 637 min=0, max=gtk.gdk.screen_width())) 638 self.add_option(IntOption('Screenlet', 'y', 639 0, _('Y-Position'), _('The Y-position of this Screenlet ...'), 640 min=0, max=gtk.gdk.screen_height())) 641 self.add_option(IntOption('Screenlet', 'width', 642 width, _('Width'), _('The width of this Screenlet ...'), 643 min=16, max=1000, hidden=True)) 644 self.add_option(IntOption('Screenlet', 'height', 645 height, _('Height'), _('The height of this Screenlet ...'), 646 min=16, max=1000, hidden=True)) 647 self.add_option(FloatOption('Screenlet', 'scale', 648 self.scale, _('Scale'), _('The scale-factor of this Screenlet ...'), 649 min=0.1, max=10.0, digits=2, increment=0.1)) 650 self.add_option(FloatOption('Screenlet', 'opacity', 651 self.opacity, _('Opacity'), _('The opacity of the Screenlet window ...'), 652 min=0.1, max=1.0, digits=2, increment=0.1)) 653 self.add_option(BoolOption('Screenlet', 'is_sticky', 654 is_sticky, _('Stick to Desktop'), 655 _('Show this Screenlet on all workspaces ...'))) 656 self.add_option(BoolOption('Screenlet', 'is_widget', 657 is_widget, _('Treat as Widget'), 658 _('Treat this Screenlet as a "Widget" ...'))) 659 self.add_option(BoolOption('Screenlet', 'lock_position', 660 self.lock_position, _('Lock position'), 661 _('Stop the screenlet from being moved...'))) 662 self.add_option(BoolOption('Screenlet', 'keep_above', 663 self.keep_above, _('Keep above'), 664 _('Keep this Screenlet above other windows ...'))) 665 self.add_option(BoolOption('Screenlet', 'keep_below', 666 self.keep_below, _('Keep below'), 667 _('Keep this Screenlet below other windows ...'))) 668 self.add_option(BoolOption('Screenlet', 'draw_buttons', 669 self.draw_buttons, _('Draw button controls'), 670 _('Draw buttons in top right corner'))) 671 self.add_option(BoolOption('Screenlet', 'skip_pager', 672 self.skip_pager, _('Skip Pager'), 673 _('Set this Screenlet to show/hide in pagers ...'))) 674 self.add_option(BoolOption('Screenlet', 'skip_taskbar', 675 self.skip_pager, _('Skip Taskbar'), 676 _('Set this Screenlet to show/hide in taskbars ...'))) 677 if uses_theme: 678 self.add_option(BoolOption('Screenlet', 'allow_option_override', 679 self.allow_option_override, _('Allow overriding Options'), 680 _('Allow themes to override options in this screenlet ...'))) 681 self.add_option(BoolOption('Screenlet', 'ask_on_option_override', 682 self.ask_on_option_override, _('Ask on Override'), 683 _('Show a confirmation-dialog when a theme wants to override ')+\ 684 _('the current options of this Screenlet ...'))) 685 # disable width/height 686 self.disable_option('width') 687 self.disable_option('height') 688 # create window 689 self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) 690 if parent_window: 691 self.window.set_parent_window(parent_window) 692 self.window.set_transient_for(parent_window) 693 self.window.set_destroy_with_parent(True) 694 self.window.resize(width, height) 695 self.window.set_decorated(False) 696 self.window.set_app_paintable(True) 697 # create pango layout, if active 698 if uses_pango: 699 self.p_context = self.window.get_pango_context() 700 if self.p_context: 701 self.p_layout = pango.Layout(self.p_context) 702 self.p_layout.set_font_description(\ 703 pango.FontDescription("Sans 12")) 704 # set type hint 705 self.window.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_TOOLBAR) 706 self.window.set_keep_above(True) 707 self.window.set_skip_taskbar_hint(True) 708 self.window.set_skip_pager_hint(True) 709 if is_sticky: 710 self.window.stick() 711 self.alpha_screen_changed(self.window) 712 self.update_shape() 713 #self.window.set_events(gtk.gdk.BUTTON_PRESS_MASK) 714 self.window.set_events(gtk.gdk.ALL_EVENTS_MASK) 715 self.window.connect("composited-changed", self.composite_changed) 716 self.window.connect("delete_event", self.delete_event) 717 self.window.connect("destroy", self.destroy) 718 self.window.connect("expose_event", self.expose) 719 self.window.connect("button-press-event", self.button_press) 720 self.window.connect("button-release-event", self.button_release) 721 self.window.connect("configure-event", self.configure_event) 722 self.window.connect("screen-changed", self.alpha_screen_changed) 723 self.window.connect("realize", self.realize_event) 724 self.window.connect("enter-notify-event", self.enter_notify_event) 725 self.window.connect("leave-notify-event", self.leave_notify_event) 726 self.window.connect("focus-in-event", self.focus_in_event) 727 self.window.connect("focus-out-event", self.focus_out_event) 728 self.window.connect("scroll-event", self.scroll_event) 729 self.window.connect("motion-notify-event",self.motion_notify_event) 730 # add key-handlers (TODO: use keyword-attrib to activate?) 731 self.window.connect("key-press-event", self.key_press) 732 # drag/drop support (NOTE: still experimental and incomplete) 733 if drag_drop: 734 self.window.drag_dest_set(gtk.DEST_DEFAULT_MOTION | 735 gtk.DEST_DEFAULT_DROP, #gtk.DEST_DEFAULT_ALL, 736 [("text/plain", 0, 0), 737 ("image", 0, 1), 738 ("text/uri-list", 0, 2)], 739 gtk.gdk.ACTION_COPY) 740 self.window.connect("drag_data_received", self.drag_data_received) 741 self.window.connect("drag-begin", self.drag_begin) 742 self.window.connect("drag-end", self.drag_end) 743 self.window.connect("drag-motion", self.drag_motion) 744 self.window.connect("drag-leave", self.drag_leave) 745 # create menu 746 self.menu = gtk.Menu() 747 # show window so it can realize , but hiding it so we can show it only when atributes have been set , this fixes some placement errors arround the screen egde 748 749 750 if show_window: 751 self.window.show() 752 print os.environ['HOME'] + '/.config/Screenlets/' + self.__name__[:-9] + '/default/'+ self.id 753 if not os.path.exists(os.environ['HOME'] + '/.config/Screenlets/' + self.__name__[:-9] + '/default/'+ self.id + '.ini'): 754 self.first_run = True 755 self.window.hide()
756
757 - def __setattr__ (self, name, value):
758 # set the value in GObject (ESSENTIAL!!!!) 759 self.on_before_set_atribute(name, value) 760 gobject.GObject.__setattr__(self, name, value) 761 # And do other actions 762 if name=="x" or name=="y": 763 if self.has_started: 764 self.window.move(self.x, self.y) 765 elif name == 'opacity': 766 self.window.set_opacity(value) 767 elif name == 'scale': 768 self.window.resize(int(self.width * self.scale), 769 int(self.height * self.scale)) 770 # TODO: call on_resize-handler here !!!! 771 self.on_scale() 772 self.redraw_canvas() 773 self.update_shape() 774 775 776 elif name == "theme_name": 777 #self.__dict__ ['theme_name'] = value 778 print _("LOAD NEW THEME: ") + value 779 print _("FOUND: ") + str(self.find_theme(value)) 780 #self.load_theme(self.get_theme_dir() + value) 781 # load theme 782 path = self.find_theme(value) 783 if path: 784 self.load_theme(path) 785 #self.load_first_theme(value) 786 self.redraw_canvas() 787 self.update_shape() 788 elif name in ("width", "height"): 789 #self.__dict__ [name] = value 790 if self.window: 791 self.window.resize(int(self.width*self.scale), int(self.height*self.scale)) 792 #self.redraw_canvas() 793 self.update_shape() 794 elif name == "is_widget": 795 if self.has_started: 796 self.set_is_widget(value) 797 elif name == "is_sticky": 798 if value == True: 799 self.window.stick() 800 else: 801 self.window.unstick() 802 #if self.__mi_sticky: 803 # self.__mi_sticky.set_active(value) 804 elif name == "keep_above": 805 if self.has_started == True: 806 self.window.set_keep_above(bool(value)) 807 #self.__mi_keep_above.set_active(value) 808 elif name == "keep_below": 809 if self.has_started == True: 810 self.window.set_keep_below(bool(value)) 811 #self.__mi_keep_below.set_active(value) 812 elif name == "skip_pager": 813 if self.window.window: 814 self.window.window.set_skip_pager_hint(bool(value)) 815 elif name == "skip_taskbar": 816 if self.window.window: 817 self.window.window.set_skip_taskbar_hint(bool(value)) 818 # NOTE: This is the new recommended way of storing options in real-time 819 # (we access the backend through the session here) 820 if self.saving_enabled: 821 o = self.get_option_by_name(name) 822 if o != None: 823 self.session.backend.save_option(self.id, o.name, 824 o.on_export(value)) 825 self.on_after_set_atribute(name, value)
826 # /TEST 827 828 #----------------------------------------------------------------------- 829 # Screenlet's public functions 830 #----------------------------------------------------------------------- 831 832 # NOTE: This function is deprecated and will get removed. The 833 # XML-based menus should be preferred
835 """Appends the default menu-items to self.menu. You can add on OR'ed 836 flag with DefaultMenuItems you want to add.""" 837 if not self.has_started: print 'WARNING - add_default_menuitems and add_menuitems should be set in on_init ,menu values will be displayed incorrectly' 838 # children already exist? add separator 839 if len(self.menu.get_children()) > 0: 840 self.add_menuitem("", "-") 841 # create menu (or submenu?) 842 #if flags & DefaultMenuItem.IS_SUBMENU : 843 # menu = gtk.Menu() 844 #else: 845 menu = self.menu 846 # EXPERIMENTAL: 847 if flags & DefaultMenuItem.XML: 848 # create XML-menu from screenletpath/menu.xml 849 xfile = self.get_screenlet_dir() + "/menu.xml" 850 xmlmenu = XmlMenu.create_menu_from_file(xfile, 851 self.menuitem_callback) 852 if xmlmenu: 853 self.menu = xmlmenu 854 pass 855 # add size-selection 856 if flags & DefaultMenuItem.SIZE: 857 size_item = gtk.MenuItem(_("Size")) 858 size_item.show() 859 size_menu = gtk.Menu() 860 menu.append(size_item) 861 size_item.set_submenu(size_menu) 862 #for i in xrange(10): 863 for i in (0.2,0.3,0.4, 0.5,0.6, 0.7,0.8,0.9, 1.0, 1.5, 2.0, 3.0, 4.0, 5.0, 7.5, 10): 864 s = str(int(i * 100)) 865 item = gtk.MenuItem(s + " %") 866 item.connect("activate", self.menuitem_callback, 867 "scale:"+str(i)) 868 item.show() 869 size_menu.append(item) 870 # create theme-selection menu 871 if flags & DefaultMenuItem.THEMES: 872 themes_item = gtk.MenuItem(_("Theme")) 873 themes_item.show() 874 themes_menu = gtk.Menu() 875 menu.append(themes_item) 876 themes_item.set_submenu(themes_menu) 877 # create theme-list from theme-directory 878 lst = self.get_available_themes() 879 for tname in lst: 880 item = gtk.MenuItem(tname) 881 item.connect("activate", self.menuitem_callback, 882 "theme:"+tname) 883 item.show() 884 themes_menu.append(item) 885 # add window-options menu 886 if flags & DefaultMenuItem.WINDOW_MENU: 887 winmenu_item = gtk.MenuItem(_("Window")) 888 winmenu_item.show() 889 winmenu_menu = gtk.Menu() 890 menu.append(winmenu_item) 891 winmenu_item.set_submenu(winmenu_menu) 892 # add "lock"-menuitem 893 self.__mi_lock = item = gtk.CheckMenuItem(_("Lock")) 894 item.set_active(self.lock_position) 895 item.connect("activate", self.menuitem_callback, 896 "option:lock") 897 item.show() 898 winmenu_menu.append(item) 899 # add "Sticky"-menuitem 900 self.__mi_sticky = item = gtk.CheckMenuItem(_("Sticky")) 901 item.set_active(self.is_sticky) 902 item.connect("activate", self.menuitem_callback, 903 "option:sticky") 904 item.show() 905 winmenu_menu.append(item) 906 # add "Widget"-menuitem 907 self.__mi_widget = item = gtk.CheckMenuItem(_("Widget")) 908 item.set_active(self.is_widget) 909 item.connect("activate", self.menuitem_callback, 910 "option:widget") 911 item.show() 912 winmenu_menu.append(item) 913 # add "Keep above"-menuitem 914 self.__mi_keep_above = item = gtk.CheckMenuItem(_("Keep above")) 915 item.set_active(self.keep_above) 916 item.connect("activate", self.menuitem_callback, 917 "option:keep_above") 918 item.show() 919 winmenu_menu.append(item) 920 # add "Keep Below"-menuitem 921 self.__mi_keep_below = item = gtk.CheckMenuItem(_("Keep below")) 922 item.set_active(self.keep_below) 923 item.connect("activate", self.menuitem_callback, 924 "option:keep_below") 925 item.show() 926 winmenu_menu.append(item) 927 # add Settings-item 928 if flags & DefaultMenuItem.PROPERTIES: 929 self.add_menuitem("", "-") 930 self.add_menuitem("options", _("Properties...")) 931 # add info-item 932 if flags & DefaultMenuItem.INFO: 933 self.add_menuitem("", "-") 934 self.add_menuitem("info", _("Info...")) 935 # add delete item 936 if flags & DefaultMenuItem.DELETE: 937 self.add_menuitem("", "-") 938 self.add_menuitem("delete", _("Delete Screenlet ...")) 939 # add Quit-item 940 self.add_menuitem("", "-") 941 self.add_menuitem("quit_instance", _("Quit this %s ...") % self.get_short_name()) 942 # add Quit-item 943 self.add_menuitem("", "-") 944 self.add_menuitem("quit", _("Quit all %ss ...") % self.get_short_name())
945
946 - def add_menuitem (self, id, label, callback=None):
947 """Simple way to add menuitems to the right-click menu.""" 948 if not self.has_started: print 'WARNING - add_default_menuitems and add_menuitems should be set in on_init ,menu values will be displayed incorrectly' 949 if callback == None: 950 callback = self.menuitem_callback 951 if label == "-": 952 menu_item = gtk.SeparatorMenuItem() 953 else: 954 menu_item = gtk.MenuItem(label) 955 menu_item.connect("activate", callback, id) 956 self.menu.append(menu_item) 957 menu_item.show() 958 return menu_item
959
960 - def create_buttons(self):
961 962 ctx = self.window.window.cairo_create() 963 ctx.save() 964 theme1 = gtk.icon_theme_get_default() 965 #ctx.set_source_rgba(0.5,0.5,0.5,0.6) 966 #self.theme.draw_rounded_rectangle(ctx,(self.width*self.scale)-36,0,5,36,16) 967 close = theme1.load_icon ("gtk-close", 16, 0) 968 prop = theme1.load_icon ("gtk-properties", 16, 0) 969 #zoom1 = theme1.load_icon ("gtk-zoom-in", 16, 0) 970 #zoom2 = theme1.load_icon ("gtk-zoom-out", 16, 0) 971 #close = gtk.image_new_from_stock(gtk.STOCK_CLOSE, 972 # 16) 973 ctx.translate((self.width*self.scale)-16,0) 974 ctx.set_source_pixbuf(close, 0, 0) 975 ctx.paint() 976 ctx.restore() 977 ctx.save() 978 ctx.translate((self.width*self.scale)-32,0) 979 ctx.set_source_pixbuf(prop, 0, 0) 980 ctx.paint() 981 ctx.restore()
982
983 - def clear_cairo_context (self, ctx):
984 """Fills the given cairo.Context with fully transparent white.""" 985 ctx.save() 986 ctx.set_source_rgba(1, 1, 1, 0) 987 ctx.set_operator (cairo.OPERATOR_SOURCE) 988 ctx.paint() 989 ctx.restore()
990
991 - def close (self):
992 """Close this Screenlet 993 TODO: send close-notify instead of destroying window?""" 994 #self.save_settings() 995 self.window.unmap() 996 self.window.destroy()
997 #self.window.event(gtk.gdk.Event(gtk.gdk.DELETE)) 998
999 - def create_drag_icon (self):
1000 """Create drag-icon and -mask for drag-operation. Returns a 2-tuple 1001 with the icon and the mask. To supply your own icon you can use the 1002 on_create_drag_icon-handler and return the icon/mask as 2-tuple.""" 1003 w = self.width 1004 h = self.height 1005 icon, mask = self.on_create_drag_icon() 1006 if icon == None: 1007 # create icon 1008 icon = gtk.gdk.Pixmap(self.window.window, w, h) 1009 ctx = icon.cairo_create() 1010 self.clear_cairo_context(ctx) 1011 self.on_draw(ctx) 1012 if mask == None: 1013 # create mask 1014 mask = gtk.gdk.Pixmap(self.window.window, w, h) 1015 ctx = mask.cairo_create() 1016 self.clear_cairo_context(ctx) 1017 self.on_draw_shape(ctx) 1018 return (icon, mask)
1019
1020 - def enable_saving (self, enabled=True):
1021 """Enable/Disable realtime-saving of options.""" 1022 self.saving_enabled = enabled
1023
1024 - def find_theme (self, name):
1025 """Find the first occurence of a theme and return its global path.""" 1026 sn = self.get_short_name() 1027 for p in SCREENLETS_PATH: 1028 fpath = p + '/' + sn + '/themes/' + name 1029 if os.path.isdir(fpath): 1030 return fpath 1031 return None
1032
1033 - def get_short_name (self):
1034 """Return the short name of this screenlet. This returns the classname 1035 of the screenlet without trailing "Screenlet". Please always use 1036 this function if you want to retrieve the short name of a Screenlet.""" 1037 return self.__class__.__name__[:-9]
1038
1039 - def get_screenlet_dir (self):
1040 """@DEPRECATED: Return the name of this screenlet's personal directory.""" 1041 p = utils.find_first_screenlet_path(self.get_short_name()) 1042 if p: 1043 return p 1044 else: 1045 if self.__path__ != '': 1046 return self.__path__ 1047 else: 1048 return os.getcwd()
1049
1050 - def get_theme_dir (self):
1051 """@DEPRECATED: Return the name of this screenlet's personal theme-dir. 1052 (Only returns the dir under the screenlet's location""" 1053 return self.get_screenlet_dir() + "/themes/"
1054
1055 - def get_available_themes (self):
1056 """Returns a list with the names of all available themes in this 1057 Screenlet's theme-directory.""" 1058 lst = [] 1059 for p in SCREENLETS_PATH: 1060 d = p + '/' + self.get_short_name() + '/themes/' 1061 if os.path.isdir(d): 1062 #dirname = self.get_theme_dir() 1063 dirlst = glob.glob(d + '*') 1064 dirlst.sort() 1065 tdlen = len(d) 1066 for fname in dirlst: 1067 dname = fname[tdlen:] 1068 # TODO: check if it's a dir 1069 lst.append(dname) 1070 return lst
1071
1072 - def finish_loading(self):
1073 """Called when screenlet finishes loading""" 1074 1075 1076 self.window.present() 1077 1078 1079 # the keep above and keep bellow must be reset after the window is shown this is absolutly necessary 1080 self.window.hide() 1081 self.window.move(self.x, self.y) 1082 self.window.present() 1083 self.has_started = True 1084 self.keep_above= self.keep_above 1085 self.keep_below= self.keep_below 1086 self.window.set_keep_above(self.keep_above) 1087 self.window.set_keep_below(self.keep_below) 1088 self.on_init() 1089 if self.is_widget: 1090 self.set_is_widget(True) 1091 self.has_focus = False 1092 ini = utils.IniReader() 1093 if ini.load (os.environ['HOME'] + '/.screenlets' + '/config.ini') and self.first_run: 1094 1095 if ini.get_option('Lock', section='Options') == 'True': 1096 self.lock_position = True 1097 1098 if ini.get_option('Sticky', section='Options') == 'True': 1099 self.is_sticky = True 1100 1101 if ini.get_option('Widget', section='Options') == 'True': 1102 self.is_widget = True 1103 1104 if ini.get_option('Keep_above', section='Options') == 'True': 1105 self.keep_above = True 1106 1107 if ini.get_option('Keep_below', section='Options') == 'True': 1108 self.keep_below = True
1109
1110 - def hide (self):
1111 """Hides this Screenlet's underlying gtk.Window""" 1112 self.window.hide() 1113 self.on_hide()
1114 1115 # EXPERIMENTAL: 1116 # NOTE: load_theme does NOT call redraw_canvas and update_shape!!!!! 1117 # To do all in one, set attribute self.theme_name instead
1118 - def load_theme (self, path):
1119 """Load a theme for this Screenlet from the given path. NOTE: 1120 load_theme does NOT call redraw_canvas and update_shape!!!!! To do all 1121 in one call, set the attribute self.theme_name instead.""" 1122 if self.theme: 1123 self.theme.free() 1124 del self.theme 1125 self.theme = ScreenletTheme(path) 1126 # check for errors 1127 if self.theme.loaded == False: 1128 print _("Error while loading theme: ") + path 1129 self.theme = None 1130 else: 1131 # call user-defined handler 1132 self.on_load_theme() 1133 # if override options is allowed, apply them 1134 if self.allow_option_override: 1135 if self.theme.has_overrides(): 1136 if self.ask_on_option_override==True and \ 1137 show_question(self, 1138 _('This theme wants to override your settings for this Screenlet. Do you want to allow that?')) == False: 1139 return 1140 self.theme.apply_option_overrides(self)
1141 # /EXPERIMENTAL 1142
1143 - def main (self):
1144 """If the Screenlet runs as stand-alone app, starts gtk.main()""" 1145 gtk.main()
1146
1147 - def register_service (self, service_classobj):
1148 """Register or create the given ScreenletService-(sub)class as the new 1149 service for this Screenlet. If self is not the first instance in the 1150 current session, the service from the first instance will be used 1151 instead and no new service is created.""" 1152 if self.session: 1153 if len(self.session.instances) == 0: 1154 # if it is the basic service, add name to call 1155 if service_classobj==services.ScreenletService: 1156 self.service = service_classobj(self, self.get_short_name()) 1157 else: 1158 # else only pass this screenlet 1159 self.service = service_classobj(self) 1160 else: 1161 self.service = self.session.instances[0].service 1162 # TODO: throw exception?? 1163 return True 1164 return False
1165
1166 - def set_is_widget (self, value):
1167 """Set this window to be treated as a Widget (only supported by 1168 compiz using the widget-plugin yet)""" 1169 if value==True: 1170 # set window type to utility 1171 #self.window.window.set_type_hint( 1172 # gtk.gdk.WINDOW_TYPE_HINT_UTILITY) 1173 # set _compiz_widget-property on window 1174 self.window.window.property_change("_COMPIZ_WIDGET", 1175 gtk.gdk.SELECTION_TYPE_WINDOW, 1176 32, gtk.gdk.PROP_MODE_REPLACE, (True,)) 1177 else: 1178 # set window type to normal 1179 #self.window.window.set_type_hint( 1180 # gtk.gdk.WINDOW_TYPE_HINT_NORMAL) 1181 # set _compiz_widget-property 1182 self.window.window.property_delete("_COMPIZ_WIDGET") 1183 # notify handler 1184 self.on_switch_widget_state(value)
1185
1186 - def show (self):
1187 """Show this Screenlet's underlying gtk.Window""" 1188 self.window.show() 1189 self.window.move(self.x, self.y) 1190 self.on_show()
1191
1192 - def show_settings_dialog (self):
1193 """Show the EditableSettingsDialog for this Screenlet.""" 1194 se = OptionsDialog(490, 450) 1195 img = gtk.Image() 1196 try: 1197 d = self.get_screenlet_dir() 1198 if os.path.isfile(d + '/icon.svg'): 1199 icn = gtk.gdk.pixbuf_new_from_file(d + '/icon.svg') 1200 elif os.path.isfile(d + '/icon.png'): 1201 icn = gtk.gdk.pixbuf_new_from_file(d + '/icon.png') 1202 img.set_from_pixbuf(icn) 1203 except: 1204 img.set_from_stock(gtk.STOCK_PROPERTIES, 5) 1205 se.set_title(self.__name__) 1206 se.set_info(self.__name__, self.__desc__, '(c) ' + self.__author__, 1207 version='v' + self.__version__, icon=img) 1208 se.show_options_for_object(self) 1209 resp = se.run() 1210 if resp == gtk.RESPONSE_REJECT: # TODO!!!!! 1211 se.reset_to_defaults() 1212 else: 1213 self.update_shape() 1214 se.destroy()
1215
1216 - def redraw_canvas (self):
1217 """Redraw the entire Screenlet's window area. 1218 TODO: store window alloaction in class and change when size changes.""" 1219 # if updates are disabled, just exit 1220 if self.disable_updates: 1221 return 1222 if self.window: 1223 x, y, w, h = self.window.get_allocation() 1224 rect = gtk.gdk.Rectangle(x, y, w, h) 1225 if self.window.window: 1226 self.window.window.invalidate_rect(rect, True) 1227 self.window.window.process_updates(True) 1228 if self.has_focus and self.draw_buttons and self.show_buttons: 1229 self.create_buttons()
1230 1231
1232 - def redraw_canvas_area (self, x, y, width, height):
1233 """Redraw the given Rectangle (x, y, width, height) within the 1234 current Screenlet's window.""" 1235 # if updates are disabled, just exit 1236 if self.disable_updates: 1237 return 1238 if self.window: 1239 rect = gtk.gdk.Rectangle(x, y, width, height) 1240 if self.window.window: 1241 self.window.window.invalidate_rect(rect, True) 1242 self.window.window.process_updates(True)
1243
1244 - def remove_shape(self):
1245 """Removed shaped window , in case the nom composited shape has been set""" 1246 if self.window.window: 1247 self.window.window.shape_combine_mask(None,0,0)
1248
1249 - def update_shape (self):
1250 """Update window shape (only call this when shape has changed 1251 because it is very ressource intense if ran too often).""" 1252 # if updates are disabled, just exit 1253 if self.disable_updates: 1254 return 1255 print _("UPDATING SHAPE") 1256 # TODO: 1257 #if not self.window.is_composited(): 1258 # self.update_shape_non_composited() 1259 # calculate new width/height of shape bitmap 1260 w = int(self.width * self.scale) 1261 h = int(self.height * self.scale) 1262 # if 0 set it to 100 to avoid crashes and stay interactive 1263 if w==0: w = 100 1264 if h==0: h = 100 1265 # if size changed, recreate shape bitmap 1266 if w != self.__shape_bitmap_width or h != self.__shape_bitmap_height: 1267 data = ''.zfill(w*h) 1268 self.__shape_bitmap = gtk.gdk.bitmap_create_from_data(None, data, 1269 w, h) 1270 self.__shape_bitmap_width = w 1271 self.__shape_bitmap_height = h 1272 # create context and draw shape 1273 ctx = self.__shape_bitmap.cairo_create() 1274 self.clear_cairo_context(ctx) #TEST 1275 if self.has_focus and self.draw_buttons and self.show_buttons: 1276 ctx.save() 1277 theme1 = gtk.icon_theme_get_default() 1278 #ctx.set_source_rgba(0.5,0.5,0.5,0.6) 1279 #self.theme.draw_rounded_rectangle(ctx,(self.width*self.scale)-36,0,5,36,16) 1280 close = theme1.load_icon ("gtk-close", 16, 0) 1281 prop = theme1.load_icon ("gtk-properties", 16, 0) 1282 #zoom1 = theme1.load_icon ("gtk-zoom-in", 16, 0) 1283 #zoom2 = theme1.load_icon ("gtk-zoom-out", 16, 0) 1284 #close = gtk.image_new_from_stock(gtk.STOCK_CLOSE, 1285 # 16) 1286 ctx.translate((self.width*self.scale)-16,0) 1287 ctx.set_source_pixbuf(close, 0, 0) 1288 ctx.paint() 1289 ctx.restore() 1290 ctx.save() 1291 ctx.translate((self.width*self.scale)-32,0) 1292 ctx.set_source_pixbuf(prop, 0, 0) 1293 ctx.paint() 1294 ctx.restore() 1295 # shape the window acording if the window is composited or not 1296 1297 if self.window.is_composited(): 1298 1299 self.on_draw_shape(ctx) 1300 # and cut window with mask 1301 self.window.input_shape_combine_mask(self.__shape_bitmap, 0, 0) 1302 else: 1303 try: self.on_draw(ctx) #Works better then the shape method on non composited windows 1304 except: self.on_draw_shape(ctx) # if error on on_draw use standard shape method 1305 # and cut window with mask 1306 self.window.shape_combine_mask(self.__shape_bitmap,0,0)
1307
1308 - def update_shape_non_composited (self):
1309 """TEST: This function is intended to shape the window whenever no 1310 composited environment can be found. (NOT WORKING YET!!!!)""" 1311 #pixbuf = gtk.gdk.GdkPixbuf.new_from_file) 1312 # calculate new width/height of shape bitmap 1313 w = int(self.width * self.scale) 1314 h = int(self.height * self.scale) 1315 # if 0 set it to 100 to avoid crashes and stay interactive 1316 if w==0: w = 100 1317 if h==0: h = 100 1318 # if size changed, recreate shape bitmap 1319 if w != self.__shape_bitmap_width or h != self.__shape_bitmap_height: 1320 data = ''.zfill(w*h) 1321 self.__shape_bitmap = gtk.gdk.pixbuf_new_from_data(data, 1322 gtk.gdk.COLORSPACE_RGB, True, 1, w, h, w) 1323 self.__shape_bitmap_width = w 1324 self.__shape_bitmap_height = h 1325 # and render window contents to it 1326 # TOOD!! 1327 if self.__shape_bitmap: 1328 # create new mask 1329 (pixmap,mask) = self.__shape_bitmap.render_pixmap_and_mask(255) 1330 # apply new mask to window 1331 self.window.shape_combine_mask(mask)
1332 1333 # ---------------------------------------------------------------------- 1334 # Screenlet's event-handler dummies 1335 # ---------------------------------------------------------------------- 1336
1337 - def on_delete (self):
1338 """Called when the Screenlet gets deleted. Return True to cancel. 1339 TODO: sometimes not properly called""" 1340 return not show_question(self, _("To quit all %s's, use 'Quit' instead. ") % self.__class__.__name__ +\ 1341 _('Really delete this %s and its settings?') % self.get_short_name()) 1342 """return not show_question(self, 'Deleting this instance of the '+\ 1343 self.__name__ + ' will also delete all your personal '+\ 1344 'changes you made to it!! If you just want to close the '+\ 1345 'application, use "Quit" instead. Are you sure you want to '+\ 1346 'delete this instance?') 1347 return False"""
1348 1349 # TODO: on_drag 1350 # TODO: on_drag_end 1351
1352 - def on_after_set_atribute(self,name, value):
1353 """Called after setting screenlet atributes""" 1354 pass
1355
1356 - def on_before_set_atribute(self,name, value):
1357 """Called before setting screenlet atributes""" 1358 pass
1359 1360
1361 - def on_create_drag_icon (self):
1362 """Called when the screenlet's drag-icon is created. You can supply 1363 your own icon and mask by returning them as a 2-tuple.""" 1364 return (None, None)
1365
1366 - def on_composite_changed(self):
1367 """Called when composite state has changed""" 1368 pass
1369 1370
1371 - def on_drag_begin (self, drag_context):
1372 """Called when the Screenlet gets dragged.""" 1373 pass
1374
1375 - def on_drag_enter (self, drag_context, x, y, timestamp):
1376 """Called when something gets dragged into the Screenlets area.""" 1377 pass
1378
1379 - def on_drag_leave (self, drag_context, timestamp):
1380 """Called when something gets dragged out of the Screenlets area.""" 1381 pass
1382
1383 - def on_draw (self, ctx):
1384 """Callback for drawing the Screenlet's window - override 1385 in subclasses to implement your own drawing.""" 1386 pass
1387
1388 - def on_draw_shape (self, ctx):
1389 """Callback for drawing the Screenlet's shape - override 1390 in subclasses to draw the window's input-shape-mask.""" 1391 pass
1392
1393 - def on_drop (self, x, y, sel_data, timestamp):
1394 """Called when a selection is dropped on this Screenlet.""" 1395 return False
1396
1397 - def on_focus (self, event):
1398 """Called when the Screenlet's window receives focus.""" 1399 pass
1400
1401 - def on_hide (self):
1402 """Called when the Screenlet gets hidden.""" 1403 pass
1404
1405 - def on_init (self):
1406 """Called when the Screenlet's options have been applied and the 1407 screenlet finished its initialization. If you want to have your 1408 Screenlet do things on startup you should use this handler.""" 1409 pass
1410
1411 - def on_key_down (self, keycode, keyvalue, event=None):
1412 """Called when a key is pressed within the screenlet's window.""" 1413 pass
1414
1415 - def on_load_theme (self):
1416 """Called when the theme is reloaded (after loading, before redraw).""" 1417 pass
1418
1419 - def on_menuitem_select (self, id):
1420 """Called when a menuitem is selected.""" 1421 pass
1422
1423 - def on_mouse_down (self, event):
1424 """Called when a buttonpress-event occured in Screenlet's window. 1425 Returning True causes the event to be not further propagated.""" 1426 return False
1427
1428 - def on_mouse_enter (self, event):
1429 """Called when the mouse enters the Screenlet's window.""" 1430 pass
1431
1432 - def on_mouse_leave (self, event):
1433 """Called when the mouse leaves the Screenlet's window.""" 1434 pass
1435
1436 - def on_mouse_move(self, event):
1437 """Called when the mouse moves in the Screenlet's window.""" 1438 pass
1439
1440 - def on_mouse_up (self, event):
1441 """Called when a buttonrelease-event occured in Screenlet's window. 1442 Returning True causes the event to be not further propagated.""" 1443 return False
1444
1445 - def on_quit (self):
1446 """Callback for handling destroy-event. Perform your cleanup here!""" 1447 return True
1448
1449 - def on_realize (self):
1450 """"Callback for handling the realize-event."""
1451
1452 - def on_scale (self):
1453 """Called when Screenlet.scale is changed.""" 1454 pass
1455
1456 - def on_scroll_up (self):
1457 """Called when mousewheel is scrolled up (button4).""" 1458 pass
1459
1460 - def on_scroll_down (self):
1461 """Called when mousewheel is scrolled down (button5).""" 1462 pass
1463
1464 - def on_show (self):
1465 """Called when the Screenlet gets shown after being hidden.""" 1466 pass
1467
1468 - def on_switch_widget_state (self, state):
1469 """Called when the Screenlet enters/leaves "Widget"-state.""" 1470 pass
1471
1472 - def on_unfocus (self, event):
1473 """Called when the Screenlet's window loses focus.""" 1474 pass
1475 1476 # ---------------------------------------------------------------------- 1477 # Screenlet's event-handlers for GTK-events 1478 # ---------------------------------------------------------------------- 1479
1480 - def alpha_screen_changed (self, window, screen=None):
1481 """set colormap for window""" 1482 if screen==None: 1483 screen = window.get_screen() 1484 map = screen.get_rgba_colormap() 1485 if map: 1486 pass 1487 else: 1488 map = screen.get_rgb_colormap() 1489 window.set_colormap(map)
1490
1491 - def button_press (self, widget, event):
1492 1493 #print "Button press" 1494 # set flags for user-handler 1495 1496 if self.lock_position == False: 1497 if event.button == 1: 1498 self.is_dragged = True 1499 # call user-handler for onmousedown 1500 if self.on_mouse_down(event) == True: 1501 return True 1502 # unhandled? continue 1503 1504 if self.mousex >= self.width - (32/self.scale) and self.mousey <= (16/self.scale) and self.draw_buttons and self.show_buttons: 1505 if self.mousex >= self.width - (16/self.scale): 1506 self.menuitem_callback(widget,'quit_instance') 1507 elif self.mousex <= self.width -(16/self.scale): 1508 self.menuitem_callback(widget,'info') 1509 elif self.lock_position == False: 1510 if event.button == 1: 1511 widget.begin_move_drag(event.button, int(event.x_root), 1512 int(event.y_root), event.time) 1513 1514 if event.button == 3: 1515 try: 1516 self.__mi_lock.set_active(self.lock_position) 1517 self.__mi_sticky.set_active(self.is_sticky) 1518 self.__mi_widget.set_active(self.is_widget) 1519 self.__mi_keep_above.set_active(self.keep_above) 1520 self.__mi_keep_below.set_active(self.keep_below) 1521 except : pass 1522 self.menu.popup(None, None, None, event.button, event.time) 1523 elif event.button == 4: 1524 print _("MOUSEWHEEL") 1525 self.scale -= 0.1 1526 elif event.button == 5: 1527 print _("MOUSEWHEEL") 1528 self.scale += 0.1 1529 return False
1530
1531 - def button_release (self, widget, event):
1532 print "Button release" 1533 self.is_dragged = False # doesn't work!!! we don't get an event when move_drag ends :( ... 1534 if self.on_mouse_up(event): 1535 return True 1536 return False
1537
1538 - def composite_changed(self,widget):
1539 #this handle is called when composition changed 1540 self.remove_shape() # removing previous set shape , this is absolutly necessary 1541 self.window.hide() # hiding the window and showing it again so the window can convert to the right composited state 1542 self.is_sticky = self.is_sticky #changing from non composited to composited makes the screenlets loose sticky state , this fixes that 1543 self.keep_above= self.keep_above 1544 self.keep_below= self.keep_below 1545 self.window.show() 1546 print _('Compositing method changed to %s') % str(self.window.is_composited()) 1547 self.update_shape() 1548 self.redraw_canvas() 1549 1550 if not self.window.is_composited () : 1551 self.show_buttons = False 1552 print _('Warning - Buttons will not be shown until screenlet is restarted') 1553 self.is_sticky = self.is_sticky #and again ... 1554 self.keep_above= self.keep_above 1555 self.keep_below= self.keep_below 1556 self.window.set_keep_above(self.keep_above) 1557 self.window.set_keep_below(self.keep_below) 1558 self.on_composite_changed()
1559 1560 # NOTE: this should somehow handle the end of a move_drag-operation
1561 - def configure_event (self, widget, event):
1562 #print "onConfigure" 1563 #print event 1564 #if self.is_dragged == True: 1565 # set new position and cause a save of this Screenlet (not use 1566 # setattr to avoid conflicts with the window.move in __setattr__) 1567 if event.x != self.x: 1568 self.__dict__['x'] = event.x 1569 if self.session: 1570 self.session.backend.save_option(self.id, 'x', str(event.x)) 1571 if event.y != self.y: 1572 self.__dict__['y'] = event.y 1573 if self.session: 1574 self.session.backend.save_option(self.id, 'y', str(event.y)) 1575 return False
1576
1577 - def delete_event (self, widget, event, data=None):
1578 # cancel event? 1579 print "delete_event" 1580 if self.on_delete() == True: 1581 print _("Cancel delete_event") 1582 return True 1583 else: 1584 self.close() 1585 return False
1586
1587 - def destroy (self, widget, data=None):
1588 # call user-defined on_quit-handler 1589 self.on_quit() 1590 #print "destroy signal occurred" 1591 self.emit("screenlet_removed", self) 1592 # close gtk? 1593 if self.quit_on_close: 1594 if self.session: # if we have a session, flush current data 1595 self.session.backend.flush() 1596 gtk.main_quit() 1597 else: 1598 del self # ??? does this really work???
1599
1600 - def drag_begin (self, widget, drag_context):
1601 print _("Start drag") 1602 self.is_dragged = True 1603 self.on_drag_begin(drag_context)
1604 #return False 1605
1606 - def drag_data_received (self, widget, dc, x, y, sel_data, info, timestamp):
1607 return self.on_drop(x, y, sel_data, timestamp)
1608
1609 - def drag_end (self, widget, drag_context):
1610 print _("End drag") 1611 self.is_dragged = False 1612 return False
1613
1614 - def drag_motion (self, widget, drag_context, x, y, timestamp):
1615 #print "Drag motion" 1616 if self.dragging_over == False: 1617 self.dragging_over = True 1618 self.on_drag_enter(drag_context, x, y, timestamp) 1619 return False
1620
1621 - def drag_leave (self, widget, drag_context, timestamp):
1622 self.dragging_over = False 1623 self.on_drag_leave(drag_context, timestamp) 1624 return
1625
1626 - def enter_notify_event (self, widget, event):
1627 #self.__mouse_inside = True 1628 1629 self.on_mouse_enter(event)
1630 1631 #self.redraw_canvas() 1632
1633 - def expose (self, widget, event):
1634 ctx = widget.window.cairo_create() 1635 # clear context 1636 self.clear_cairo_context(ctx) 1637 # set a clip region for the expose event 1638 ctx.rectangle(event.area.x, event.area.y, 1639 event.area.width, event.area.height) 1640 ctx.clip() 1641 1642 # scale context 1643 #ctx.scale(self.scale, self.scale) 1644 # call drawing method 1645 self.on_draw(ctx) 1646 # and delete context (needed?) 1647 del ctx 1648 return False
1649
1650 - def focus_in_event (self, widget, event):
1651 self.has_focus = True 1652 self.on_focus(event) 1653 self.update_shape() 1654 self.redraw_canvas()
1655 1656 1657 1658
1659 - def focus_out_event (self, widget, event):
1660 self.has_focus = False 1661 self.on_unfocus(event) 1662 self.update_shape() 1663 self.redraw_canvas()
1664 1665 1666
1667 - def key_press (self, widget, event):
1668 """Handle keypress events, needed for in-place editing.""" 1669 self.on_key_down(event.keyval, event.string, event)
1670
1671 - def leave_notify_event (self, widget, event):
1672 #self.__mouse_inside = False 1673 1674 self.on_mouse_leave(event)
1675 1676 #self.redraw_canvas() 1677
1678 - def menuitem_callback (self, widget, id):
1679 if id == "delete": 1680 if not self.on_delete(): 1681 # remove instance 1682 self.session.delete_instance (self.id) 1683 # notify about being rmeoved (does this get send???) 1684 self.service.instance_removed(self.id) 1685 elif id == "quit_instance": 1686 print _('Quitting current screenlet instance') 1687 self.session.quit_instance (self.id) 1688 self.service.instance_removed(self.id) 1689 elif id == "quit": 1690 self.close() 1691 elif id in ("info", "about", "settings", "options", "properties"): 1692 # show settings dialog 1693 self.show_settings_dialog() 1694 elif id.startswith('scale:'): 1695 self.scale = float(id[6:]) 1696 elif id[:5] == "size:": # DEPRECATED?? 1697 # set size and update shape (redraw is done by setting height) 1698 #self.__dict__['width'] = int(id[5:]) 1699 self.width = int(id[5:]) 1700 self.height = int(id[5:]) 1701 self.update_shape() 1702 elif id[:6]=="theme:": 1703 print _("Screenlet: Set theme %s") % id[6:] 1704 # set theme 1705 self.theme_name = id[6:] 1706 elif id[:8] == "setting:": 1707 # set a boolean option to the opposite state 1708 try: 1709 if type(self.__dict__[id[8:]]) == bool: 1710 self.__dict__[id[8:]] = not self.__dict__[id[8:]] # UNSAFE!! 1711 except: 1712 print _("Error: Cannot set missing or non-boolean value '")\ 1713 + id[8:] + "'" 1714 elif id[:7] == "option:": 1715 # NOTE: this part should be removed and XML-menus 1716 # should be used by default ... maybe 1717 # set option 1718 if id[7:]=="lock": 1719 if self.__mi_lock.get_active () != self.lock_position: 1720 self.lock_position = not self.lock_position 1721 elif id[7:]=="sticky": 1722 if self.__mi_sticky.get_active () != self.is_sticky: 1723 self.is_sticky = not self.is_sticky 1724 #widget.toggle() 1725 elif id[7:]=="widget": 1726 if self.__mi_widget.get_active () != self.is_widget: 1727 self.is_widget = not self.is_widget 1728 elif id[7:]=="keep_above": 1729 if self.__mi_keep_above.get_active () != self.keep_above: 1730 self.keep_above = not self.keep_above 1731 self.__mi_keep_above.set_active(self.keep_above) 1732 if self.keep_below and self.keep_above : 1733 self.keep_below = False 1734 self.__mi_keep_below.set_active(False) 1735 elif id[7:]=="keep_below": 1736 if self.__mi_keep_below.get_active () != self.keep_below: 1737 self.keep_below = not self.keep_below 1738 self.__mi_keep_below.set_active(self.keep_below) 1739 if self.keep_below and self.keep_above : 1740 self.keep_above = False 1741 self.__mi_keep_above.set_active(False) 1742 else: 1743 #print "Item: " + string 1744 pass 1745 # call user-handler 1746 self.on_menuitem_select(id) 1747 return False
1748
1749 - def motion_notify_event(self, widget, event):
1750 self.mousex = event.x / self.scale 1751 self.mousey = event.y / self.scale 1752 self.on_mouse_move(event)
1753
1754 - def realize_event (self, widget):
1755 """called when window has been realized""" 1756 if self.window.window: 1757 self.window.window.set_back_pixmap(None, False) # needed? 1758 1759 self.on_realize()
1760
1761 - def scroll_event (self, widget, event):
1762 if event.direction == gtk.gdk.SCROLL_UP: 1763 self.on_scroll_up() 1764 elif event.direction == gtk.gdk.SCROLL_DOWN: 1765 self.on_scroll_down() 1766 return False
1767 1768 1769 1770 # TEST!!!
1771 -class ShapedWidget (gtk.DrawingArea):
1772 """A simple base-class for creating owner-drawn gtk-widgets""" 1773 1774 __widget=None 1775 1776 mouse_inside = False 1777 width = 32 1778 height = 32 1779
1780 - def __init__ (self, width, height):
1781 # call superclass 1782 super(ShapedWidget, self).__init__() 1783 # create/setup widget 1784 #self.__widget = gtk.Widget() 1785 self.set_app_paintable(True) 1786 self.set_size_request(width, height) 1787 # connect handlers 1788 self.set_events(gtk.gdk.ALL_EVENTS_MASK) 1789 self.connect("expose-event", self.expose_event) 1790 self.connect("button-press-event", self.button_press) 1791 self.connect("button-release-event", self.button_release) 1792 self.connect("enter-notify-event", self.enter_notify) 1793 self.connect("leave-notify-event", self.leave_notify)
1794 1795 # EXPERIMENTAL: TODO: cache bitmap until size changes
1796 - def update_shape (self):
1797 """update widget's shape (only call this when shape has changed)""" 1798 data = "" 1799 for i in xrange(self.width*self.height): 1800 data += "0" 1801 bitmap = gtk.gdk.bitmap_create_from_data(None, 1802 data, self.width, self.height) 1803 ctx = bitmap.cairo_create() 1804 ctx.set_source_rgba(1, 1, 1, 0) 1805 ctx.set_operator (cairo.OPERATOR_SOURCE) 1806 ctx.paint() 1807 self.draw_shape(ctx) 1808 self.input_shape_combine_mask(bitmap, 0, 0) 1809 print "Updating shape."
1810
1811 - def button_press (self, widget, event):
1812 if event.button==1: 1813 print "left button pressed!" 1814 return False
1815
1816 - def button_release (self, widget, event):
1817 #if event.button==1: 1818 #print "left button release!" 1819 return False
1820
1821 - def enter_notify (self, widget, event):
1822 self.mouse_inside = True 1823 self.queue_draw()
1824 #print "mouse enter" 1825
1826 - def leave_notify (self, widget, event):
1827 self.mouse_inside = False 1828 self.queue_draw()
1829 #print "mouse leave" 1830
1831 - def draw (self, ctx):
1832 pass
1833
1834 - def draw_shape (self, ctx):
1835 self.draw(ctx)
1836
1837 - def expose_event (self, widget, event):
1838 ctx = widget.window.cairo_create() 1839 # set a clip region for the expose event 1840 ctx.rectangle(event.area.x, event.area.y, 1841 event.area.width, event.area.height) 1842 ctx.clip() 1843 # clear context 1844 ctx.set_source_rgba(1, 1, 1, 0) 1845 ctx.set_operator (cairo.OPERATOR_SOURCE) 1846 ctx.paint() 1847 # call drawing method 1848 self.draw(ctx) 1849 # and delete context 1850 del ctx 1851 return False
1852
1853 -class Tooltip:
1854 """A window that displays a text and serves as Tooltip (very basic yet).""" 1855 1856 # internals 1857 __timeout = None 1858 1859 # attribs 1860 text = '' 1861 font_name = 'FreeSans 9' 1862 width = 100 1863 height = 20 1864 x = 0 1865 y = 0 1866
1867 - def __init__ (self, width, height):
1868 object.__init__(self) 1869 # init 1870 self.__dict__['width'] = width 1871 self.__dict__['height'] = height 1872 self.window = gtk.Window() 1873 self.window.set_app_paintable(True) 1874 self.window.set_size_request(width, height) 1875 self.window.set_decorated(False) 1876 self.window.set_accept_focus(False) 1877 self.window.set_skip_pager_hint(True) 1878 self.window.set_skip_taskbar_hint(True) 1879 self.window.set_keep_above(True) 1880 self.screen_changed(self.window) 1881 self.window.connect("expose_event", self.expose) 1882 self.window.connect("screen-changed", self.screen_changed) 1883 #self.window.show() 1884 self.p_context = self.window.get_pango_context() 1885 self.p_layout = pango.Layout(self.p_context) 1886 self.p_layout.set_font_description(\ 1887 pango.FontDescription(self.font_name)) 1888 #self.p_layout.set_width(-1) 1889 self.p_layout.set_width(width * pango.SCALE - 6)
1890
1891 - def __setattr__ (self, name, value):
1892 self.__dict__[name] = value 1893 if name in ('width', 'height', 'text'): 1894 if name== 'width': 1895 self.p_layout.set_width(width) 1896 elif name == 'text': 1897 self.p_layout.set_markup(value) 1898 ink_rect, logical_rect = self.p_layout.get_pixel_extents() 1899 self.height = min(max(logical_rect[3], 16), 400) + 6 1900 self.window.set_size_request(self.width, self.height) 1901 self.window.queue_draw() 1902 elif name == 'x': 1903 self.window.move(int(value), int(self.y)) 1904 elif name == 'y': 1905 self.window.move(int(self.x), int(value))
1906
1907 - def show (self):
1908 """Show the Tooltip window.""" 1909 self.cancel_show() 1910 self.window.show() 1911 self.window.set_keep_above(True)
1912
1913 - def show_delayed (self, delay):
1914 """Show the Tooltip window after a given delay.""" 1915 self.cancel_show() 1916 self.__timeout = gobject.timeout_add(delay, self.__show_timeout)
1917
1918 - def hide (self):
1919 """Hide the Tooltip window.""" 1920 self.cancel_show() 1921 self.window.destroy()
1922
1923 - def cancel_show (self):
1924 """Cancel showing of the Tooltip.""" 1925 if self.__timeout: 1926 gobject.source_remove(self.__timeout) 1927 self.p_context = None 1928 self.p_layout = None
1929
1930 - def __show_timeout (self):
1931 self.show()
1932
1933 - def screen_changed (self, window, screen=None):
1934 if screen == None: 1935 screen = window.get_screen() 1936 map = screen.get_rgba_colormap() 1937 if not map: 1938 map = screen.get_rgb_colormap() 1939 window.set_colormap(map)
1940
1941 - def expose (self, widget, event):
1942 ctx = self.window.window.cairo_create() 1943 ctx.set_antialias (cairo.ANTIALIAS_SUBPIXEL) # ? 1944 # set a clip region for the expose event 1945 ctx.rectangle(event.area.x, event.area.y,event.area.width, event.area.height) 1946 ctx.clip() 1947 # clear context 1948 ctx.set_source_rgba(1, 1, 1, 0) 1949 ctx.set_operator (cairo.OPERATOR_SOURCE) 1950 ctx.paint() 1951 # draw rectangle 1952 ctx.set_source_rgba(1, 1, 0.5, 1) 1953 ctx.rectangle(0, 0, self.width, self.height) 1954 ctx.fill() 1955 # draw text 1956 ctx.save() 1957 ctx.translate(3, 3) 1958 ctx.set_source_rgba(0, 0, 0, 1) 1959 ctx.show_layout(self.p_layout) 1960 ctx.fill() 1961 ctx.restore() 1962 ctx.rectangle(0, 0, self.width, self.height) 1963 ctx.set_source_rgba(0, 0, 0, 0.7) 1964 ctx.stroke()
1965
1966 -class Notify:
1967 """A window that displays a text and serves as Notification (very basic yet).""" 1968 1969 # internals 1970 __timeout = None 1971 1972 # attribs 1973 text = '' 1974 font_name = 'FreeSans 9' 1975 width = 200 1976 height = 100 1977 x = 0 1978 y = 0 1979 gradient = cairo.LinearGradient(0, 100,0, 0) 1980
1981 - def __init__ (self):
1982 object.__init__(self) 1983 # init 1984 self.window = gtk.Window() 1985 self.window.set_app_paintable(True) 1986 self.window.set_size_request(self.width, self.height) 1987 self.window.set_decorated(False) 1988 self.window.set_accept_focus(False) 1989 self.window.set_skip_pager_hint(True) 1990 self.window.set_skip_taskbar_hint(True) 1991 self.window.set_keep_above(True) 1992 self.screen_changed(self.window) 1993 self.window.connect("expose_event", self.expose) 1994 self.window.connect("screen-changed", self.screen_changed) 1995 #self.window.show() 1996 self.p_context = self.window.get_pango_context() 1997 self.p_layout = pango.Layout(self.p_context) 1998 self.p_layout.set_font_description(\ 1999 pango.FontDescription(self.font_name)) 2000 #self.p_layout.set_width(-1) 2001 self.p_layout.set_width(self.width * pango.SCALE - 6)
2002
2003 - def __setattr__ (self, name, value):
2004 self.__dict__[name] = value 2005 if name in ('text'): 2006 if name == 'text': 2007 self.p_layout.set_markup(value) 2008 ink_rect, logical_rect = self.p_layout.get_pixel_extents() 2009 self.window.queue_draw()
2010
2011 - def show (self):
2012 """Show the Notify window.""" 2013 self.window.move(gtk.gdk.screen_width() - self.width, gtk.gdk.screen_height() - self.height) 2014 self.cancel_show() 2015 self.window.show() 2016 self.window.set_keep_above(True)
2017
2018 - def show_delayed (self, delay):
2019 """Show the Notify window after a given delay.""" 2020 self.cancel_show() 2021 self.__timeout = gobject.timeout_add(delay, self.__show_timeout)
2022
2023 - def hide (self):
2024 """Hide the Notify window.""" 2025 self.cancel_show() 2026 self.window.destroy()
2027
2028 - def cancel_show (self):
2029 """Cancel showing of the Notify.""" 2030 if self.__timeout: 2031 gobject.source_remove(self.__timeout) 2032 self.p_context = None 2033 self.p_layout = None
2034
2035 - def __show_timeout (self):
2036 self.show()
2037
2038 - def screen_changed (self, window, screen=None):
2039 if screen == None: 2040 screen = window.get_screen() 2041 map = screen.get_rgba_colormap() 2042 if not map: 2043 map = screen.get_rgb_colormap() 2044 window.set_colormap(map)
2045
2046 - def expose (self, widget, event):
2047 ctx = self.window.window.cairo_create() 2048 ctx.set_antialias (cairo.ANTIALIAS_SUBPIXEL) # ? 2049 # set a clip region for the expose event 2050 ctx.rectangle(event.area.x, event.area.y,event.area.width, event.area.height) 2051 ctx.clip() 2052 # clear context 2053 ctx.set_source_rgba(1, 1, 1, 0) 2054 ctx.set_operator (cairo.OPERATOR_SOURCE) 2055 ctx.paint() 2056 # draw rectangle 2057 self.gradient.add_color_stop_rgba(1,0.3, 0.3, 0.3, 0.9) 2058 self.gradient.add_color_stop_rgba(0.3, 0, 0, 0, 0.9) 2059 ctx.set_source(self.gradient) 2060 ctx.rectangle(0, 0, self.width, self.height) 2061 ctx.fill() 2062 # draw text 2063 ctx.save() 2064 ctx.translate(3, 3) 2065 ctx.set_source_rgba(1, 1, 1, 1) 2066 ctx.show_layout(self.p_layout) 2067 ctx.fill() 2068 ctx.restore() 2069 ctx.rectangle(0, 0, self.width, self.height) 2070 ctx.set_source_rgba(0, 0, 0, 0.7) 2071 ctx.stroke()
2072 2073 # TEST (as the name implies) 2074 """class TestWidget(ShapedWidget): 2075 2076 def __init__(self, width, height): 2077 #ShapedWidget.__init__(self, width, height) 2078 super(TestWidget, self).__init__(width, height) 2079 2080 def draw(self, ctx): 2081 if self.mouse_inside: 2082 ctx.set_source_rgba(1, 0, 0, 0.8) 2083 else: 2084 ctx.set_source_rgba(1, 1, 0, 0.8) 2085 ctx.rectangle(0, 0, 32, 32) 2086 ctx.fill() 2087 """ 2088 2089 2090 # ------------------------------------------------------------------------------ 2091 # MODULE-FUNCTIONS 2092 # ------------------------------------------------------------------------------ 2093 2094 # the new recommended way of launching a screenlet from the "outside"
2095 -def launch_screenlet (name, debug=False):
2096 """Launch a screenlet, either through its service or by launching a new 2097 process of the given screenlet. Name has to be the name of the Screenlet's 2098 class without trailing 'Screenlet'. 2099 NOTE: we could only launch the file here""" 2100 # check for service 2101 if services.service_is_running(name): 2102 # add screenlet through service, if running 2103 srvc = services.get_service_by_name(name) 2104 if srvc: 2105 try: 2106 srvc.add('') # empty string for auto-creating ID 2107 return True 2108 except Exception, ex: 2109 print "Error while adding instance by service: %s" % ex 2110 # service not running or error? launch screenlet's file 2111 path = utils.find_first_screenlet_path(name) 2112 if path: 2113 # get full path of screenlet's file 2114 slfile = path + '/' + name + 'Screenlet.py' 2115 # launch screenlet as separate process 2116 print "Launching Screenlet from: %s" % slfile 2117 if debug: 2118 print "Logging output goes to: $HOME/.config/Screenlets/%sScreenlet.log" % name 2119 out = '$HOME/.config/Screenlets/%sScreenlet.log' % name 2120 else: 2121 out = '/dev/null' 2122 os.system('python -u %s > %s &' % (slfile, out)) 2123 return True 2124 else: 2125 print "Screenlet '%s' could not be launched." % name 2126 return False
2127
2128 -def show_message (screenlet, message, title=''):
2129 """Show a message for the given Screenlet (may contain Pango-Markup). 2130 If screenlet is None, this function can be used by other objects as well.""" 2131 if screenlet == None: 2132 md = gtk.MessageDialog(None, type=gtk.MESSAGE_INFO, 2133 buttons=gtk.BUTTONS_OK) 2134 md.set_title(title) 2135 else: 2136 md = gtk.MessageDialog(screenlet.window, type=gtk.MESSAGE_INFO, 2137 buttons=gtk.BUTTONS_OK) 2138 md.set_title(screenlet.__name__) 2139 md.set_markup(message) 2140 md.run() 2141 md.destroy()
2142
2143 -def show_question (screenlet, message, title=''):
2144 """Show a question for the given Screenlet (may contain Pango-Markup).""" 2145 if screenlet == None: 2146 md = gtk.MessageDialog(None, type=gtk.MESSAGE_QUESTION, 2147 buttons=gtk.BUTTONS_YES_NO) 2148 md.set_title(title) 2149 else: 2150 md = gtk.MessageDialog(screenlet.window, type=gtk.MESSAGE_QUESTION, 2151 buttons=gtk.BUTTONS_YES_NO) 2152 md.set_title(screenlet.__name__) 2153 md.set_markup(message) 2154 response = md.run() 2155 md.destroy() 2156 if response == gtk.RESPONSE_YES: 2157 return True 2158 return False
2159
2160 -def show_error (screenlet, message, title='Error'):
2161 """Show an error for the given Screenlet (may contain Pango-Markup).""" 2162 if screenlet == None: 2163 md = gtk.MessageDialog(None, type=gtk.MESSAGE_ERROR, 2164 buttons=gtk.BUTTONS_OK) 2165 md.set_title(title) 2166 else: 2167 md = gtk.MessageDialog(screenlet.window, type=gtk.MESSAGE_ERROR, 2168 buttons=gtk.BUTTONS_OK) 2169 md.set_title(screenlet.__name__) 2170 md.set_markup(message) 2171 md.run() 2172 md.destroy()
2173
2174 -def fatal_error (message):
2175 """Raise a fatal error to stdout and stderr and exit with an errorcode.""" 2176 import sys 2177 msg = 'FATAL ERROR: %s\n' % message 2178 sys.stdout.write(msg) 2179 sys.stderr.write(msg) 2180 sys.exit(1)
2181 2182 # LEGACY support: functions that are not used any longer (raise fatal error) 2183
2184 -def create_new_instance (name):
2185 fatal_error("This screenlet seems to be written for an older version of the framework. Please download a newer version of the %s." % name)
2186