Example #1
0
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)
Example #2
0
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
Example #3
0
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)
Example #6
0
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