#!/usr/bin/python3
# -*-  coding: UTF-8 -*-

# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
#
# Authors: Patrick Niklaus (patrick.niklaus@student.kit.edu)
# Copyright (C) 2007 Patrick Niklaus

import gi
gi.require_version('Pango', '1.0')
gi.require_version('PangoCairo', '1.0')
gi.require_version('Rsvg', '2.0')
gi.require_version('Gtk', '3.0')
gi.require_version('Gdk', '3.0')
from gi.repository import GObject, GLib, Gtk, Gdk

import shutil
import os
import subprocess
import sys
import cairo
import compizconfig as ccs
import ccm
import locale
import gettext
import time
import signal
from optparse import OptionParser
locale.setlocale(locale.LC_ALL, "")
_ = gettext.gettext

DataDir = '/usr/share/simple-ccsm/'

# Switcher keybinding
SwitcherKey = "<Alt>Tab"
ReverseSwitcherKey = "<Shift><Alt>Tab"

# Since there seems no way to get the untranslated names,
# we need to keep a list here.
CloseOpenEffectNames = {\
'animation:None':'None',
'animation:Random':'Random',
'animation:Curved Fold':'Curved Fold',
'animation:Dream':'Dream',
'animation:Fade':'Fade',
'animation:Glide 1':'Glide 1',
'animation:Glide 2':'Glide 2',
'animation:Horizontal Folds':'Horizontal Folds',
'animation:Magic Lamp':'Magic Lamp',
'animation:Sidekick':'Sidekick',
'animation:Vacuum':'Vacuum',
'animation:Wave':'Wave',
'animationaddon:Zoom':'Zoom',
'animationaddon:Airplane':'Airplane',
'animationaddon:Beam Up':'Beam Up',
'animationaddon:Burn':'Burn',
'animationaddon:Domino':'Domino',
'animationaddon:Explode':'Explode',
'animationaddon:Fold':'Fold',
'animationaddon:Glide 3':'Glide 3',
'animationaddon:Leaf Spread':'Leaf Spread',
'animationaddon:Razr':'Razr',
'animationaddon:Skewer':'Skewer'
}

MinimizeEffectNames = {\
'animation:None':'None',
'animation:Random':'Random',
'animation:Curved Fold':'Curved Fold',
'animation:Dream':'Dream',
'animation:Fade':'Fade',
'animation:Glide 1':'Glide 1',
'animation:Glide 2':'Glide 2',
'animation:Horizontal Folds':'Horizontal Folds',
'animation:Magic Lamp':'Magic Lamp',
'animation:Sidekick':'Sidekick',
'animation:Zoom':'Zoom',
'animationaddon:Airplane':'Airplane',
'animationaddon:Beam Up':'Beam Up',
'animationaddon:Burn':'Burn',
'animationaddon:Domino':'Domino',
'animationaddon:Explode':'Explode',
'animationaddon:Fold':'Fold',
'animationaddon:Glide 3':'Glide 3',
'animationaddon:Leaf Spread':'Leaf Spread',
'animationaddon:Razr':'Razr',
'animationaddon:Skewer':'Skewer'
}

FocusEffectNames = {\
'animation:None':'None',
'animation:Dodge':'Dodge',
'animation:Fade':'Fade',
'animation:Wave':'Wave'
}

# 0 to 5 stars
AnimationRatings = {\
'None': 0,
'Random': 5,
'Airplane': 5,
'Beam Up': 4,
'Burn': 4,
'Curved Fold': 2,
'Domino': 3,
'Dream': 4,
'Explode': 4,
'Fade': 1,
'Fold': 2,
'Glide 1': 2,
'Glide 2': 2,
'Glide 3': 2,
'Horizontal Folds': 2,
'Leaf Spread': 4,
'Magic Lamp': 5,
'Razr': 4,
'Sidekick': 3,
'Skewer': 3,
'Vacuum': 5,
'Wave': 4,
'Zoom': 2,
'Dodge': 5
}

gettext.bindtextdomain("simple-ccsm", "/usr/share/locale")
gettext.textdomain("simple-ccsm")

Profiles = {\
_("Minimal"): 'Minimal',
_("Medium"): 'Medium',
_("Advanced"): 'Advanced',
_("Ultimate"): 'Ultimate'
}

Descriptions = {\
'Minimal': _("Provides a simple desktop environment with very few effects."),
'Medium': _("Provides good balance between attractiveness and moderate performance requirements."),
'Advanced': _("Provides more aesthetically pleasing set of effects."),
'Ultimate': _("Provides very advanced and eye-catching set of effects. Requires faster graphics card.")
}

# 0 to 5 stars
EffectPluginRatings = {\
'wobbly': 5,
'cube': 3,
'wall': 1,
'expo': 4,
'blur': 5,
'mblur': 5,
'3d': 5,
'water': 5,
'firepaint': 4,
'shift': 5,
'scale': 2,
'cubeaddon': 5
}

Pages = {
'profile': 0,
'animations': 1,
'desktop': 2,
'accessibility': 3
}

AnimationSettings = {
'openAnimationBox': "open_effects",
'closeAnimationBox': "close_effects",
'minimizeAnimationBox': "minimize_effects",
'focusAnimationBox': "focus_effects"
}

CompizName = "compiz"

# Change to your default
CompizEnableDesktopEffects = False
CompizStartCommand = "compiz ccp --replace --sm-disable --ignore-desktop-hints"
CompizDryRunCommand = "compiz --version > /dev/null"

# Utility Functions
def GetXdgConfigHome(subdir):
    if 'XDG_CONFIG_HOME' in os.environ and os.environ['XDG_CONFIG_HOME']:
        xdg = os.path.expanduser(os.environ['XDG_CONFIG_HOME'])
    else:
        xdg = os.path.expanduser('~/.config')

    if subdir:
        ret = os.path.join(xdg, subdir)
    else:
        ret = xdg

    # create the directory if it's not there
    try:
        os.makedirs(ret, 0o700)
    except Exception:
        pass

    return ret

class BuilderHandlers(object):
    def __init__(self, backingObjects):
        self.backingObjects = backingObjects

    def __getattr__(self, name):
        for obj in self.backingObjects:
            if hasattr(obj, name):
                return getattr(obj, name)
        else:
            raise AttributeError("%r not found on any of %r"
              % (name, self.backingObjects))

def EnablePlugin(plugin, active):
    # attempt to resolve conflicts...
    conflicts = (plugin.Enabled and plugin.DisableConflicts) or plugin.EnableConflicts
    conflict = ccm.PluginConflict(plugin, conflicts, autoResolve=True)
    if conflict.Resolve():
        plugin.Enabled = active
    else:
        return False

    plugin.Context.Write()

    return True

def SetupBoxModel(box):
    if not box.get_model():
        store = Gtk.ListStore(GObject.TYPE_STRING)
        box.set_model(store)
        cell = Gtk.CellRendererText()
        box.pack_start(cell, True)
        box.add_attribute(cell, 'text', 0)
    else:
        box.get_model().clear()

class DesktopPreview(Gtk.Widget):
    def __init__(self, size=(0,0)):
        Gtk.Widget.__init__(self)

        self.size = size
        self.default_desktop_height = 30
        self.default_desktop_width = 40
        self.desktop_height = 30
        self.desktop_width = 40
        self.desktop_space = 5
        self.line_width = 1.0

        self.set_has_window(False)

        self.connect("unrealize", self.unrealize_event)
        self.connect("size-allocate", self.size_allocate_event)
        if Gtk.check_version(3, 0, 0) is None:
            self.connect("draw", self.draw_event)
        else:
            self.connect("expose_event", self.draw_event)

    def set_value(self, size):
        self.size = size
        self.queue_resize()

    def get_value(self):
        return self.size

    def unrealize_event(self, widget):
        widget.get_window().destroy()

    def size_allocate_event(self, widget, allocation):
        width = allocation.width
        height = allocation.height
        self.desktop_width = (float(width) / self.size[0]) - self.desktop_space
        self.desktop_height = (self.desktop_width * 3.0) / 4.0
        factor = ((float(height) / self.size[1]) - self.desktop_space) / self.desktop_height
        #factor = float(height) / ((self.desktop_height + self.desktop_space) * self.size[1])

        if factor < 1.0:
            self.desktop_width = int(self.desktop_width * factor)
            self.desktop_height = int(self.desktop_height * factor)

    def draw_event(self, widget, data=None):
        cr = widget.get_window().cairo_create()

        if Gtk.check_version(3, 6, 0) is None:
            context = widget.get_style_context ()
            context.save()
            context.add_class(Gtk.STYLE_CLASS_VIEW)
            context.set_state(Gtk.StateFlags.SELECTED)
            fg = context.get_background_color(context.get_state())
            context.set_state(Gtk.StateFlags.NORMAL)
            dark = context.get_color(context.get_state())
            context.restore()
        else:
            fg = widget.get_style().lookup_color('selected_bg_color')
            if fg[0] == False:
                # If there's no selected_bg_color, let it be gray.
                fg[1].red = 0xAAAA
                fg[1].green = 0xAAAA
                fg[1].blue = 0xAAAA
            dark = widget.get_style().lookup_color('fg_color')

        x = widget.get_allocation().x + self.line_width / 2.0
        y = widget.get_allocation().y + self.line_width / 2.0
        for i in range(self.size[1]):
            for j in range(self.size[0]):
                if Gtk.check_version(3, 6, 0) is None:
                    cr.set_source_rgba(*fg)
                else:
                    cr.set_source_rgb(fg[1].red/65535.0,
                                      fg[1].green/65535.0,
                                      fg[1].blue/65535.0)

                cr.rectangle(x, y, self.desktop_width, self.desktop_height)
                cr.fill_preserve()

                cr.set_line_width(self.line_width)
                if Gtk.check_version(3, 6, 0) is None:
                    cr.set_source_rgba(*dark)
                else:
                    cr.set_source_rgb(dark[1].red/65535.0,
                                      dark[1].green/65535.0,
                                      dark[1].blue/65535.0)
                cr.stroke()

                x += self.desktop_width + self.desktop_space

            y += self.desktop_height + self.desktop_space
            x = widget.get_allocation().x + self.line_width / 2.0

class StarScale(Gtk.Widget):
    def __init__(self, stars=0.0, max=5):
        Gtk.Widget.__init__(self)

        self.stars = stars
        self.max = max
        self.star_size = 16
        self.star_space = 5

        self.image_star = cairo.ImageSurface.create_from_png("%s/images/star.png" % DataDir)
        self.image_dark = cairo.ImageSurface.create_from_png("%s/images/star_dark.png" % DataDir)
        self.surface_star = None
        self.surface_dark = None
        self.surface      = None

        self._size_changed = False

        self.set_has_window(False)
        self.set_size_request((self.star_size+self.star_space)*self.max, self.star_size)

        self.connect("size-allocate", self.size_allocate_event)
        if Gtk.check_version(3, 0, 0) is None:
            self.connect("draw", self.draw_event)
        else:
            self.connect("expose_event", self.draw_event)

    def set_value(self, stars):
        self.stars = stars
        if self.surface:
            self.draw_surface()

    def set_max(self, max):
        self.max = max
        self.queue_resize()

    def get_value(self):
        return self.stars

    def get_max(self):
        return self.max

    def size_allocate_event(self, widget, allocation):
        allocation = widget.get_allocation()
        allocation.width = (self.star_size+self.star_space)*self.max
        allocation.height = self.star_size
        widget.size_allocate(allocation)
        self._size_changed = True

    def draw_sources(self):
        width = self.get_allocation().width
        height = self.get_allocation().height
        self.surface_star = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height)
        self.surface_dark = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height)

        data = ((self.surface_star, self.image_star), (self.surface_dark, self.image_dark))

        for target, source in data:
            cr = cairo.Context(target)
            x = 0
            for i in range(self.max):
                cr.set_source_surface(source, x, 0)
                cr.rectangle(x, 0, self.star_size, self.star_size)
                cr.fill()
                x += self.star_size + self.star_space

    def draw_surface(self):
        if not self.surface_dark or not self.surface_star or self._size_changed:
            self.draw_sources()
            self._size_changed = False

        width = self.get_allocation().width
        height = self.get_allocation().height
        self.surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height)
        cr = cairo.Context(self.surface)

        width = (self.star_size+self.star_space)*self.max

        q = (self.max - self.stars) / self.max
        cr.set_source_surface(self.surface_dark)
        cr.rectangle(width - q*width, 0, q*width, self.star_size)
        cr.fill()

        q = self.stars / self.max
        cr.set_source_surface(self.surface_star)
        cr.rectangle(0, 0, q*width, self.star_size)
        cr.fill()

    def draw_event(self, widget, data=None):
        if not self.surface or self._size_changed:
            self.draw_surface()

        x = widget.get_allocation().x
        y = widget.get_allocation().y
        width  = widget.get_allocation().width
        height = widget.get_allocation().height

        cr = self.get_window().cairo_create()
        cr.set_source_surface(self.surface, x, y)
        cr.rectangle(x, y, width, height)
        cr.fill()


GObject.type_register(StarScale)
GObject.type_register(DesktopPreview)

class CheckImage(Gtk.HBox):
    def __init__(self, text="", value=False):
        Gtk.Box.__init__(self)
        self.image = Gtk.Image()
        self.label = Gtk.Label(label=text)
        self.set_spacing(5)

        self.pack_start(self.image, False, False, 0)
        self.pack_start(self.label, False, False, 0)

        self.value = value

        self.image.props.xalign = 0.0
        self.label.props.xalign = 0.0

        self.update()

    def set_value(self, value):
        self.value = value

        self.update()

    def get_value(self):
        return self.value

    def update(self):
        size = Gtk.IconSize.BUTTON
        if self.value:
            self.image.set_from_stock(Gtk.STOCK_APPLY, size)
            self.set_tooltip_text(_("Enabled"))
        else:
            self.image.set_from_stock(Gtk.STOCK_DIALOG_ERROR, size)
            self.set_tooltip_text(_("Disabled"))

class ProfilePage:
    def __init__(self, context, builder):
        self.Builder = builder
        self.Context = context
        self.DesktopPlugins = {}

        # Check List
        self.EffectStars = StarScale()
        self.AnimationStars = StarScale()
        self.ZoomCheck = CheckImage(_("Zoom"))
        self.ColorfilterCheck = CheckImage(_("Colorfilter"))
        effectsAlign = self.Builder.get_object("effectsAlignment")
        effectsAlign.add(self.EffectStars)
        animationAlign = self.Builder.get_object("animationsAlignment")
        animationAlign.add(self.AnimationStars)
        accessibilityBox = self.Builder.get_object("accessibilityBox")
        accessibilityBox.pack_start(self.ZoomCheck, False, False, 0)
        accessibilityBox.pack_start(self.ColorfilterCheck, False, False, 0)

        # Desktop Label
        self.DesktopLayout = "%s"
        self.DesktopLabel = self.Builder.get_object("desktopLabel")

    def UpdateDesktopPlugins(self):
        self.DesktopPlugins = {}
        for plugin in self.Context.Plugins.values():
            if "largedesktop" in plugin.Features:
                self.DesktopPlugins[plugin.ShortDesc] = plugin

    def SetDesktopLabel(self, widget=None):
        for shortDesc, plugin in self.DesktopPlugins.items():
            if plugin.Enabled:
                self.DesktopLabel.set_markup(self.DesktopLayout % shortDesc)
                break

    def SetDescriptionLabel(self):
        label = self.Builder.get_object("descriptionLabel")
        name = self.Context.CurrentProfile.Name

        description = _("None")
        if name in Descriptions:
            description = Descriptions[name]

        label.set_text(description)

    def SetEffectRating(self, widget=None):
        rating = 0.0

        for pluginName, stars in EffectPluginRatings.items():
            if not pluginName in self.Context.Plugins:
                continue
            plugin = self.Context.Plugins[pluginName]
            if plugin.Enabled:
                rating += stars

        rating = rating / float(len(EffectPluginRatings) - 1)

        self.EffectStars.set_value(rating)

    def SetAnimationRating(self, widget=None):
        if 'animation' not in self.Context.Plugins:
            return

        names = {
            'close_effects': CloseOpenEffectNames,
            'open_effects': CloseOpenEffectNames,
            'minimize_effects': MinimizeEffectNames,
            'focus_effects': FocusEffectNames
        }
        plugin = self.Context.Plugins['animation']

        rating = 0.0
        for box, settingName in AnimationSettings.items():
            box = self.Builder.get_object(box)
            if not box.get_model():
                continue
            try:
                text = box.do_get_active_text (box)
            except (AttributeError, NameError, TypeError):
                text = box.get_active_text ()
            setting = plugin.Screens[0][settingName]
            if len(setting.Value) >= 1:
                value = setting.Value[0]
                if value in names[settingName]:
                    name = names[settingName][value]
                    rating += AnimationRatings[name]
                else:
                    rating += 5 # Assume "medium" rating for unknown animations

        if not plugin.Enabled:
            rating = 0.0

        rating = rating / len(AnimationSettings)

        self.AnimationStars.set_value(rating)

    def CheckAccessibility(self, widget=None):
        enabled = False
        for name in ('ezoom', 'zoom', 'mag'):
            plugin = self.Context.Plugins[name]
            if plugin.Enabled:
                enabled = True
                break
        self.ZoomCheck.set_value(enabled)

        enabled = False
        if 'colorfilter' in self.Context.Plugins:
            enabled = self.Context.Plugins['colorfilter'].Enabled
        self.ColorfilterCheck.set_value(enabled)

    def Update(self):
        self.UpdateDesktopPlugins()
        self.SetDesktopLabel()
        self.SetDescriptionLabel()
        self.SetEffectRating()
        self.SetAnimationRating()
        self.CheckAccessibility()

class AnimationPage:
    def __init__(self, context, builder):
        self.Builder = builder
        self.Context = context
        self.Block   = 0
        self.AnimExtensionPlugins = []

        if 'animation' not in self.Context.Plugins:
            # Disable animations CheckButton
            widget = self.Builder.get_object("enableAnimations")
            widget.set_sensitive(False)
            widget.set_tooltip_text(_("Can't find the animation plugin."))

            # Disable extra animations CheckButton
            widget = self.Builder.get_object("enableExtraAnim")
            widget.set_sensitive(False)
            return

        context.Plugins['animation'].Update()

        # Get a list of plugins that extend the animation plugin
        for (name, plugin) in self.Context.Plugins.items():
            # Assume the name of extension plugins will start with 'animation'
            if name == 'animation' or name[:9] != 'animation':
                continue
            for basePlugin in plugin.GetExtensionBasePlugins():
                if basePlugin.Name == 'animation':
                    self.AnimExtensionPlugins.append(plugin)
                    context.Plugins[name].Update()

        if len(self.AnimExtensionPlugins) == 0:
            # Disable extra animations CheckButton
            widget = self.Builder.get_object("enableExtraAnim")
            widget.set_sensitive(False)
            widget.set_tooltip_text(_("Can't find any animation extension plugins."))

    def EnableAnimationsChanged(self, widget):
        if self.Block > 0:
            return

        active = widget.get_active()
        plugin = self.Context.Plugins['animation']
        EnablePlugin(plugin, active)
        for prefix in ("open", "close", "minimize", "focus"):
            name = prefix + "AnimationBox"
            widget = self.Builder.get_object(name)
            widget.set_sensitive(active)
        widget = self.Builder.get_object('enableExtraAnim')
        widget.set_sensitive(active)
        if active:
            if widget.get_active():
                # If enableExtraAnim is checked, enable the extension plugins
                self.EnableExtraAnimationsChanged(widget)
            else:
                # Otherwise, just fill boxes with the base animation effects
                self.Context.UpdateExtensiblePlugins()
                self.FillAnimationBoxes()

    def EnableExtraAnimationsChanged(self, widget):
        if self.Block > 0:
            return

        # Enable/disable all extension plugins, considering dependencies
        pluginsToChange = list(self.AnimExtensionPlugins)
        while len(pluginsToChange) > 0:
            lastPluginsToActivate = list(pluginsToChange)
            pluginList = list(pluginsToChange)
            for plugin in pluginList:
                if plugin.Enabled != widget.get_active() and \
                    EnablePlugin(plugin, widget.get_active()):
                    pluginsToChange.remove(plugin)
            if pluginsToChange == lastPluginsToActivate:
                break  # no progress this iteration, so stop

        self.Context.UpdateExtensiblePlugins()
        self.FillAnimationBoxes()

    def AnimationBoxChanged(self, widget):
        if self.Block > 0:
            return

        name = Gtk.Buildable.get_name(widget)
        settingName = AnimationSettings[name]

        try:
            text = widget.do_get_active_text (widget)
        except (AttributeError, NameError, TypeError):
            text = widget.get_active_text ()
        plugin = self.Context.Plugins['animation']
        setting = plugin.Screens[0][settingName]
        value = setting.Value
        if len(value) >= 1:
            if text:  # Handle "chosen animation is in an extension plugin" case
                value[0] = setting.Info[1][0][text]
                setting.Value = value
                self.Context.Write()
        else:
            for setting in plugin.Groups[setting.Group][setting.SubGroup].Screens[0].values():
                setting.Reset()
            self.Context.Write()
            self.AnimationBoxChanged(widget, settingName)

    def SetEnableAnimations(self):
        widget = self.Builder.get_object("enableAnimations")
        active = False
        if 'animation' in self.Context.Plugins:
            plugin = self.Context.Plugins['animation']
            active = plugin.Enabled

        widget.set_active(active)

        # If at least one animation extension plugin is active,
        # make the enableExtraAnimations widget active
        widget = self.Builder.get_object("enableExtraAnim")
        atLeastOneActive = False
        for plugin in self.AnimExtensionPlugins:
            if plugin.Enabled:
                atLeastOneActive = True
                break
        widget.set_active(atLeastOneActive)

        for prefix in ("open", "close", "minimize", "focus"):
            name = prefix + "AnimationBox"
            widget = self.Builder.get_object(name)
            widget.set_sensitive(active)

    def FillAnimationBoxes(self):
        if 'animation' not in self.Context.Plugins:
            return

        plugin = self.Context.Plugins['animation']

        for boxName, settingName in AnimationSettings.items():
            box = self.Builder.get_object(boxName)
            setting = plugin.Screens[0][settingName]
            info = setting.Info[1]
            itemsByValue = info[1]
            items = info[2]
            SetupBoxModel(box)
            for key, value in items:
                box.append_text(key)
            if len(setting.Value) >= 1:
                value = setting.Value[0]
                if value in itemsByValue:
                    box.set_active(itemsByValue[value][1])
            else:
                box.set_active(0)

    def Update(self):
        self.Block += 1
        self.SetEnableAnimations()
        self.FillAnimationBoxes()
        self.Block -= 1

class EffectPage:
    def __init__(self, context, builder):
        self.Builder = builder
        self.Context = context
        self.Block   = 0

    def UpdateSwitcherPlugins(self):
        self.SwitcherPlugins = {}
        self.SwitcherKeySettings = {}
        self.ReverseSwitcherKeySettings = {}
        for pluginName in ('switcher', 'shift', 'ring', 'staticswitcher', 'stackswitch'):
            if pluginName in self.Context.Plugins:
                plugin = self.Context.Plugins[pluginName]

                if pluginName == 'shift':
                    self.SwitcherPlugins[_("%s (Cover)") % plugin.ShortDesc] = plugin
                    self.SwitcherPlugins[_("%s (Flip)") % plugin.ShortDesc] = plugin
                else:
                    self.SwitcherPlugins[plugin.ShortDesc] = plugin

                setting = plugin.Display['next_key']
                self.SwitcherKeySettings[pluginName] = setting
                setting = plugin.Display['prev_key']
                self.ReverseSwitcherKeySettings[pluginName] = setting


    def EffectPluginChanged(self, widget):
        if self.Block > 0:
            return

        name = Gtk.Buildable.get_name(widget)
        effectPlugins = {
        'enableScale': "scale",
        'enableWobbly': "wobbly",
        'enableBlur': "blur",
        'enableExpo': "expo",
        'enable3D': "3d"
        }
        pluginName = effectPlugins[name]

        plugin = self.Context.Plugins[pluginName]
        value  = widget.get_active()

        EnablePlugin(plugin, value)

    def SwitcherBoxChanged(self, widget):
        if self.Block > 0:
            return

        try:
            text = widget.do_get_active_text (widget)
        except (AttributeError, NameError, TypeError):
            text = widget.get_active_text ()

        for shortDesc, plugin in self.SwitcherPlugins.items():
            if text != shortDesc:
                EnablePlugin(plugin, False)
                setting = self.SwitcherKeySettings[plugin.Name]
                if not setting.IsDefault and setting.DefaultValue != SwitcherKey:
                    setting.Reset()
                setting = self.ReverseSwitcherKeySettings[plugin.Name]
                if not setting.IsDefault and setting.DefaultValue != ReverseSwitcherKey:
                    setting.Reset()

        self.Context.Write()

        if not text in self.SwitcherPlugins:
            return

        plugin = self.SwitcherPlugins[text]
        EnablePlugin(plugin, True)

        # Set default key binding to Alt-Tab
        setting = self.SwitcherKeySettings[plugin.Name]
        settings = self.SwitcherKeySettings.values()
        conflict = ccm.KeyConflict(setting, SwitcherKey, settings=settings, autoResolve=True)
        if conflict.Resolve(ccm.GlobalUpdater):
            setting.Value = SwitcherKey

            setting = self.ReverseSwitcherKeySettings[plugin.Name]
            settings = self.ReverseSwitcherKeySettings.values()
            conflict = ccm.KeyConflict(setting, SwitcherKey, settings=settings, autoResolve=True)
            if conflict.Resolve(ccm.GlobalUpdater):
                setting.Value = ReverseSwitcherKey

            # Exception for shift, since it has 2 modes
            if plugin.Name == 'shift':
                setting = plugin.Screens[0]['mode']

                if text.find(_("Cover")) != -1:
                    setting.Value = 0
                elif text.find(_("Flip")) != -1:
                    setting.Value = 1

        self.Context.Write()

    def DeformationBoxChanged(self, widget):
        if self.Block > 0:
            return

        try:
            text = widget.do_get_active_text (widget)
        except (AttributeError, NameError, TypeError):
            text = widget.get_active_text ()
        plugin = self.Context.Plugins['cubeaddon']
        setting = plugin.Screens[0]['deformation']
        value = setting.Info[2][text]
        if value != 0 and not plugin.Enabled:
            EnablePlugin(plugin, True)
        setting.Value = value

        self.Context.Write()

    def OpacityChanged(self, widget):
        if self.Block > 0:
            return

        value = widget.get_value()
        plugin = self.Context.Plugins['cube']
        # Only change cube opacity on rotate
        setting = plugin.Screens[0]['active_opacity']
        setting.Value = float(value)

        self.Context.Write()

    def EnableReflectionChanged(self, widget):
        if self.Block > 0:
            return

        value = widget.get_active()
        plugin = self.Context.Plugins['cubeaddon']
        setting = plugin.Screens[0]['reflection']
        setting.Value = value

        self.Context.Write()

    def SetEffectPlugins(self):
        widgets = {
        'scale': "enableScale",
        'wobbly': "enableWobbly",
        'blur': "enableBlur",
        'expo': "enableExpo",
        '3d': "enable3D"
        }

        for pluginName, widgetName in widgets.items():
            widget = self.Builder.get_object(widgetName)
            active = False
            sensitive = False
            if pluginName in self.Context.Plugins:
                plugin = self.Context.Plugins[pluginName]
                active = plugin.Enabled
                sensitive = True
            widget.set_sensitive(sensitive)
            widget.set_active(active)

    def SetCubeEffects(self, widget=None):
        alignment = self.Builder.get_object("cubeEffectsAlignment")
        sensitive = False
        if 'cube' in self.Context.Plugins:
            plugin = self.Context.Plugins['cube']
            sensitive = plugin.Enabled
        alignment.set_sensitive(sensitive)

    def SetOpacity(self):
        widget = self.Builder.get_object("cubeOpacity")

        if not 'cube' in self.Context.Plugins:
            return

        plugin = self.Context.Plugins['cube']
        setting = plugin.Screens[0]['active_opacity']
        value = setting.Value
        widget.set_value(int(value))

    def SetReflection(self):
        widget = self.Builder.get_object("enableReflection")

        if not 'cubeaddon' in self.Context.Plugins:
            return

        plugin = self.Context.Plugins['cubeaddon']
        setting = plugin.Screens[0]['reflection']
        value = setting.Value
        widget.set_active(value)

    def FillSwitcherBox(self):
        box = self.Builder.get_object("switcherPluginChooser")
        SetupBoxModel(box)
        box.append_text(_("None"))
        box.set_active(0)

        i = 1
        for shortDesc, plugin in self.SwitcherPlugins.items():
            box.append_text(shortDesc)
            if plugin.Enabled:
                if plugin.Name == 'shift':
                    modes = [_("Cover"), _("Flip")]
                    setting = plugin.Screens[0]['mode']
                    mode = modes[setting.Value]
                    if mode in shortDesc:
                        box.set_active(i)
                else:
                    box.set_active(i)
            i += 1

    def FillDeformationBox(self):
        box = self.Builder.get_object("deformationChooser")
        SetupBoxModel(box)

        if not 'cubeaddon' in self.Context.Plugins:
            box.set_sensitive(False)
            return

        plugin = self.Context.Plugins['cubeaddon']
        setting = plugin.Screens[0]['deformation']

        items = sorted(setting.Info[2].items(), key=ccm.EnumSettingKeyFunc)
        for key, value in items:
            box.append_text(key)
        box.set_active(setting.Value)

    def Update(self):
        self.Block += 1
        self.SetEffectPlugins()
        self.SetCubeEffects()
        self.SetOpacity()
        self.SetReflection()
        self.UpdateSwitcherPlugins()
        self.FillSwitcherBox()
        self.FillDeformationBox()
        self.Block -= 1

class DesktopPage:
    def __init__(self, context, builder):
        self.Builder = builder
        self.Context = context
        self.Block   = 0

        # Preview Widget
        self.DesktopPreview = DesktopPreview()
        previewAlign = self.Builder.get_object("previewAlignment")
        previewAlign.add(self.DesktopPreview)

    def UpdateDesktopPlugins(self):
        self.DesktopPlugins = {}
        for plugin in self.Context.Plugins.values():
            if "largedesktop" in plugin.Features:
                self.DesktopPlugins[plugin.ShortDesc] = plugin

    def DesktopSizeChanged(self, widget):
        if self.Block > 0:
            return

        name = Gtk.Buildable.get_name(widget)
        settings = {
        'horizontalDesktops': "hsize",
        'verticalDesktops': "vsize"
        }
        settingName = settings[name]

        value = widget.get_value()
        self.Context.Plugins['core'].Screens[0][settingName].Value = value
        self.Context.Write()
        self.SetDesktopPreview()

    def AppearenceBoxChanged(self, widget):
        if self.Block > 0:
            return

        try:
            text = widget.do_get_active_text (widget)
        except (AttributeError, NameError, TypeError):
            text = widget.get_active_text ()

        for shortDesc, plugin in self.DesktopPlugins.items():
            if text != shortDesc:
                EnablePlugin(plugin, False)

        self.Context.Write()

        for shortDesc, plugin in self.DesktopPlugins.items():
            if text == shortDesc:
                plugin.Enabled = True
                # exception for cube, since it requires rotate
                if plugin.Name == 'cube':
                    setting = self.Context.Plugins['core'].Screens[0]['vsize']
                    setting.Value = 1 # Cube can only use 1 vertical viewport
                    if 'rotate' in self.Context.Plugins:
                        EnablePlugin(self.Context.Plugins['rotate'], True)
                    if 'cubeaddon' in self.Context.Plugins:
                        EnablePlugin(self.Context.Plugins['cubeaddon'], True)

        self.Context.Write()
        self.SetDesktopSize()

    def SetDesktopPreview(self):
        hsize = self.Context.Plugins['core'].Screens[0]["hsize"].Value
        vsize = self.Context.Plugins['core'].Screens[0]["vsize"].Value
        self.DesktopPreview.set_value((hsize, vsize))

    def SetDesktopSize(self):
        scales = {"horizontalDesktops" : "hsize",
                  "verticalDesktops"   : "vsize"}

        for widgetName, settingName in scales.items():
            widget = self.Builder.get_object(widgetName)
            setting = self.Context.Plugins['core'].Screens[0][settingName]
            widget.set_value(setting.Value)

    def FillAppearenceBox(self):
        box = self.Builder.get_object("desktopPluginChooser")
        SetupBoxModel(box)

        i = 0
        for shortDesc, plugin in self.DesktopPlugins.items():
            box.append_text(shortDesc)
            if plugin.Enabled:
                box.set_active(i)
            i += 1

    def Update(self):
        self.Block += 1
        self.SetDesktopPreview()
        self.SetDesktopSize()
        self.UpdateDesktopPlugins()
        self.FillAppearenceBox()
        self.Block -= 1

class ZoomPage:
    def __init__(self, context, builder):
        self.Builder = builder
        self.Context = context
        self.Block   = 0

        # Zoom Keybindings
        self.Widgets = {}
        self.Settings = {
        # identifier  -   plugin   -    setting     -      container
        'screenZoomIn':  ('ezoom', 'zoom_in_button',  'screenZoomBox'),
        'screenZoomOut': ('ezoom', 'zoom_out_button', 'screenZoomBox'),
        'areaZoomIn':    ('mag',   'zoom_in_button',  'areaZoomBox'),
        'areaZoomOut':   ('mag',   'zoom_out_button', 'areaZoomBox')
        }

    def ZoomChanged(self, widget):
        if self.Block > 0:
            return

        available = {}
        for name in ('ezoom', 'mag'):
            if name in self.Context.Plugins:
                plugin = self.Context.Plugins[name]
                available[name] = plugin

        widget = self.Builder.get_object("enableZoom")
        if 'ezoom' in available:
            available['ezoom'].Enabled = widget.get_active()
        elif 'zoom' in available:
            available['ezoom'].Enabled = widget.get_active()

        widget = self.Builder.get_object("enableMag")
        if 'mag' in available:
            available['mag'].Enabled = widget.get_active()

        self.Context.Write()
        for identifier, data in self.Settings.items():
            pluginName, settingName, containerName = data

            if pluginName not in self.Context.Plugins:
                continue

            plugin    = self.Context.Plugins[pluginName]
            container = self.Builder.get_object(containerName)
            container.set_sensitive(plugin.Enabled)

    def SetZoom(self):
        for identifier, data in self.Settings.items():
            pluginName, settingName, containerName = data

            if pluginName not in self.Context.Plugins:
                continue

            if identifier not in self.Widgets:
                plugin    = self.Context.Plugins[pluginName]
                setting   = plugin.Display[settingName]
                widget    = ccm.MakeSetting(setting)
                container = self.Builder.get_object(containerName)
                container.pack_start(widget.EBox, True, True, 0)
                if not plugin.Enabled:
                    container.set_sensitive(False)
                self.Widgets[identifier] = widget

            self.Widgets[identifier].Read()

        available = {}
        for name in ('ezoom', 'zoom', 'mag'):
            if name in self.Context.Plugins:
                plugin = self.Context.Plugins[name]
                available[name] = plugin

        widget = self.Builder.get_object("enableZoom")
        widget.set_sensitive(True)
        if 'ezoom' in available:
            plugin = available['ezoom']
            widget.set_active(plugin.Enabled)
        elif 'zoom' in available:
            plugin = available['zoom']
            widget.set_active(plugin.Enabled)
        else:
            widget.set_sensitive(False)
            widget.set_active(False)

        widget = self.Builder.get_object("enableMag")
        widget.set_sensitive(True)
        if 'mag' in available:
            plugin = available['mag']
            widget.set_active(plugin.Enabled)
        else:
            widget.set_sensitive(False)
            widget.set_active(False)

    def Update(self):
        self.Block += 1
        self.SetZoom()
        self.Block -= 1

class EdgePage:
    def __init__(self, context, builder):
        self.Builder = builder
        self.Context = context
        self.Block   = 0

        align = self.Builder.get_object("edgesAlignment")
        self.EdgeSelector = ccm.GlobalEdgeSelector(self.Context)
        align.add(self.EdgeSelector)

    def Update(self):
        pass

class MainWin:
    def __init__(self, context, page = -1):
        self.Builder = Gtk.Builder()
        self.Builder.set_translation_domain("simple-ccsm")
        self.Builder.add_from_file(DataDir + "simple-ccsm.ui")

        self.Context = context
        self.Block = 0

        if page != -1 and page in Pages:
            notebook = self.Builder.get_object("notebook")
            notebook.set_current_page (Pages[page])

        # Window
        self.Window = self.Builder.get_object("mainWin")

        # Enable effects button
        self.EnableEffectsButton = self.Builder.get_object("enableEffects")
        self.EnableEffectsButton.connect ("toggled", self.EnableDesktopEffectsChanged)
        self.Notebook = self.Builder.get_object("notebook")
        if not CompizEnableDesktopEffects:
            self.EnableEffectsButton.hide()
            self.EnableEffectsButton.set_no_show_all(True)

        # Profile Chooser
        self.ProfileChooser = self.Builder.get_object("profileChooser")

        # Pages
        self.AnimationPage = AnimationPage(self.Context, self.Builder)
        self.DesktopPage   = DesktopPage(self.Context, self.Builder)
        self.ZoomPage      = ZoomPage(self.Context, self.Builder)
        self.ProfilePage   = ProfilePage(self.Context, self.Builder)
        self.EffectPage    = EffectPage(self.Context, self.Builder)
        self.EdgePage      = EdgePage(self.Context, self.Builder)

        self.Builder.connect_signals(BuilderHandlers([self, self.AnimationPage,
          self.DesktopPage, self.ZoomPage, self.ProfilePage, self.EffectPage, self.EdgePage]))

        self.Update()
        self.Window.show_all()

        GLib.timeout_add(1000, self.EnableIntegration)

    def EnableIntegration(self):
        if os.getenv("XDG_CURRENT_DESKTOP", "").endswith("MATE") or os.getenv("MATE_DESKTOP_SESSION_ID") is not None:
            if not self.Context.Integration:
                self.Context.Integration = True

            compat = self.Context.Plugins["matecompat"]
            if not compat.Enabled:
                EnablePlugin(compat, True)
        elif os.getenv("XDG_CURRENT_DESKTOP", "").endswith("GNOME") or os.getenv("GNOME_DESKTOP_SESSION_ID") is not None:
            if not self.Context.Integration:
                self.Context.Integration = True

        self.Context.Write()


    def CheckForCompiz(self):
        composited = self.Window.is_composited()
        if composited:
            # Now do the dirty work - check if it is really Compiz
            psCMD = "ps -e".split(" ")
            ps = subprocess.Popen(psCMD, stdout=subprocess.PIPE)
            grepCMD = "grep compiz".split(" ")
            if sys.version_info.major >= 3:
                grep = subprocess.Popen(grepCMD, stdin=ps.stdout, stdout=subprocess.PIPE, encoding="utf-8")
            else:
                grep = subprocess.Popen(grepCMD, stdin=ps.stdout, stdout=subprocess.PIPE)
            grep.wait()
            lines = grep.stdout.readlines()
            for l in lines:
                name = l.replace("\n", "").split(" ")[-1]
                if name == CompizName:
                    return True

        return False

    def Update(self):
        self.Context.Read()
        self.Block += 1

        self.AnimationPage.Update()
        self.DesktopPage.Update()
        self.EffectPage.Update()
        self.ZoomPage.Update()
        self.ProfilePage.Update()
        self.EdgePage.Update()

        self.SetProfile()
        if CompizEnableDesktopEffects:
            self.SetEnableDesktopEffects()

        self.Block -= 1

    def SetEnableDesktopEffects(self):
        running = self.CheckForCompiz()
        self.EnableEffectsButton.set_active(running)
        self.Notebook.set_sensitive(running)
        self.ProfileChooser.set_sensitive(running)

    def EnableDesktopEffectsChanged(self, widget):
        if self.Block > 0:
            return

        enabled = self.EnableEffectsButton.get_active()
        if enabled:
            # First try to check if compiz can be run
            cmd = CompizDryRunCommand
            proc = subprocess.Popen(cmd, shell=True)
            proc.wait()
            if proc.returncode != 0:
                # Dry run detected problems, warn the user
                dialog = Gtk.Dialog ()
                dialog.set_title("Error")
                dialog.set_border_width(6)
                label = Gtk.Label(label=_("Desktop effects are not supported on your current hardware / configuration. Would you like to cancel enabling of desktop effects or run them anyway?"))
                label.set_line_wrap(True)
                dialog.vbox.pack_start(label,
                                       True,
                                       False,
                                       3)
                dialog.add_button(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL)
                dialog.add_button("Run anyway", Gtk.ResponseType.OK)
                dialog.show_all()
                response = dialog.run()
                dialog.destroy()
                if response != Gtk.ResponseType.OK:
                    self.EnableEffectsButton.set_active(False)
                    return

            # Start compiz
            cmd = CompizStartCommand.split(" ")
            subprocess.Popen(cmd)

            # Create a file that indicates wether compiz is enabled or not
            xdg_compiz = GetXdgConfigHome('compiz')
            path = os.path.join(xdg_compiz, 'enable-compiz')
            open(path, "w+").close() # touch replacement

            path = os.path.join(xdg_compiz, 'disable-compiz')
            try:
                os.remove(path)
            except (IOError, OSError):
                pass
        else:
            fallbackWM = ""
            if os.getenv("XDG_CURRENT_DESKTOP", "").endswith("MATE") or os.getenv("MATE_DESKTOP_SESSION_ID") is not None:
                fallbackWM = "marco"
            elif os.getenv("XDG_CURRENT_DESKTOP", "").endswith("GNOME") or os.getenv("GNOME_DESKTOP_SESSION_ID") is not None:
                fallbackWM = "metacity"

            if fallbackWM:
                cmd = "%s --replace" % fallbackWM
                cmd = cmd.split(" ")
                subprocess.Popen(cmd)

            # Create a file that indicates compiz is explicitly disabled, so should not be started
            xdg_compiz = GetXdgConfigHome('compiz')
            path = os.path.join(xdg_compiz, 'disable-compiz')
            open(path, "w+").close() # touch replacement

            # Remove old config
            files = (os.path.join(xdg_compiz, 'enable-compiz'), os.path.join(xdg_compiz, 'compiz-manager'))
            for file in files:
                path = os.path.expanduser(file)
                try:
                    os.remove(path)
                except (IOError, OSError):
                    pass

        self.Notebook.set_sensitive(enabled)
        self.ProfileChooser.set_sensitive(enabled)


    def ApplyProfile(self, widget):
        try:
            profile = self.ProfileChooser.do_get_active_text (self.ProfileChooser)
        except (AttributeError, NameError, TypeError):
            profile = self.ProfileChooser.get_active_text ()

        if profile == _("Default"):
            profile = "Default"
            self.Context.ResetProfile()
        elif profile in Profiles:
            profile = Profiles[profile]

        profilePath = "%s/profiles/%s.profile" % (DataDir, profile)
        self.Context.CurrentProfile = ccs.Profile(self.Context, profile)
        self.Context.Read()
        self.Context.UpdateProfiles()
        self.Context.Import(profilePath)

        self.Context.Write()
        self.Update()

    def SetProfile(self):
        SetupBoxModel(self.ProfileChooser)

        self.Context.UpdateProfiles()

        self.ProfileChooser.append_text(_("Default"))
        profiles = sorted(Profiles.values())
        for profile in profiles:
            self.ProfileChooser.append_text(_(profile))

        current = self.Context.CurrentProfile.Name or _("Default")
        if current in profiles:
            pos = profiles.index(current) + 1
            self.ProfileChooser.set_active(pos)
        elif current != _("Default"):
            self.ProfileChooser.prepend_text(current)
            self.ProfileChooser.set_active(0)
        else:
            self.ProfileChooser.set_active(0)

    def Quit(self, widget=None):
        Gtk.main_quit()

if __name__ == "__main__":
    signal.signal(signal.SIGINT, signal.SIG_DFL)
    context = ccs.Context()
    page = -1
    parser = OptionParser()
    parser.add_option("-p", "--page", dest = "page",
		      help = "Directly jump to page PAGE", metavar = "PAGE")
    (options, args) = parser.parse_args()
    if options.page:
        page = options.page
    Gtk.Window.set_default_icon_name('simple-ccsm')
    mainWin = MainWin(context, page)
    Gtk.main()
