class DockTheme(gobject.GObject): __gsignals__ = {"dock-theme-reloaded": (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,()),} def __init__(self): gobject.GObject.__init__(self) self.globals = Globals() self.name = "DBX" self.settings = {} self.globals.connect("dock-theme-changed", self.on_theme_changed) self.on_theme_changed() def get(self, key, default=None): return self.settings.get(key, default) def get_bg(self, bar, size=None): if size is None: return self.bg[bar] if size != self.bg_sizes[bar]: bg = self.bg[bar] w = bg.get_width() self.resized_bg[bar] = self.__resize_surface(bg, w, size) self.bg_sizes[bar] = size return self.resized_bg[bar] def find_themes(self): # Reads the themes from /usr/share/dockbarx/themes/dock_themes and # ${XDG_DATA_HOME:-$HOME/.local/share}/dockbarx/themes/dock_themes # and returns a dict of the theme file names and paths so that a # theme can be loaded themes = {} theme_paths = [] theme_folder = os.path.join(get_app_homedir(), "themes", "dock") dirs = ["/usr/share/dockbarx/themes/dock", theme_folder] for dir in dirs: if os.path.exists(dir) and os.path.isdir(dir): for f in os.listdir(dir): if f[-7:] == ".tar.gz": themes[f] = dir+"/"+f return themes def on_theme_changed(self, arg=None): themes = self.find_themes() if self.globals.settings["dock/theme_file"] in themes: self.theme_path = themes[self.globals.settings["dock/theme_file"]] else: self.theme_path = themes.get("dbx.tar.gz", "dbx.tar.gz") self.reload() def reload(self): if self.theme_path is None: return self.default_colors = {"bg_color": "#111111", "bg_alpha": 127, "bar2_bg_color":"#111111", "bar2_bg_alpha": 127} try: tar = taropen(self.theme_path) except: logger.debug("Error opening dock theme %s" % self.theme_path) self.settings = {} self.name = "DBX" self.bg = {1: None, 2:None} self.bg_sizes = {1: -1, 2:-1} self.globals.set_dock_theme("dbx.tar.gz", self.default_colors) self.emit("dock-theme-reloaded") return # Load settings try: config = tar.extractfile("theme") except: logger.exception("Error extracting theme from %s" % \ self.theme_path) tar.close() self.settings = {} self.name = "DBX" self.bg = None self.globals.set_dock_theme("dbx.tar.gz", self.default_colors) self.emit("dock-theme-reloaded") return old_settings = self.settings self.settings = {} name = None for line in config.readlines(): # Split at "=" and clean up the key and value if not "=" in line: continue key, value = line.split("=", 1) key = key.strip().lstrip().lower() value = value.strip().lstrip() # Remove comments if "#" in key: continue # If there is a trailing comment, remove it # But avoid removing # if it's in a quote sharp = value.find("#") if sharp != -1 and value.count("\"", 0, sharp) % 2 == 0 and \ value.count("'", 0, sharp) % 2 == 0: value = value.split("#", 1)[0].strip() # Remove quote signs if value[0] in ("\"", "'") and value[-1] in ("\"", "'"): value = value[1:-1] if key == "name": name = value continue value = value.lower() self.settings[key] = value config.close() if name: self.name = name else: # Todo: Error handling here! self.settings = old_settings tar.close() self.globals.set_dock_theme("dbx.tar.gz", self.default_colors) self.emit("dock-theme-reloaded") return # Load background self.bg = {1:None, 2:None} self.bg_sizes = {1: -1, 2: -1} self.resized_bg = {} if "background.png" in tar.getnames(): bgf = tar.extractfile("background.png") self.bg[1] = cairo.ImageSurface.create_from_png(bgf) bgf.close() if "bar2_background.png" in tar.getnames(): bgf = tar.extractfile("bar2_background.png") self.bg[2] = cairo.ImageSurface.create_from_png(bgf) bgf.close() tar.close() for key in self.default_colors.keys(): if key in self.settings: value = self.settings.pop(key) if "alpha" in key: value = int(round(int(value))*2.55) elif value[0] != "#": value = "#%s" % value self.default_colors[key] = value # Inform rest of dockbar about the reload. self.globals.set_dock_theme(self.theme_path.rsplit("/", 1)[-1], self.default_colors) self.emit("dock-theme-reloaded") def get_themes(self): # For DockbarX preference. This function makes a dict of the names and # file names of the themes for all themes that can be opened correctly. themes = {} theme_folder = os.path.join(get_app_homedir(), "themes", "dock") dirs = ["/usr/share/dockbarx/themes/dock", theme_folder] for dir in dirs: if os.path.exists(dir) and os.path.isdir(dir): for f in os.listdir(dir): if f[-7:] == ".tar.gz": name = self.check(dir+"/"+f) if name: themes[name] = f # The default theme (if the theme doesn't set another one) is DBX, # wheter or not the file actually exists. if not "DBX" in themes: themes["DBX"] = "dbx.tar.gz" return themes def check(self, theme_path): try: tar = taropen(theme_path) except: return None try: config = tar.extractfile("theme") except: tar.close() return None name = None for line in config.readlines(): # Split at "=" and clean up the key and value if not "=" in line: continue key, value = line.split("=", 1) key = key.strip().lstrip().lower() value = value.strip().lstrip() # Remove comments if "#" in key: continue # If there is a trailing comment, remove it # But avoid removing # if it's in a quote sharp = value.find("#") if sharp != -1 and value.count("\"", 0, sharp) % 2 == 0 and \ value.count("'", 0, sharp) % 2 == 0: value = value.split("#", 1)[0].strip() # Remove quote signs if value[0] in ("\"", "'") and value[-1] in ("\"", "'"): value = value[1:-1] if key == "name": name = value break tar.close() return name def __surface2pil(self, surface): w = surface.get_width() h = surface.get_height() return Image.frombuffer("RGBA", (w, h), surface.get_data(), "raw", "RGBA", 0,1) def __pil2surface(self, im): """Transform a PIL Image into a Cairo ImageSurface.""" # This function is only supposed to work with little endinan # systems. Could that be a problem ever? if im.mode != 'RGBA': im = im.convert('RGBA') s = im.tobytes('raw', 'BGRA') a = array.array('B', s) dest = cairo.ImageSurface(cairo.FORMAT_ARGB32, im.size[0], im.size[1]) ctx = cairo.Context(dest) non_premult_src_wo_alpha = cairo.ImageSurface.create_for_data( a, cairo.FORMAT_RGB24, im.size[0], im.size[1]) non_premult_src_alpha = cairo.ImageSurface.create_for_data( a, cairo.FORMAT_ARGB32, im.size[0], im.size[1]) ctx.set_source_surface(non_premult_src_wo_alpha) ctx.mask_surface(non_premult_src_alpha) return dest def __resize_surface(self, surface, w, h): im = self.__surface2pil(surface) im = im.resize((w, h), Image.ANTIALIAS) return self.__pil2surface(im)
class PopupStyle(gobject.GObject): __gsignals__ = {"popup-style-reloaded": (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,()),} def __new__(cls, *p, **k): if not "_the_instance" in cls.__dict__: cls._the_instance = gobject.GObject.__new__(cls) return cls._the_instance def __init__(self): if "is_initiated" in self.__dict__: # This is not the first instance of PopupStyle, # no need to initiate anything return self.is_initiated = True gobject.GObject.__init__(self) self.globals = Globals() self.name = "DBX" self.settings = {} self.globals.connect("popup-style-changed", self.on_style_changed) self.on_style_changed() def get(self, key, default=None): return self.settings.get(key, default) def find_styles(self): # Reads the styles from /usr/share/dockbarx/themes/popup_styles and # ${XDG_DATA_HOME:-$HOME/.local/share}/dockbarx/themes/popup_styles # and returns a dict of the style file names and paths so that a # style can be loaded styles = {} style_paths = [] style_folder = os.path.join(get_app_homedir(), "themes", "popup_styles") dirs = ["/usr/share/dockbarx/themes/popup_styles", style_folder] for dir in dirs: if os.path.exists(dir) and os.path.isdir(dir): for f in os.listdir(dir): if f[-7:] == ".tar.gz": styles[f] = dir+"/"+f return styles def on_style_changed(self, arg=None): styles = self.find_styles() if self.globals.popup_style_file in styles: self.style_path = styles[self.globals.popup_style_file] elif self.globals.default_popup_style in styles: self.style_path = styles[self.globals.default_popup_style] else: self.style_path = styles.get("dbx.tar.gz", "dbx.tar.gz") self.reload() def reload(self): if self.style_path is None: return # Default settings self.bg = None self.cb_pressed_pic = None self.cb_hover_pic = None self.cb_normal_pic = None self.settings = {"border_color2": "#000000", "menu_item_lr_padding": 3} self.name = "DBX" try: tar = taropen(self.style_path) except: logger.debug("Error opening style %s" % self.style_path) self.globals.set_popup_style("dbx.tar.gz") self.emit("popup-style-reloaded") return # Load settings try: config = tar.extractfile("style") except: logger.exception("Error extracting style from %s" % \ self.style_path) tar.close() self.globals.set_popup_style("dbx.tar.gz") self.emit("popup-style-reloaded") return self.settings = {} for line in config.readlines(): # Split at "=" and clean up the key and value if not "=" in line: continue key, value = line.split("=", 1) key = key.strip().lstrip().lower() value = value.strip().lstrip() # Remove comments if "#" in key: continue # If there is a trailing comment, remove it # But avoid removing # if it's in a quote sharp = value.find("#") if sharp != -1 and value.count("\"", 0, sharp) % 2 == 0 and \ value.count("'", 0, sharp) % 2 == 0: value = value.split("#", 1)[0].strip() # Remove quote signs if value[0] in ("\"", "'") and value[-1] in ("\"", "'"): value = value[1:-1] if key == "name": name = value continue value = value.lower() self.settings[key] = value config.close() if name: self.name = name else: self.settings = {"border_color2": "#000000", "menu_item_lr_padding": 3} self.globals.set_popup_style("dbx.tar.gz") self.emit("popup-style-reloaded") tar.close() return # Load background if "background.png" in tar.getnames(): bgf = tar.extractfile("background.png") self.bg = cairo.ImageSurface.create_from_png(bgf) bgf.close() if "closebutton/normal.png" in tar.getnames(): cbf = tar.extractfile("closebutton/normal.png") self.cb_normal_pic = cairo.ImageSurface.create_from_png(cbf) cbf.close() if "closebutton/pressed.png" in tar.getnames(): cbf = tar.extractfile("closebutton/pressed.png") self.cb_pressed_pic = cairo.ImageSurface.create_from_png(cbf) cbf.close() if "closebutton/hover.png" in tar.getnames(): cbf = tar.extractfile("closebutton/hover.png") self.cb_hover_pic = cairo.ImageSurface.create_from_png(cbf) cbf.close() tar.close() # Inform rest of dockbar about the reload. self.globals.set_popup_style(self.style_path.rsplit("/", 1)[-1]) self.emit("popup-style-reloaded") def get_styles(self, theme_name=None): # For DockbarX preference. This function makes a dict of the names and # file names of the styles for all styles that can be opened correctly. styles = {} style_folder = os.path.join(get_app_homedir(), "themes", "popup_styles") dirs = ["/usr/share/dockbarx/themes/popup_styles", style_folder] for dir in dirs: if os.path.exists(dir) and os.path.isdir(dir): for f in os.listdir(dir): if f[-7:] == ".tar.gz": name, oft = self.check(dir+"/"+f) if oft: # The style is meant only for themes # mentioned in oft. if theme_name is None: continue oft = [t.strip().lstrip().lower() \ for t in oft.split(",")] if not theme_name.lower() in oft: continue if name: styles[name] = f # The default style (if the theme doesn't set another one) is DBX, # wheter or not the file actually exists. if not "DBX" in styles: styles["DBX"] = "dbx.tar.gz" return styles def check(self, style_path): try: tar = taropen(style_path) except: return None try: config = tar.extractfile("style") except: tar.close() return None name = None oft = None for line in config.readlines(): # Split at "=" and clean up the key and value if not "=" in line: continue key, value = line.split("=", 1) key = key.strip().lstrip().lower() value = value.strip().lstrip() # Remove comments if "#" in key: continue # If there is a trailing comment, remove it # But avoid removing # if it's in a quote sharp = value.find("#") if sharp != -1 and value.count("\"", 0, sharp) % 2 == 0 and \ value.count("'", 0, sharp) % 2 == 0: value = value.split("#", 1)[0].strip() # Remove quote signs if value[0] in ("\"", "'") and value[-1] in ("\"", "'"): value = value[1:-1] if key == "only_for_themes": oft = value if key == "name": name = value tar.close() return name, oft
class Theme(gobject.GObject): __gsignals__ = { "theme_reloaded": (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,()), } def __new__(cls, *p, **k): if not "_the_instance" in cls.__dict__: cls._the_instance = gobject.GObject.__new__(cls) return cls._the_instance def __init__(self): if "theme" in self.__dict__: # This is not the first instance of Theme, # no need to initiate anything return gobject.GObject.__init__(self) self.globals = Globals() self.globals.connect("theme_changed", self.on_theme_changed) self.on_theme_changed() def on_theme_changed(self, arg=None): self.themes = self.find_themes() default_theme_path = None for theme, path in self.themes.items(): if theme.lower() == self.globals.settings["theme"].lower(): self.theme_path = path break if theme.lower() == self.globals.DEFAULT_SETTINGS["theme"].lower(): default_theme_path = path else: if default_theme_path: # If the current theme according to gconf couldn't be found, # the default theme is used. self.theme_path = default_theme_path else: # Just use one of the themes that where found if default # theme couldn't be found either. self.theme_path = self.themes.values()[0] self.reload() def find_themes(self): # Reads the themes from /usr/share/dockbarx/themes and # ${XDG_DATA_HOME:-$HOME/.local/share}/dockbarx/themes # and returns a dict of the theme names and paths so # that a theme can be loaded. themes = {} theme_paths = [] theme_folder = os.path.join(get_app_homedir(), "themes") dirs = ["/usr/share/dockbarx/themes", theme_folder] for dir in dirs: if os.path.exists(dir) and os.path.isdir(dir): for f in os.listdir(dir): if f[-7:] == ".tar.gz": theme_paths.append(dir+"/"+f) for theme_path in theme_paths: try: name = self.check(theme_path) except Exception, detail: logger.exception("Error loading theme from %s"%theme_path) name = None if name is not None: name = str(name) themes[name] = theme_path if not themes: md = gtk.MessageDialog(None, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, gtk.MESSAGE_ERROR, gtk.BUTTONS_CLOSE, _("No working themes found in /usr/share/dockbarx/themes or ${XDG_DATA_HOME:-$HOME/.local/share}/dockbarx/themes")) md.run() md.destroy() raise NoThemesError("No working themes found in " + \ "/usr/share/dockbarx/themes or ${XDG_DATA_HOME:-$HOME/.local/share}/dockbarx/themes") return themes
class WindowButton(gobject.GObject): __gsignals__ = {"minimized": (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,()), "unminimized": (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,()), "needs-attention-changed": (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,()), "popup-hide": (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,(str, )), "popup-hide-request": (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,()), "popup-expose-request": (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,()), "monitor-changed": (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,())} def __init__(self, window): gobject.GObject.__init__(self) self.globals = Globals() self.globals.connect('color-changed', self.update_label_state) self.globals.connect('show-only-current-monitor-changed', self.on_show_only_current_monitor_changed) self.globals.connect('show-previews-changed', self.on_show_preview_changed) self.screen = wnck.screen_get_default() self.name = window.get_name() self.window = window self.is_active_window = False self.needs_attention = False self.opacified = False self.button_pressed = False self.window_button = CairoWindowButton() self.label = gtk.Label() self.label.set_alignment(0, 0.5) self.on_window_name_changed(self.window) if window.needs_attention(): self.needs_attention = True self.window_button_icon = gtk.Image() self.on_window_icon_changed(window) self.preview_image = None self.on_show_preview_changed() self.update_label_state() #--- Events self.window_button.connect("enter-notify-event", self.on_button_mouse_enter) self.window_button.connect("leave-notify-event", self.on_button_mouse_leave) self.window_button.connect("button-press-event", self.on_window_button_press_event) self.window_button.connect("button-release-event", self.on_window_button_release_event) self.window_button.connect("scroll-event", self.on_window_button_scroll_event) self.state_changed_event = self.window.connect("state-changed", self.on_window_state_changed) self.icon_changed_event = self.window.connect("icon-changed", self.on_window_icon_changed) self.name_changed_event = self.window.connect("name-changed", self.on_window_name_changed) self.geometry_changed_event = None self.on_show_only_current_monitor_changed() #--- D'n'D self.window_button.drag_dest_set(gtk.DEST_DEFAULT_HIGHLIGHT, [], 0) self.window_button.connect("drag_motion", self.on_button_drag_motion) self.window_button.connect("drag_leave", self.on_button_drag_leave) self.button_drag_entered = False self.dnd_select_window = None def set_button_active(self, mode): self.is_active_window = mode self.update_label_state() def update_label_state(self, arg=None): """Updates the style of the label according to window state.""" attr_list = pango.AttrList() if self.needs_attention: attr_list.insert(pango.AttrStyle(pango.STYLE_ITALIC, 0, 200)) if self.is_active_window: color = self.globals.colors['color3'] elif self.window.is_minimized(): color = self.globals.colors['color4'] else: color = self.globals.colors['color2'] # color is a hex-string (like '#FFFFFF'). r = int(color[1:3], 16)*256 g = int(color[3:5], 16)*256 b = int(color[5:7], 16)*256 attr_list.insert(pango.AttrForeground(r, g, b, 0, 200)) self.label.set_attributes(attr_list) def is_on_current_desktop(self): if (self.window.get_workspace() is None \ or self.screen.get_active_workspace() == self.window.get_workspace()) \ and self.window.is_in_viewport(self.screen.get_active_workspace()): return True else: return False def get_monitor(self): if not self.globals.settings['show_only_current_monitor']: return 0 gdk_screen = gtk.gdk.screen_get_default() win = gtk.gdk.window_lookup(self.window.get_xid()) x, y, w, h, bit_depth = win.get_geometry() return gdk_screen.get_monitor_at_point(x + (w / 2), y + (h / 2)) def on_show_only_current_monitor_changed(self, arg=None): if self.globals.settings['show_only_current_monitor']: if self.geometry_changed_event is None: self.geometry_changed_event = self.window.connect( 'geometry-changed', self.on_geometry_changed) else: if self.geometry_changed_event is not None: self.window.disconnect(self.geometry_changed_event) self.monitor = self.get_monitor() def del_button(self): self.window_button.destroy() self.window.disconnect(self.state_changed_event) self.window.disconnect(self.icon_changed_event) self.window.disconnect(self.name_changed_event) del self.icon del self.icon_transp del self.screen del self.window del self.globals gc.collect() #### Previews def prepare_preview(self, size=None): if not self.globals.settings["preview"]: return False if size is None: size = self.globals.settings["preview_size"] pixbuf = self.window.get_icon() self.preview_image.set_from_pixbuf(pixbuf) self.preview_image.set_size_request(size,size) del pixbuf gc.collect() def get_preview_alloc(self, size): x,y,w,h = self.window.get_geometry() w = float(w) h = float(h) size = float(size) if w>h: h = size/(w/h) w = size x = 0 y = (size - h)//2 else: w = size/(h/w) h = size x = (size - w)//2 y = 0 return (x, y, w, h) def clear_preview_image(self): if self.preview_image: self.preview_image.clear() gc.collect() def on_show_preview_changed(self, arg=None): child = self.window_button.get_child() if child: self.window_button.remove(child) oldbox = self.window_button_icon.get_parent() if oldbox: oldbox.remove(self.window_button_icon) oldbox.remove(self.label) self.on_window_name_changed(self.window) hbox = gtk.HBox() hbox.pack_start(self.window_button_icon, False, padding = 2) hbox.pack_start(self.label, True, True) if self.globals.settings["preview"]: vbox = gtk.VBox() vbox.pack_start(hbox, False) self.preview_image = gtk.Image() vbox.pack_start(self.preview_image, True, True, padding = 2) self.window_button.add(vbox) # Fixed with of self.label. self.label.set_ellipsize(pango.ELLIPSIZE_END) else: self.label.set_ellipsize(pango.ELLIPSIZE_NONE) self.window_button.add(hbox) if self.preview_image: self.preview_image.clear() self.preview_image = None gc.collect() #### Windows's Events def on_window_state_changed(self, window,changed_mask, new_state): try: state_minimized = wnck.WINDOW_STATE_MINIMIZED except: state_minimized = 1 << 0 if state_minimized & changed_mask & new_state: self.window_button_icon.set_from_pixbuf(self.icon_transp) self.update_label_state() self.emit('minimized') elif state_minimized & changed_mask: self.window_button_icon.set_from_pixbuf(self.icon) self.update_label_state() self.emit('unminimized') # Check if the window needs attention if window.needs_attention() != self.needs_attention: self.needs_attention = window.needs_attention() self.update_label_state() self.emit('needs-attention-changed') def on_window_icon_changed(self, window): # Creates pixbufs for minimized and normal icons # from the window's mini icon and set the one that should # be used as window_button_icon according to window state. self.icon = window.get_mini_icon() pixbuf = self.icon.copy() self.icon_transp = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, True, 8, pixbuf.get_width(), pixbuf.get_height()) self.icon_transp.fill(0x00000000) pixbuf.composite(self.icon_transp, 0, 0, pixbuf.get_width(), pixbuf.get_height(), 0, 0, 1, 1, gtk.gdk.INTERP_BILINEAR, 190) self.icon_transp.saturate_and_pixelate(self.icon_transp, 0.12, False) if window.is_minimized(): self.window_button_icon.set_from_pixbuf(self.icon_transp) else: self.window_button_icon.set_from_pixbuf(self.icon) del pixbuf def on_window_name_changed(self, window): name = u""+window.get_name() # TODO: fix a better way to shorten names. if len(name) > 40 and not self.globals.settings["preview"]: name = name[0:37]+"..." self.name = name self.label.set_label(name) def on_geometry_changed(self, *args): monitor = self.get_monitor() if monitor != self.monitor: self.monitor = monitor self.emit('monitor-changed') #### Opacify def opacify(self): # Makes all windows but the one connected # to this windowbutton transparent. if self.globals.opacity_values is None: try: self.globals.opacity_values = compiz_call( 'obs/screen0/opacity_values','get') except: try: self.globals.opacity_values = compiz_call( 'core/screen0/opacity_values','get') except: return if self.globals.opacity_matches is None: try: self.globals.opacity_matches = compiz_call( 'obs/screen0/opacity_matches','get') except: try: self.globals.opacity_matches = compiz_call( 'core/screen0/opacity_matches','get') except: return self.globals.opacified = True self.opacified = True ov = [self.globals.settings['opacify_alpha']] om = ["!(xid=%s)"%self.window.get_xid() + \ " & !(class=Dockbarx_factory.py) & (type=Normal | type=Dialog)"] try: compiz_call('obs/screen0/opacity_values','set', ov) compiz_call('obs/screen0/opacity_matches','set', om) except: try: compiz_call('core/screen0/opacity_values','set', ov) compiz_call('core/screen0/opacity_matches','set', om) except: return def opacify_request(self): if self.window.is_minimized(): return False # if self.button_pressed is true, opacity_request is called by an # wrongly sent out enter_notification_event sent after a # button_press (because of a bug in compiz). if self.button_pressed: self.button_pressed = False return False # Check if mouse cursor still is over the window button. b_m_x,b_m_y = self.window_button.get_pointer() b_r = self.window_button.get_allocation() if b_m_x >= 0 and b_m_x < b_r.width \ and b_m_y >= 0 and b_m_y < b_r.height: self.opacify() return False def deopacify(self): # always called from deopacify_request (with timeout) # If another window button has called opacify, don't deopacify. if self.globals.opacified and not self.opacified: return False if self.globals.opacity_values is None: return False try: compiz_call('obs/screen0/opacity_values','set', self.globals.opacity_values) compiz_call('obs/screen0/opacity_matches','set', self.globals.opacity_matches) except: try: compiz_call('core/screen0/opacity_values','set', self.globals.opacity_values) compiz_call('core/screen0/opacity_matches','set', self.globals.opacity_matches) except: print "Error: Couldn't set opacity back to normal." self.globals.opacity_values = None self.globals.opacity_matches = None return False def deopacify_request(self): if not self.opacified: return False # Make sure that mouse cursor really has left the window button. b_m_x,b_m_y = self.window_button.get_pointer() b_r = self.window_button.get_allocation() if b_m_x >= 0 and b_m_x < b_r.width \ and b_m_y >= 0 and b_m_y < b_r.height: return True self.globals.opacified = False self.opacified = False # Wait before deopacifying in case a new windowbutton # should call opacify, to avoid flickering gobject.timeout_add(110, self.deopacify) return False #### D'n'D def on_button_drag_motion(self, widget, drag_context, x, y, t): if not self.button_drag_entered: self.emit('popup-expose-request') self.button_drag_entered = True self.dnd_select_window = \ gobject.timeout_add(600,self.action_select_window) drag_context.drag_status(gtk.gdk.ACTION_PRIVATE, t) return True def on_button_drag_leave(self, widget, drag_context, t): self.button_drag_entered = False gobject.source_remove(self.dnd_select_window) self.emit('popup-expose-request') self.emit('popup-hide-request') #### Events def on_button_mouse_enter(self, widget, event): # In compiz there is a enter and # a leave event before a button_press event. # Keep that in mind when coding this def! if self.button_pressed : return self.update_label_state(True) if self.globals.settings["opacify"]: gobject.timeout_add(100,self.opacify_request) # Just for safty in case no leave-signal is sent gobject.timeout_add(500, self.deopacify_request) def on_button_mouse_leave(self, widget, event): # In compiz there is a enter and a leave # event before a button_press event. # Keep that in mind when coding this def! self.button_pressed = False self.update_label_state(False) if self.globals.settings["opacify"]: self.deopacify_request() def on_window_button_press_event(self, widget,event): # In compiz there is a enter and a leave event before # a button_press event. # self.button_pressed is used to stop functions started with # gobject.timeout_add from self.on_button_mouse_enter # or self.on_button_mouse_leave. self.button_pressed = True gobject.timeout_add(600, self.set_button_pressed_false) def set_button_pressed_false(self): # Helper function for on_window_button_press_event. self.button_pressed = False return False def on_window_button_scroll_event(self, widget, event): if self.globals.settings["opacify"] and self.opacified: self.globals.opacified = False self.opacified = False self.deopacify() if not event.direction in (gtk.gdk.SCROLL_UP, gtk.gdk.SCROLL_DOWN): return direction = {gtk.gdk.SCROLL_UP: 'scroll_up', gtk.gdk.SCROLL_DOWN: 'scroll_down'}[event.direction] action = self.globals.settings['windowbutton_%s'%direction] self.action_function_dict[action](self, widget, event) if self.globals.settings['windowbutton_close_popup_on_%s'%direction]: self.emit('popup-hide', None) def on_window_button_release_event(self, widget, event): if self.globals.settings["opacify"] and self.opacified: self.globals.opacified = False self.opacified = False self.deopacify() if not event.button in (1, 2, 3): return button = {1:'left', 2: 'middle', 3: 'right'}[event.button] if event.state & gtk.gdk.SHIFT_MASK: mod = 'shift_and_' else: mod = '' action_str = 'windowbutton_%s%s_click_action'%(mod, button) action = self.globals.settings[action_str] self.action_function_dict[action](self, widget, event) popup_close = 'windowbutton_close_popup_on_%s%s_click'%(mod, button) if self.globals.settings[popup_close]: self.emit('popup-hide', None) #### Menu functions def menu_closed(self, menushell): self.globals.right_menu_showing = False self.emit('popup-hide', 'menu-closed') def minimize_window(self, widget=None, event=None): if self.window.is_minimized(): self.window.unminimize(gtk.get_current_event_time()) else: self.window.minimize() #### Actions def action_select_or_minimize_window(self, widget=None, event=None, minimize=True): # The window is activated, unless it is already # activated, then it's minimized. Minimized # windows are unminimized. The workspace # is switched if the window is on another # workspace. if event: t = event.time else: t = gtk.get_current_event_time() if self.window.get_workspace() is not None \ and self.screen.get_active_workspace() != self.window.get_workspace(): self.window.get_workspace().activate(t) if not self.window.is_in_viewport(self.screen.get_active_workspace()): win_x,win_y,win_w,win_h = self.window.get_geometry() self.screen.move_viewport(win_x-(win_x%self.screen.get_width()), win_y-(win_y%self.screen.get_height())) # Hide popup since mouse movment won't # be tracked during compiz move effect # which means popup list can be left open. self.emit('popup-hide', 'viewport-change') if self.window.is_minimized(): self.window.unminimize(t) elif self.window.is_active() and minimize: self.window.minimize() else: self.window.activate(t) def action_select_window(self, widget = None, event = None): self.action_select_or_minimize_window(widget, event, False) def action_close_window(self, widget=None, event=None): self.window.close(gtk.get_current_event_time()) def action_maximize_window(self, widget=None, event=None): if self.window.is_maximized(): self.window.unmaximize() else: self.window.maximize() def action_shade_window(self, widget, event): self.window.shade() def action_unshade_window(self, widget, event): self.window.unshade() def action_show_menu(self, widget, event): try: action_minimize = wnck.WINDOW_ACTION_MINIMIZE action_unminimize = wnck.WINDOW_ACTION_UNMINIMIZE action_maximize = wnck.WINDOW_ACTION_MAXIMIZE except: action_minimize = 1 << 12 action_unminimize = 1 << 13 action_maximize = 1 << 14 #Creates a popup menu menu = gtk.Menu() menu.connect('selection-done', self.menu_closed) #(Un)Minimize minimize_item = None if self.window.get_actions() & action_minimize \ and not self.window.is_minimized(): minimize_item = gtk.MenuItem(_('_Minimize')) elif self.window.get_actions() & action_unminimize \ and self.window.is_minimized(): minimize_item = gtk.MenuItem(_('Un_minimize')) if minimize_item: menu.append(minimize_item) minimize_item.connect("activate", self.minimize_window) minimize_item.show() # (Un)Maximize maximize_item = None if not self.window.is_maximized() \ and self.window.get_actions() & action_maximize: maximize_item = gtk.MenuItem(_('Ma_ximize')) elif self.window.is_maximized() \ and self.window.get_actions() & action_unminimize: maximize_item = gtk.MenuItem(_('Unma_ximize')) if maximize_item: menu.append(maximize_item) maximize_item.connect("activate", self.action_maximize_window) maximize_item.show() # Close close_item = gtk.MenuItem(_('_Close')) menu.append(close_item) close_item.connect("activate", self.action_close_window) close_item.show() menu.popup(None, None, None, event.button, event.time) self.globals.right_menu_showing = True def action_none(self, widget = None, event = None): pass action_function_dict = ODict(( ('select or minimize window', action_select_or_minimize_window), ('select window', action_select_window), ('maximize window', action_maximize_window), ('close window', action_close_window), ('show menu', action_show_menu), ('shade window', action_shade_window), ('unshade window', action_unshade_window), ('no action', action_none) ))
class IconFactory(): """IconFactory takes care of finding the right icon for a program and prepares the cairo surface.""" icon_theme = gtk.icon_theme_get_default() # Constants # Icon types SOME_MINIMIZED = 1<<4 ALL_MINIMIZED = 1<<5 LAUNCHER = 1<<6 # Icon effects MOUSE_OVER = 1<<7 MOUSE_BUTTON_DOWN = 1<<8 NEEDS_ATTENTION = 1<<9 BLINK = 1<<10 # ACTIVE_WINDOW ACTIVE = 1<<11 LAUNCH_EFFECT = 1<<12 # Double width/height icons for drag and drop situations. DRAG_DROPP = 1<<13 TYPE_DICT = {'some_minimized':SOME_MINIMIZED, 'all_minimized':ALL_MINIMIZED, 'launcher':LAUNCHER, 'mouse_over':MOUSE_OVER, 'needs_attention':NEEDS_ATTENTION, 'blink':BLINK, 'active':ACTIVE, 'launching':LAUNCH_EFFECT, 'mouse_button_down':MOUSE_BUTTON_DOWN} def __init__(self, class_group=None, desktop_entry=None, identifier=None): self.theme = Theme() self.globals = Globals() self.globals.connect('color-changed', self.reset_surfaces) self.desktop_entry = desktop_entry self.identifier = identifier self.class_group = class_group # Setting size to something other than zero to # avoid crashes if surface_update() is runned # before the size is set. self.size = 15 self.icon = None self.surfaces = {} self.average_color = None self.max_win_nr = self.theme.get_windows_cnt() self.types_in_theme = 0 for type in self.theme.get_types(): if not type in self.TYPE_DICT: continue self.types_in_theme = self.types_in_theme | self.TYPE_DICT[type] def remove(self): del self.desktop_entry del self.class_group del self.icon del self.surfaces del self.theme def set_desktop_entry(self, desktop_entry): self.desktop_entry = desktop_entry self.surfaces = {} del self.icon self.icon = None def set_class_group(self, class_group): if not self.desktop_entry and not self.class_group: self.surfaces = {} del self.icon self.icon = None self.class_group = class_group def set_size(self, size): if size <= 0: # To avoid chrashes. size = 15 self.size = size self.surfaces = {} self.average_color = None def get_size(self): return self.size def reset_surfaces(self, arg=None): self.surfaces = {} self.average_color = None def surface_update(self, type = 0): # Checks if the requested pixbuf is already # drawn and returns it if it is. # Othervice the surface is drawn, saved and returned. #The first four bits of type is for telling the number of windows self.win_nr = min(type & 15, self.max_win_nr) # Remove all types that are not used by the theme (saves memory) dnd = type & self.DRAG_DROPP type = type & self.types_in_theme type += self.win_nr if type in self.surfaces: surface = self.surfaces[type] else: self.temp = {} surface = None commands = self.theme.get_icon_dict() self.ar = self.theme.get_aspect_ratio() self.type = type for command, args in commands.items(): try: f = getattr(self,"command_%s"%command) except: raise else: surface = f(surface, **args) # Todo: add size correction. self.surfaces[type] = surface del self.temp gc.collect() if dnd: surface = self.dd_highlight(surface, self.globals.orient) gc.collect() return surface def dd_highlight(self, surface, direction = 'h'): w = surface.get_width() h = surface.get_height() # Make a background almost twice as wide or high # as the surface depending on panel orientation. if direction == 'v': h = h + 4 else: w = w + 4 bg = cairo.ImageSurface(cairo.FORMAT_ARGB32, w, h) ctx = cairo.Context(bg) # Put arrow pointing to the empty part on it. if direction == 'v': ctx.move_to(1, h - 1.5) ctx.line_to(w - 1, h - 1.5) ctx.set_source_rgba(1, 1, 1, 0.2) ctx.set_line_width(2) ctx.stroke() ctx.move_to(2, h - 1.5) ctx.line_to(w - 2, h - 1.5) ctx.set_source_rgba(0, 0, 0, 0.7) ctx.set_line_width(1) ctx.stroke() else: ctx.move_to(w - 1.5, 1) ctx.line_to(w - 1.5, h - 1) ctx.set_source_rgba(1, 1, 1, 0.2) ctx.set_line_width(2) ctx.stroke() ctx.move_to(w - 1.5, 2) ctx.line_to(w - 1.5, h - 2) ctx.set_source_rgba(0, 0, 0, 0.7) ctx.set_line_width(1) ctx.stroke() # And put the surface on the left/upper half of it. ctx.set_source_surface(surface, 0, 0) ctx.paint() return bg def get_color(self, color): if color == "active_color": color = 'color5' if color in ['color%s'%i for i in range(1, 9)]: color = self.globals.colors[color] if color == "icon_average": color = self.get_average_color() else: try: if len(color) != 7: raise ValueError('The string has the wrong lenght') t = int(color[1:], 16) except: print "Theme error: the color attribute for a theme command"+ \ " should be a six digit hex string eg. \"#FFFFFF\" or"+ \ " the a dockbarx color (\"color1\"-\"color8\")." color = "#000000" return color def get_alpha(self, alpha): # Transparency if alpha == "active_opacity": # For backwards compability alpha = "color5" for i in range(1, 9): if alpha in ('color%s'%i, 'opacity%s'%i): if self.globals.colors.has_key('color%s_alpha'%i): a = float(self.globals.colors['color%s_alpha'%i])/255 else: print "Theme error: The theme has no" + \ " opacity option for color%s."%i a = 1.0 break else: try: a = float(alpha)/100 if a > 1.0 or a < 0: raise except: print 'Theme error: The opacity attribute of a theme ' + \ 'command should be a number between "0" ' + \ ' and "100" or "color1" to "color8".' a = 1.0 return a def get_average_color(self): if self.average_color is not None: return self.average_color r = 0 b = 0 g = 0 i = 0 pb = self.surface2pixbuf(self.icon) for row in pb.get_pixels_array(): for pix in row: if pix[3] > 30: i += 1 r += pix[0] g += pix[1] b += pix[2] if i > 0: r = int(float(r) / i + 0.5) g = int(float(g) / i + 0.5) b = int(float(b) / i + 0.5) r = ("0%s"%hex(r)[2:])[-2:] g = ("0%s"%hex(g)[2:])[-2:] b = ("0%s"%hex(b)[2:])[-2:] self.average_color = "#"+r+g+b del pb return self.average_color #### Flow commands def command_if(self, surface, type=None, windows=None, size=None, content=None): if content is None: return surface # TODO: complete this ## l = [] ## splits = ['!', '(', ')', '&', '|'] ## for c in type: ## if c in splits: ## l.append(c) ## elif l[-1] in splits: ## l.append(c) ## elif not l: ## l.append(c) ## else: ## l[-1] += c # Check if the type condition is satisfied if type is not None: negation = False if type[0] == "!" : type = type[1:] negation = True is_type = bool(type in self.TYPE_DICT \ and self.type & self.TYPE_DICT[type]) if not (is_type ^ negation): return surface #Check if the window number condition is satisfied if windows is not None: arg = windows negation = False if arg[0] == "!" : arg = windows[1:] negation = True if arg[0] == ":": arg = "0" + arg elif arg[-1] == ":": arg = arg +"15" l = arg.split(":", 1) try: l = [int(n) for n in l] except ValueError: print 'Theme Error: The windows attribute of ' + \ 'an <if> statement can\'t look like this:' + \ ' "%s". See Theming HOWTO for more information'%windows return surface if len(l) == 1: if not ((l[0] == self.win_nr) ^ negation): return surface else: if not ((l[0]<=self.win_nr and self.win_nr<=l[1]) ^ negation): return surface #Check if the icon size condition is satisfied if size is not None: arg = size negation = False if arg[0] == "!" : arg = size[1:] negation = True if arg[0] == ":": arg = "0" + arg elif arg[-1] == ":": arg = arg +"200" l = arg.split(":", 1) try: l = [int(n) for n in l] except ValueError: print 'Theme Error: The size attribute of ' + \ 'an <if> statement can\'t look like this:' + \ ' "%s". See Theming HOWTO for more information'%size return surface if len(l) == 1: if not ((l[0] == self.win_nr) ^ negation): return surface else: if not ((l[0]<=self.size and self.size<=l[1]) ^ negation): return surface # All tests passed, proceed. for command, args in content.items(): try: f = getattr(self,"command_%s"%command) except: raise else: surface = f(surface, **args) return surface def command_pixmap_from_self(self, surface, name, content=None): if not name: print "Theme Error: no name given for pixmap_from_self" raise Exeption w = int(surface.get_width()) h = int(surface.get_height()) self.temp[name] = cairo.ImageSurface(cairo.FORMAT_ARGB32, w, h) ctx = cairo.Context(self.temp[name]) ctx.set_source_surface(surface) ctx.paint() if content is None: return surface for command,args in content.items(): try: f = getattr(self,"command_%s"%command) except: raise else: self.temp[name] = f(self.temp[name], **args) return surface def command_pixmap(self, surface, name, content=None, size=None): if size is not None: # TODO: Fix for different height and width w = h = self.size + int(size) elif surface is None: w = h = self.size else: w = surface.get_width() h = surface.get_height() self.temp[name] = cairo.ImageSurface(cairo.FORMAT_ARGB32, w, h) if content is None: return surface for command,args in content.items(): try: f = getattr(self,"command_%s"%command) except: raise else: self.temp[name] = f(self.temp[name], **args) return surface #### Get icon def command_get_icon(self,surface=None, size=0): size = int(size) size = self.size + size if size <= 0: # To avoid chrashes. size = 15 if self.icon \ and self.icon.get_width() == size \ and self.icon.get_height() == size: return self.icon del self.icon self.icon = None pb = self.find_icon_pixbuf(size) if pb.get_width() != pb.get_height(): if pb.get_width() < pb.get_height(): h = size w = pb.get_width() * size/pb.get_height() elif pb.get_width() > pb.get_height(): w = size h = pb.get_height() * size/pb.get_width() self.icon = cairo.ImageSurface(cairo.FORMAT_ARGB32, size, size) ctx = gtk.gdk.CairoContext(cairo.Context(self.icon)) pbs = pb.scale_simple(w, h, gtk.gdk.INTERP_BILINEAR) woffset = int(float(size - w) / 2 + 0.5) hoffset = int(float(size - h) / 2 + 0.5) ctx.set_source_pixbuf(pb, woffset, hoffset) ctx.paint() del pb del pbs elif pb.get_width() != size: pbs = pb.scale_simple(size, size, gtk.gdk.INTERP_BILINEAR) self.icon = self.pixbuf2surface(pbs) del pb del pbs else: self.icon = self.pixbuf2surface(pb) del pb return self.icon def find_icon_pixbuf(self, size): # Returns the icon pixbuf for the program. Uses the following metods: # 1) If it is a launcher, return the icon from the # launcher's desktopfile # 2) Get the icon from the gio app # 3) Check if the res_class fits an themed icon. # 4) Search in path after a icon matching reclass. # 5) Use the mini icon for the class pixbuf = None icon_name = None if self.desktop_entry: icon_name = self.desktop_entry.getIcon() if os.path.isfile(icon_name): pixbuf = self.icon_from_file_name(icon_name, size) if pixbuf is not None: return pixbuf if not icon_name: if self.identifier: icon_name = self.identifier.lower() elif self.class_group: icon_name = self.class_group.get_res_class().lower() else: icon_name = "" # Special cases if icon_name.startswith('openoffice'): # Makes sure openoffice gets a themed icon icon_name = "ooo-writer" if self.icon_theme.has_icon(icon_name): return self.icon_theme.load_icon(icon_name,size,0) if icon_name[-4:] in (".svg", ".png", ".xpm"): if self.icon_theme.has_icon(icon_name[:-4]): pixbuf = self.icon_theme.load_icon(icon_name[:-4],size,0) if pixbuf is not None: return pixbuf pixbuf = self.icon_search_in_data_path(icon_name, size) if pixbuf is not None: return pixbuf if self.class_group: return self.class_group.get_icon().copy() # If no pixbuf has been found (can only happen for an unlaunched # launcher), make an empty pixbuf and show a warning. if self.icon_theme.has_icon('application-default-icon'): pixbuf = self.icon_theme.load_icon('application-default-icon', size, 0) else: pixbuf = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, True, 8, size,size) pixbuf.fill(0x00000000) if self.desktop_entry: name = self.desktop_entry.getName() else: name = None ## dialog = gtk.MessageDialog( ## parent=None, ## flags=gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, ## type=gtk.MESSAGE_WARNING, ## buttons=gtk.BUTTONS_OK, ## message_format= (_("Cannot load icon for %s")%name)) ## dialog.set_title('DockBarX') ## dialog.run() ## dialog.destroy() return pixbuf def icon_from_file_name(self, icon_name, icon_size = -1): if os.path.isfile(icon_name): try: return gtk.gdk.pixbuf_new_from_file_at_size(icon_name, -1, icon_size) except: pass return None def icon_search_in_data_path(self, icon_name, icon_size): data_folders = None if os.environ.has_key("XDG_DATA_DIRS"): data_folders = os.environ["XDG_DATA_DIRS"] if not data_folders: data_folders = "/usr/local/share/:/usr/share/" for data_folder in data_folders.split(':'): #The line below line used datafolders instead of datafolder. #I changed it because I suspect it was a bug. paths = (os.path.join(data_folder, "pixmaps", icon_name), os.path.join(data_folder, "icons", icon_name)) for path in paths: if os.path.isfile(path): icon = self.icon_from_file_name(path, icon_size) if icon: return icon return None #### Other commands def command_get_pixmap(self, surface, name, size=0): if surface is None: if self.globals.orient == 'h': width = int(self.size * ar) height = self.size else: width = self.size height = int(self.size * ar) else: width = surface.get_width() height = surface.get_height() if self.theme.has_surface(name): surface = self.resize_surface(self.theme.get_surface(name), width, height) else: print "theme error: pixmap %s not found"%name return surface def command_fill(self, surface, color, opacity=100): w = surface.get_width() h = surface.get_height() new = cairo.ImageSurface(cairo.FORMAT_ARGB32, w, h) ctx = cairo.Context(new) ctx.set_source_surface(surface) ctx.paint() alpha = self.get_alpha(opacity) c = self.get_color(color) r = float(int(c[1:3], 16))/255 g = float(int(c[3:5], 16))/255 b = float(int(c[5:7], 16))/255 ctx.set_source_rgba(r, g, b) ctx.set_operator(cairo.OPERATOR_SOURCE) ctx.paint_with_alpha(alpha) return new def command_combine(self, surface, pix1, pix2, degrees=90): # Combines left half of surface with right half of surface2. # The transition between the two halves are soft. w = surface.get_width() h = surface.get_height() if pix1=="self": p1 = surface elif pix1 in self.temp: p1 = self.temp[pix1] elif self.theme.has_surface(pix1): w = surface.get_width() h = surface.get_height() p1 = self.resize_surface(self.theme.get_surface(bg), w, h) else: print "theme error: pixmap %s not found"%pix1 if pix2=="self": p2 = surface elif pix2 in self.temp: p2 = self.temp[pix2] elif self.theme.has_surface(pix2): w = surface.get_width() h = surface.get_height() p2 = self.resize_surface(self.theme.get_surface(bg), w, h) else: print "theme error: pixmap %s not found"%pix2 surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, p1.get_width(), p1.get_height()) ctx = cairo.Context(surface) linear = cairo.LinearGradient(0, 0, p1.get_width(), 0) linear.add_color_stop_rgba(0.4, 0, 0, 0, 0.5) linear.add_color_stop_rgba(0.6, 0, 0, 0, 1) ctx.set_source_surface(p2, 0, 0) #ctx.mask(linear) ctx.paint() linear = cairo.LinearGradient(0, 0, p1.get_width(), 0) linear.add_color_stop_rgba(0.4, 0, 0, 0, 1) linear.add_color_stop_rgba(0.6, 0, 0, 0, 0) ctx.set_source_surface(p1, 0, 0) ctx.mask(linear) try: del pb del pbs except: pass return surface def command_transp_sat(self, surface, opacity=100, saturation=100): # Makes the icon desaturized and/or transparent. w = surface.get_width() h = surface.get_height() new = cairo.ImageSurface(cairo.FORMAT_ARGB32, w, h) ctx = gtk.gdk.CairoContext(cairo.Context(new)) alpha = self.get_alpha(opacity) # Todo: Add error check for saturation if int(saturation) < 100: sio = StringIO() surface.write_to_png(sio) sio.seek(0) loader = gtk.gdk.PixbufLoader() loader.write(sio.getvalue()) loader.close() sio.close() pixbuf = loader.get_pixbuf() saturation = min(1.0, float(saturation)/100) pixbuf.saturate_and_pixelate(pixbuf, saturation, False) ctx.set_source_pixbuf(pixbuf, 0, 0) ctx.paint_with_alpha(alpha) del loader del sio del pixbuf gc.collect() else: ctx.set_source_surface(surface) ctx.paint_with_alpha(alpha) return new def command_composite(self, surface, bg, fg, opacity=100, xoffset=0, yoffset=0): if fg=="self": foreground = surface elif fg in self.temp: foreground = self.temp[fg] elif self.theme.has_surface(fg): w = surface.get_width() h = surface.get_height() foreground = self.resize_surface(self.theme.get_surface(fg), w, h) else: print "theme error: pixmap %s not found"%fg return surface if bg=="self": background = surface elif bg in self.temp: w = self.temp[bg].get_width() h = self.temp[bg].get_height() background = cairo.ImageSurface(cairo.FORMAT_ARGB32, w, h) ctx = cairo.Context(background) ctx.set_source_surface(self.temp[bg]) ctx.paint() elif self.theme.has_surface(bg): w = surface.get_width() h = surface.get_height() background = self.resize_surface(self.theme.get_surface(bg), w, h) else: print "theme error: pixmap %s not found"%bg return surface opacity = self.get_alpha(opacity) xoffset = float(xoffset) yoffset = float(yoffset) ctx = cairo.Context(background) ctx.set_source_surface(foreground, xoffset, yoffset) ctx.paint_with_alpha(opacity) return background def command_shrink(self, surface, percent=0, pixels=0): w0 = surface.get_width() h0 = surface.get_height() new = cairo.ImageSurface(cairo.FORMAT_ARGB32, w0, h0) ctx = cairo.Context(new) w = int(((100-int(percent)) * w0)/100)-int(pixels) h = int(((100-int(percent)) * h0)/100)-int(pixels) shrinked = self.resize_surface(surface, w, h) x = int(float(w0 - w) / 2 + 0.5) y = int(float(h0 - h) / 2 + 0.5) ctx.set_source_surface(shrinked, x, y) ctx.paint() del shrinked return new def command_correct_size(self, surface): if surface is None: return if self.globals.orient == 'v': width = self.size height = int(self.size * self.ar) else: width = int(self.size * self.ar) height = self.size if surface.get_width() == width and surface.get_height() == height: return surface woffset = int(float(width - surface.get_width()) / 2 + 0.5) hoffset = int(float(height - surface.get_height()) / 2 + 0.5) new = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height) ctx = cairo.Context(new) ctx.set_source_surface(surface, woffset, hoffset) ctx.paint() return new def command_glow(self, surface, color, opacity=100): # Adds a glow around the parts of the surface # that isn't completely transparent. alpha = self.get_alpha(opacity) # Thickness (pixels) tk = 1.5 # Prepare the glow that should be put behind the icon cs = self.command_colorize(surface, color) w = surface.get_width() h = surface.get_height() glow = cairo.ImageSurface(cairo.FORMAT_ARGB32, w, h) ctx = cairo.Context(glow) tk1 = tk/2.0 for x, y in ((-tk1,-tk1), (-tk1,tk1), (tk1,-tk1), (tk1,tk1)): ctx.set_source_surface(cs, x, y) ctx.paint_with_alpha(0.66) for x, y in ((-tk,-tk), (-tk,tk), (tk,-tk), (tk,tk)): ctx.set_source_surface(cs, x, y) ctx.paint_with_alpha(0.27) # Add glow and icon to a new canvas new = cairo.ImageSurface(cairo.FORMAT_ARGB32, w, h) ctx = cairo.Context(new) ctx.set_source_surface(glow) ctx.paint_with_alpha(alpha) ctx.set_source_surface(surface) ctx.paint() return new def command_colorize(self, surface, color): # Changes the color of all pixels to color. # The pixels alpha values are unchanged. # Convert color hex-string (format '#FFFFFF')to int r, g, b color = self.get_color(color) r = int(color[1:3], 16)/255.0 g = int(color[3:5], 16)/255.0 b = int(color[5:7], 16)/255.0 w = surface.get_width() h = surface.get_height() new = cairo.ImageSurface(cairo.FORMAT_ARGB32, w, h) ctx = cairo.Context(new) ctx.set_source_rgba(r,g,b,1.0) ctx.mask_surface(surface) return new def command_bright(self, surface, strength = None, strenght = None): if strength is None and strenght is not None: # For compability with older themes. strength = strenght alpha = self.get_alpha(strength) w = surface.get_width() h = surface.get_height() # Colorize white white = cairo.ImageSurface(cairo.FORMAT_ARGB32, w, h) ctx = cairo.Context(white) ctx.set_source_rgba(1.0, 1.0, 1.0, 1.0) ctx.mask_surface(surface) # Apply the white version over the icon # with the chosen alpha value new = cairo.ImageSurface(cairo.FORMAT_ARGB32, w, h) ctx = cairo.Context(new) ctx.set_source_surface(surface) ctx.paint() ctx.set_source_surface(white) ctx.paint_with_alpha(alpha) return new def command_alpha_mask(self, surface, mask): if mask in self.temp: mask = self.temp[mask] elif self.theme.has_surface(mask): m = self.surface2pixbuf(self.theme.get_surface(mask)) m = m.scale_simple(surface.get_width(), surface.get_height(), gtk.gdk.INTERP_BILINEAR) mask = self.pixbuf2surface(m) w = surface.get_width() h = surface.get_height() new = cairo.ImageSurface(cairo.FORMAT_ARGB32, w, h) ctx = cairo.Context(new) ctx.set_source_surface(surface) ctx.mask_surface(mask) return new #### Format conversions def pixbuf2surface(self, pixbuf): if pixbuf is None: return None w = pixbuf.get_width() h = pixbuf.get_height() surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, w, h) ctx = gtk.gdk.CairoContext(cairo.Context(surface)) ctx.set_source_pixbuf(pixbuf, 0, 0) ctx.paint() del pixbuf return surface def surface2pixbuf(self, surface): if surface is None: return None sio = StringIO() surface.write_to_png(sio) sio.seek(0) loader = gtk.gdk.PixbufLoader() loader.write(sio.getvalue()) loader.close() sio.close() pixbuf = loader.get_pixbuf() return pixbuf def surface2pil(self, surface): w = surface.get_width() h = surface.get_height() return Image.frombuffer("RGBA", (w, h), surface.get_data(), "raw", "RGBA", 0,1) def pil2surface(self, im): imgd = im.tostring("raw","RGBA",0,1) a = array.array('B',imgd) w = im.size[0] h = im.size[1] stride = im.size[0] * 4 surface = cairo.ImageSurface.create_for_data (a, cairo.FORMAT_ARGB32, w, h, stride) return surface def resize_surface(self, surface, w, h): im = self.surface2pil(surface) im = im.resize((w, h), Image.ANTIALIAS) return self.pil2surface(im)
class CairoAppButton(gtk.EventBox): __gsignals__ = {"expose-event" : "override", "size_allocate": "override"} def __init__(self, surface=None, expose_on_clear=False): gtk.EventBox.__init__(self) self.set_visible_window(False) self.area = gtk.Alignment(0, 0, 1, 1) self.add(self.area) self.area.show() self.globals = Globals() self.surface = surface self.expose_on_clear = expose_on_clear self.badge = None self.badge_text = None self.progress_bar = None self.progress = None self.bl_sid = self.globals.connect("badge-look-changed", self.__on_badge_look_changed) self.pbl_sid = self.globals.connect("progress-bar-look-changed", self.__on_progress_bar_look_changed) def update(self, surface=None): a = self.area.get_allocation() if surface is not None: self.surface = surface if self.window is None: return if self.expose_on_clear: self.area.window.clear_area_e(a.x, a.y, a.width, a.height) else: self.area.window.clear_area(a.x, a.y, a.width, a.height) ctx = self.area.window.cairo_create() ctx.rectangle(a.x, a.y, a.width, a.height) ctx.clip() ctx.set_source_surface(self.surface, a.x, a.y) ctx.paint() for surface in (self.badge, self.progress_bar): if surface is not None: ctx.rectangle(a.x, a.y, a.width, a.height) ctx.clip() ctx.set_source_surface(surface, a.x, a.y) ctx.paint() child = self.area.get_child() if child: child.queue_draw() def do_expose_event(self, event): if self.surface is not None: ctx = self.area.window.cairo_create() ctx.rectangle(event.area.x, event.area.y, event.area.width, event.area.height) ctx.clip() a = self.get_allocation() ctx.set_source_surface(self.surface, a.x, a.y) ctx.paint() for surface in (self.badge, self.progress_bar): if surface is not None: ctx.rectangle(event.area.x, event.area.y, event.area.width, event.area.height) ctx.clip() ctx.set_source_surface(surface, a.x, a.y) ctx.paint() self.propagate_expose(self.area, event) def do_size_allocate(self, allocation): gtk.EventBox.do_size_allocate(self, allocation) if self.badge: self.make_badge(self.badge_text) if self.progress_bar: self.make_progress_bar(self.progress) def make_badge(self, text): if not text: self.badge_text = None self.badge = None return self.badge_text = text a = self.area.get_allocation() self.badge = cairo.ImageSurface(cairo.FORMAT_ARGB32, a.width, a.height) ctx = gtk.gdk.CairoContext(cairo.Context(self.badge)) layout = ctx.create_layout() if self.globals.settings["badge_use_custom_font"]: font = self.globals.settings["badge_font"] font_base, font_size = font.rsplit(" ", 1) font_size = int(font_size) else: font_size = max(int(round(0.2 * a.height)), 6) font_base = "sans bold" font = "%s %s" % (font_base, font_size) layout.set_font_description(pango.FontDescription(font)) layout.set_text(text) te = layout.get_pixel_extents() w = te[1][2] h = te[0][1] + te[0][3] size = min(a.width, a.height) p = 2 d = int(round(0.05 * size)) # Make sure the badge isn't too wide. while w + 2 * p + d >= a.width and font_size > 4: font_size = max(4, font_size - max(2, int(font_size * 0.2))) font = "%s %s" % (font_base, font_size) layout.set_font_description(pango.FontDescription(font)) te = layout.get_pixel_extents() w = te[1][2] h = te[0][1] + te[0][3] x = a.width - w - p - d y = a.height - h - p - d make_path(ctx, x - p, y + te[0][1] - (p + 1), w + 2 * p, h - te[0][1] + 2 * (p + 1), r=4) if self.globals.settings["badge_custom_bg_color"]: color = self.globals.settings["badge_bg_color"] alpha = float(self.globals.settings["badge_bg_alpha"]) / 255 else: color = "#CDCDCD" alpha = 1.0 r = int(color[1:3], 16)/255.0 g = int(color[3:5], 16)/255.0 b = int(color[5:7], 16)/255.0 ctx.set_source_rgba(r, g, b, alpha) ctx.fill_preserve() if self.globals.settings["badge_custom_fg_color"]: color = self.globals.settings["badge_fg_color"] alpha = float(self.globals.settings["badge_fg_alpha"]) / 255 else: color = "#020202" alpha = 1.0 r = int(color[1:3], 16)/255.0 g = int(color[3:5], 16)/255.0 b = int(color[5:7], 16)/255.0 ctx.set_source_rgba(r, g, b, alpha) ctx.set_line_width(0.8) ctx.stroke() ctx.move_to(x,y) ctx.show_layout(layout) def make_progress_bar(self, progress): if progress is None: self.progress = None self.progress_bar = None return self.progress = progress a = self.area.get_allocation() x = max(0.1 * a.width, 2) y = max(0.15 * a.height, 3) w = min(max (0.60 * a.width, 20), a.width - 2 * x) h = max(0.10 * a.height, 3.0) ro = h / 2 self.progress_bar = cairo.ImageSurface(cairo.FORMAT_ARGB32, a.width, a.height) ctx = cairo.Context(self.progress_bar) ctx.move_to(x,y) ctx.line_to(x + w * progress, y) ctx.line_to(x + w * progress, y + h) ctx.line_to(x, y + h) ctx.close_path() ctx.clip() if self.globals.settings["progress_custom_fg_color"]: color = self.globals.settings["progress_fg_color"] alpha = float(self.globals.settings["progress_fg_alpha"]) / 255 else: color = "#772953" alpha = 1.0 r = int(color[1:3], 16)/255.0 g = int(color[3:5], 16)/255.0 b = int(color[5:7], 16)/255.0 ctx.set_source_rgba(r, g, b, alpha) make_path(ctx, x, y, w, h, r=ro, b=0) ctx.fill() ctx.reset_clip() ctx.move_to(x + w * progress,y) ctx.line_to(x + w, y) ctx.line_to(x + w, y + h) ctx.line_to(x + w * progress, y + h) ctx.clip() make_path(ctx, x, y, w, h, r=ro, b=0) if self.globals.settings["progress_custom_bg_color"]: color = self.globals.settings["progress_bg_color"] bg_alpha = float(self.globals.settings["progress_bg_alpha"]) / 255 else: color = "#CDCDCD" bg_alpha = 0.25 br = int(color[1:3], 16)/255.0 bg = int(color[3:5], 16)/255.0 bb = int(color[5:7], 16)/255.0 ctx.set_source_rgba(br, bg, bb, bg_alpha) ctx.fill_preserve() ctx.reset_clip() ctx.set_source_rgba(r, g, b, alpha) ctx.set_line_width(0.8) ctx.stroke_preserve() def __on_badge_look_changed(self, *args): if self.badge: self.make_badge(self.badge_text) self.update() def __on_progress_bar_look_changed(self, *args): if self.progress_bar: self.make_progress_bar(self.progress) self.update() def destroy(self, *args, **kwargs): if self.bl_sid: self.globals.disconnect(self.bl_sid) self.bl_sid = None if self.pbl_sid: self.globals.disconnect(self.pbl_sid) self.pbl_sid = None if self.surface: self.surface = None gtk.EventBox.destroy(self, *args, **kwargs) def pointer_is_inside(self): b_m_x,b_m_y = self.get_pointer() b_r = self.get_allocation() if b_m_x >= 0 and b_m_x < b_r.width and \ b_m_y >= 0 and b_m_y < b_r.height: return True else: return False