#!/usr/bin/python3.13 -s
# -*-  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('Gtk', '3.0')
gi.require_version('Gdk', '3.0')
from gi.repository import GObject, GLib, Gio, Gtk, Gdk

GLIB_VERSION = (GLib.MAJOR_VERSION, GLib.MINOR_VERSION, GLib.MICRO_VERSION)
GTK_VERSION = (Gtk.MAJOR_VERSION, Gtk.MINOR_VERSION, Gtk.MICRO_VERSION)

import gettext
import locale
import os
import shutil
import signal
import subprocess
import sys
import time
import cairo
import ccm
import compizconfig as ccs
if GLIB_VERSION < (2, 42, 0):
    import optparse

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 = True
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

def OnCommandLine(application, cmdLine, context):
    options  = {"page": -1}
    page     = -1

    if GLIB_VERSION >= (2, 42, 0):
        optionValues = cmdLine.get_options_dict()
        for option in options.keys():
            if optionValues.contains(option):
                options.update({option:
                                optionValues.lookup_value(option).unpack()})
    else:
        parser = optparse.OptionParser()
        parser.add_option("-p", "--page", dest="page",
                          help="Directly jump to page PAGE", metavar="PAGE")
        (optionValues, _) = parser.parse_args(cmdLine.get_arguments())
        for option in options.keys():
            options.update({option: getattr(optionValues, option)})

    if GTK_VERSION >= (3, 6, 0):
        prevWin = application.get_active_window()
    else:
        prevWin = None
        if hasattr(application, "activeWindow"):
            prevWin = application.activeWindow

    if prevWin:
        prevWin.present()
        return 0

    if options["page"]:
        page = options["page"]

    Gtk.Window.set_default_icon_name("simple-ccsm")
    mainWin = MainWin(context, page)
    if GTK_VERSION >= (3, 6, 0):
        application.add_window(mainWin.Window)
    else:
        application.activeWindow = mainWin.Window
        mainWin.Application = application
        application.hold()
    return 0

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_app_paintable(True)
        self.set_has_window(False)

        self.connect("unrealize", self.unrealize_event)
        self.connect("size-allocate", self.size_allocate_event)
        if GTK_VERSION >= (3, 0, 0):
            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):
        if GTK_VERSION >= (3, 0, 0):
            cr = data
        else:
            event = data
            cr = event.window.cairo_create()
            Gdk.cairo_rectangle(cr, event.area)
            cr.clip()
            x, y = widget.translate_coordinates(widget.get_toplevel(), 0, 0)
            cr.translate(x, y)

        if GTK_VERSION >= (3, 6, 0):
            style = widget.get_style_context ()
            style.save()
            style.add_class(Gtk.STYLE_CLASS_VIEW)
            style.set_state(Gtk.StateFlags.SELECTED)
            fg = style.get_background_color(style.get_state())
            style.set_state(Gtk.StateFlags.NORMAL)
            dark = style.get_color(style.get_state())
            style.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 = self.line_width / 2.0
        y = self.line_width / 2.0
        for i in range(self.size[1]):
            for j in range(self.size[0]):
                if GTK_VERSION >= (3, 6, 0):
                    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_VERSION >= (3, 6, 0):
                    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 = self.line_width / 2.0

        return False

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_app_paintable(True)
        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_VERSION >= (3, 0, 0):
            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):
        if not self.surface or self._size_changed:
            self.draw_surface()

        if GTK_VERSION >= (3, 0, 0):
            cr = data
        else:
            event = data
            cr = event.window.cairo_create()
            Gdk.cairo_rectangle(cr, event.area)
            cr.clip()
            x, y = widget.translate_coordinates(widget.get_toplevel(), 0, 0)
            cr.translate(x, y)
        cr.set_source_surface(self.surface, 0, 0)
        cr.paint()

        return False

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_icon_name("gtk-apply", size)
            self.set_tooltip_text(_("Enabled"))
        else:
            self.image.set_from_icon_name("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.get_screen().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)
            lines = grep.communicate()[0].split("\n")
            ps.wait()
            for l in lines:
                name = l.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)
                button = dialog.add_button(_("_Cancel"), Gtk.ResponseType.CANCEL)
                button.set_image(Gtk.Image.new_from_icon_name("gtk-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):
        if GTK_VERSION >= (3, 6, 0):
            self.Window.destroy()
        else:
            self.Application.release()

if __name__ == "__main__":
    signal.signal(signal.SIGINT, signal.SIG_DFL)

    context = ccs.Context()
    if GTK_VERSION >= (3, 6, 0):
        application = Gtk.Application(application_id="org.compiz.simple-ccsm",
                                      flags=Gio.ApplicationFlags.HANDLES_COMMAND_LINE)
    else:
        application = Gio.Application(application_id="org.compiz.simple-ccsm",
                                      flags=Gio.ApplicationFlags.HANDLES_COMMAND_LINE)
    application.connect("command-line", OnCommandLine, context)

    try:
        application.register()
    except TypeError:
        application.register(None)

    if GLIB_VERSION >= (2, 42, 0):
        application.add_main_option("page", b"p", GLib.OptionFlags.NONE,
                                    GLib.OptionArg.STRING,
                                    "Directly jump to page PAGE",
                                    "PAGE")

    if application.get_is_remote():
        sys.stderr.write("Another Simple CCSM instance is already running...\n")
        Gdk.notify_startup_complete()
    application.run(sys.argv)
