#!/usr/bin/env python
# -*- 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: Quinn Storm (quinn@beryl-project.org)
#          Patrick Niklaus (marex@opencompositing.org)
# Copyright (C) 2007 Quinn Storm

import pygtk
import gtk
import gtk.gdk

import compizconfig
ccs = compizconfig

from ccm.Constants import *
from ccm.Settings import *
from ccm.Conflicts import *
from ccm.Utils import *
from ccm.Widgets import *

import locale
import gettext
locale.setlocale(locale.LC_ALL, "")
gettext.bindtextdomain("ccsm", DataDir + "/locale")
gettext.textdomain("ccsm")
_ = gettext.gettext

CurrentUpdater = None

# Action Page
#
class ActionPage:
    def __init__(self, context, plugin = None, filter = None, createUpdater=True):
        global CurrentUpdater
        self.EdgeList = ['None', 'TopLeft', 'Top', 'TopRight', 'Left', 'Right', 'BottomLeft', 'Bottom', 'BottomRight']
        self.Widget = gtk.VBox()
        self.Plugin = plugin
        self.Filter = filter
        self.Context = context
        self.Scroll = gtk.ScrolledWindow()
        self.Scroll.props.hscrollbar_policy = gtk.POLICY_AUTOMATIC
        self.Scroll.props.vscrollbar_policy = gtk.POLICY_AUTOMATIC
        self.Store = gtk.TreeStore(
                gobject.TYPE_STRING,     #0-Name
                gobject.TYPE_UINT,         #1-Key
                gobject.TYPE_UINT,         #2-KeyMods
                gobject.TYPE_STRING,     #3-Button
                gobject.TYPE_STRING,     #4-Edges
                gobject.TYPE_STRING,    #5-EdgeButton
                gobject.TYPE_BOOLEAN,     #6-Visible/Editable
                gobject.TYPE_BOOLEAN,     #7-Bell
                gobject.TYPE_BOOLEAN,   #8-KeyPossible
                gobject.TYPE_BOOLEAN,   #9-ButPossible
                gobject.TYPE_BOOLEAN,   #10-EdgePossible
                gobject.TYPE_BOOLEAN,   #11-BellPossible
                gobject.TYPE_STRING,     #12-group
                gobject.TYPE_STRING,     #13-subgroup
                gobject.TYPE_STRING,     #14-setting
                gobject.TYPE_STRING,     #15-plugin
                gobject.TYPE_STRING,     #16-disp/scn str XXX
                )
        
        self.TreeView = gtk.TreeView(self.Store)
        self.TreeView.set_headers_visible(True)
        # Name
        self.TreeView.insert_column_with_attributes(-1, _("Name"), gtk.CellRendererText(), markup=0)
        # Key
        keyRenderer = gtk.CellRendererAccel()
        keyRenderer.connect('accel-edited', self.KeyEdited)
        keyRenderer.connect('accel-cleared', self.KeyCleared)
        self.TreeView.insert_column_with_attributes(-1, _("Key"), keyRenderer, accel_key=1, accel_mods=2, editable=8, visible=6, sensitive=8)
        # Button
        buttonRenderer = gtk.CellRendererText()
        buttonRenderer.connect('edited', self.ButtonEdited)
        self.TreeView.insert_column_with_attributes(-1, _("Button"), buttonRenderer, text=3, editable=9, visible=6, sensitive=9)
        # Edges
        edgeRenderer = gtk.CellRendererText()
        self.TreeView.insert_column_with_attributes(-1, _("Screen Edge"), edgeRenderer, text=4, visible=6, sensitive=10)
        # Edge Button
        self.EdgeButtonStore = gtk.ListStore(gobject.TYPE_STRING)
        for number in range(0, 6):
            iter = self.EdgeButtonStore.append()
            text = ""
            if number != 0:
                text = _("Button %i") % number
            else:
                text = _("None")
            self.EdgeButtonStore.set(iter, 0, text)
        edgeButtonRenderer = gtk.CellRendererCombo()
        edgeButtonRenderer.props.has_entry = False
        edgeButtonRenderer.props.model = self.EdgeButtonStore
        edgeButtonRenderer.props.text_column = 0
        edgeButtonRenderer.connect('edited', self.EdgeButtonEdited)
        self.TreeView.insert_column_with_attributes(-1, _("Edge Button"), edgeButtonRenderer, text=5, visible=6, editable=10, sensitive=10)
        # Bell -- may replace with one setting in general
        bellRenderer = gtk.CellRendererToggle()
        bellRenderer.props.xalign = 0
        bellRenderer.connect('toggled', self.BellEdited)
        self.TreeView.insert_column_with_attributes(-1, _("On System Bell"), bellRenderer, activatable=11, visible=6, sensitive=11, active=7) 
        
        for col in self.TreeView.get_columns():
            col.set_resizable(True)
        
        self.Scroll.add(self.TreeView)
        self.Widget.pack_start(self.Scroll, True, True)
        self.TreeView.connect('row-activated', self.Activated)

        self.UpdateTreeView()

        if createUpdater and CurrentUpdater == None:
            CurrentUpdater = Updater(context)

        CurrentUpdater.Append(self)

    def Read(self):
        self.UpdateTreeView()
        
    def UpdateTreeView(self):
        self.Store.clear()
        self.Setting = []
        self.Empty = True

        self.Plugins = {}
        if self.Plugin:
            self.DoGroups(self.Plugin.Groups, None, self.Plugin.Name)
        else:
            for plugin in sorted(self.Context.Plugins.values(), PluginSortCompare):
                pluginIter = self.Store.append(None)
                markup = "%s"
                if plugin.Enabled:
                    markup = "<b>%s</b>"
                self.Store.set(pluginIter, 0, markup % plugin.ShortDesc, 6, False, 8, False, 9, False, 10, False, 11, False)
                if not self.DoGroups(plugin.Groups, pluginIter, plugin.Name):
                    self.Store.remove(pluginIter)
    
    def DoGroups(self, groupList, iter, pluginName):
        self.Plugins[pluginName] = {}
        pluginEmpty = True
        
        for groupName, group in groupList.items():
            groupEmpty = True
            groupIter = self.Store.append(iter)
            name = (groupName == '' and _("General") or groupName)
            self.Store.set(groupIter, 0, name, 6, False, 8, False, 9, False, 10, False, 11, False)
            self.Plugins[pluginName][groupName] = {}

            subGroupsSorted = sorted(group.items(), FirstItemSortCompare)
            for subGroupName, subGroup in subGroupsSorted:
                if subGroupName != '':
                    subGroupIter = self.Store.append(groupIter)
                    self.Store.set(subGroupIter, 0, subGroupName, 6, False, 8, False, 9, False, 10, False, 11, False)
                self.Plugins[pluginName][groupName][subGroupName] = {}
                subGroupEmpty = True
                
                settings = sum((v.values() for v in [subGroup.Display]+[subGroup.Screens[CurrentScreenNum]]), [])
                settings = sorted(FilterSettings(settings, self.Filter), SettingSortCompare)

                for setting in settings:
                    if setting.Type == 'Action':
                        self.Setting.append(setting)

                        if subGroupName != '':
                            i = self.Store.append(subGroupIter)
                        else:
                            i = self.Store.append(groupIter)

                        key, mods = gtk.accelerator_parse(setting.Value[0])
                        edges = 'None'
                        if len(setting.Value[3]) > 0:
                            edges = " | ".join(setting.Value[3])

                        edgeButton = setting.Value[4]
                        if edgeButton != 0:
                            edgeButton = _("Button %i") % edgeButton
                        else:
                            edgeButton = _("None")

                        markup = "%s"
                        if setting.Integrated:
                            markup = "<span color='blue'>%s</span>"
                        
                        self.Store.set(i, 0, markup % setting.ShortDesc, 1, key, 2, mods, 3, setting.Value[1], 4,
                                       edges, 5, edgeButton, 6, True, 7, setting.Value[2], 8, setting.Info[0], 9, setting.Info[1], 10,
                                       setting.Info[3], 11, setting.Info[2], 12, groupName, 13, subGroupName, 14,
                                       setting.Name, 15, pluginName)
                        self.Plugins[pluginName][groupName][subGroupName][setting.Name] = setting
                        
                        groupEmpty = False
                        subGroupEmpty = False
                        pluginEmpty = False
                        self.Empty = False
                
                if subGroupEmpty:
                    if subGroupName != '':
                        self.Store.remove(subGroupIter)
            if groupEmpty:
                self.Store.remove(groupIter)
        
        return not pluginEmpty
    
    def Activated(self, object, path, col):
        def ResetButton(object, setting, iter):
            if setting.Info[0]:
                keyEntry.set_text(setting.DefaultValue[0])
            if setting.Info[1]:
                buttonEntry.set_text(setting.DefaultValue[1])
            if setting.Info[2]:
                bellButton.set_active(setting.DefaultValue[2])
            if setting.Info[3]:
                children = edgeTable.get_children()
                edges = setting.DefaultValue[3]
                for checkBox in children:
                    checkBox.set_active(False)
                    for edge in edges:
                        if checkBox.get_label() == edge:
                            checkBox.set_active(True)
                            break
                edgeButtonCombo.set_active(setting.DefaultValue[4])

        iter = self.Store.get_iter(path)
        store = self.Store.get(iter, 12, 13, 14, 15, 1, 2, 3, 4, 5, 7)
        groupName, subGroupName, settingName, pluginName, key, keyModifier, button, edges, edgeButton, bell = store

        if subGroupName is None or groupName is None or settingName is None:
            if self.TreeView.row_expanded(path):
                self.TreeView.collapse_row(path)
            else:
                self.TreeView.expand_row(path, False)
            return

        edges = edges.split(" | ")
        setting = self.Plugins[pluginName][groupName][subGroupName][settingName]

        dlg = gtk.Dialog(_("Edit Action: %s") % setting.ShortDesc)
        dlg.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL)
        dlg.add_button(gtk.STOCK_OK, gtk.RESPONSE_OK).grab_default()
        dlg.set_default_response(gtk.RESPONSE_OK)
        table = gtk.Table()
        dlg.vbox.pack_start(table, False, False)
        
        # Key
        if setting.Info[0]:
            keyEntry = gtk.Entry()
            keyEntry.set_text(gtk.accelerator_name(key, keyModifier) or 'None')
            keyLabel = gtk.Label(_("Key"))
            keyLabel.props.xalign = 0
            table.attach(keyLabel, 0, 1, 0, 1, TableDef, TableDef, TableX, TableY)
            table.attach(keyEntry, 1, 2, 0, 1, TableDef, TableDef, TableX, TableY)
        
        # Button
        if setting.Info[1]:
            buttonEntry = gtk.Entry()
            buttonEntry.set_text(button)
            buttonLabel = gtk.Label(_("Button"))
            buttonLabel.props.xalign = 0
            table.attach(buttonLabel, 0, 1, 1, 2, TableDef, TableDef, TableX, TableY)
            table.attach(buttonEntry, 1, 2, 1, 2, TableDef, TableDef, TableX, TableY)
        
        # Edge + EdgeButton
        if setting.Info[3]:
            edgeTable = gtk.Table()
            row = 0
            col = 0
            for e in self.EdgeList:
                if e == 'None':
                    continue
                
                edgeCheck = gtk.CheckButton(e)
                if edges.count(e) > 0:
                    edgeCheck.set_active(True)
                edgeTable.attach(edgeCheck, col, col+1, row, row+1, TableDef, TableDef, TableX, TableY)

                col += 1
                if col > 2:
                    row += 1
                    col = 0

                if (row == 1 and col > 0):
                    col += 1
                    
            edgeLabel = gtk.Label(_("Screen Edges"))
            edgeLabel.props.xalign = 0
            table.attach(edgeLabel, 0, 1, 2, 3, TableDef, TableDef, TableX, TableY)
            table.attach(edgeTable, 1, 2, 2, 3, TableDef, TableDef, TableX, TableY)

            edgeButtonCombo = gtk.ComboBox(self.EdgeButtonStore)
            if edgeButton == _("None"):
                edgeButtonCombo.set_active(0)
            else:
                edgeButtonCombo.set_active(int(edgeButton[-1]))
            edgeButtonComboRenderer = gtk.CellRendererText()
            edgeButtonCombo.pack_start(edgeButtonComboRenderer)
            edgeButtonCombo.add_attribute(edgeButtonComboRenderer,'text',0)
            edgeButtonLabel = gtk.Label(_("Edge Button"))
            edgeButtonLabel.props.xalign = 0
            table.attach(edgeButtonLabel, 0, 1, 3, 4, TableDef, TableDef, TableX, TableY)
            table.attach(edgeButtonCombo, 1, 2, 3, 4, TableDef, TableDef, TableX, TableY)
        
        # Bell
        if setting.Info[2]:
            bellButton = gtk.CheckButton(_("On System Bell"))
            bellButton.set_active(bell)
            table.attach(bellButton, 0, 2, 4, 5, TableDef, TableDef, TableX, TableY)
        
        # Reset
        box = gtk.HBox()
        box.pack_start(Label(_("Reset To Defaults")), True, True)
        resetButton = gtk.Button()
        resetImage = gtk.Image()
        resetImage.set_from_stock(gtk.STOCK_CLEAR, gtk.ICON_SIZE_BUTTON)
        resetButton.set_image(resetImage)
        box.pack_end(resetButton, False, False)
        resetButton.connect('clicked', ResetButton, setting, iter)
        table.attach(box, 0, 2, 5, 6, TableDef, TableDef, TableX, TableY)
        
        dlg.show_all()
        resp = dlg.run()
        if setting.Info[0]:
            key = keyEntry.get_text()
        if setting.Info[1]:
            button = buttonEntry.get_text()
        if setting.Info[2]:
            bell = bellButton.get_active()
        if setting.Info[3]:
            children = edgeTable.get_children()
            edges = []
            for checkBox in children:
                if checkBox.get_active():
                    edges.append(checkBox.get_label())
            edges = " | ".join(edges)
            edgeButton = edgeButtonCombo.get_active_text()
        
        dlg.destroy()
        if resp == gtk.RESPONSE_OK:
            if setting.Info[0]:
                akey, amods = gtk.accelerator_parse(key)
                self.Store.set(iter, 1, akey, 2, amods)
            if setting.Info[1]:
                self.Store.set(iter, 3, button)
            if setting.Info[2]:
                self.Store.set(iter, 7, bell)
            if setting.Info[3]:
                self.Store.set(iter, 4, edges)
                self.Store.set(iter, 5, edgeButton)
            self.WriteSetting(iter)
    
    def WriteSetting(self, iter):
        store = self.Store.get(iter, 12, 13, 14, 15, 1, 2, 3, 4, 5, 7)
        groupName, subGroupName, settingName, pluginName, key, keyModifier, button, edges, edgeButton, bell = store
        
        setting = self.Plugins[pluginName][groupName][subGroupName][settingName]
        realKey = gtk.accelerator_name(key, keyModifier)
        edges = edges.split(" | ")
        if edgeButton != _("None"):
            edgeButton = int(edgeButton[-1])
        else:
            edgeButton = 0
        
        conflict = ActionConflict(setting, realKey, button, bell, edges, edgeButton)
        if conflict.Resolve():
            setting.Value = [realKey, button, bell, edges, edgeButton]
            setting.Plugin.Context.Write()
            
        realKey, button, bell, edges, edgeButton = setting.Value
        key, keyModifier = gtk.accelerator_parse(realKey)
        if len(edges) > 0:
            edges = " | ".join(setting.Value[3])
        else:
            edges = 'None'
        if edgeButton != 0:
            edgeButton = _("Button %i") % edgeButton
        else:
            edgeButton = _("None")

        self.Store.set(iter, 1, key, 2, keyModifier, 3, button, 4, edges, 5, edgeButton, 7, bell)
    
    def KeyEdited(self, obj, path, key, mods, code):
        iter = self.Store.get_iter_from_string(path)
        self.Store.set(iter, 1, key, 2, mods)
        self.WriteSetting(iter)
    
    def KeyCleared(self, obj, path):
        iter = self.Store.get_iter_from_string(path)
        self.Store.set(iter, 1, 0, 2, 0)
        self.WriteSetting(iter)
    
    def ButtonEdited(self, obj, path, val):
        iter = self.Store.get_iter_from_string(path)
        self.Store.set(iter, 3, val)
        self.WriteSetting(iter)
    
    def EdgeEdited(self, obj, path, val):
        iter = self.Store.get_iter_from_string(path)
        self.Store.set(iter, 4, val)
        self.WriteSetting(iter)

    def EdgeButtonEdited(self, obj, path, val):
        iter = self.Store.get_iter_from_string(path)
        self.Store.set(iter, 5, val)
        self.WriteSetting(iter)
    
    def BellEdited(self, obj, path):
        iter = self.Store.get_iter_from_string(path)
        value = self.Store.get(iter, 7)[0]
        if value:
            value = False
        else:
            value = True
        self.Store.set(iter, 7, value)
        self.WriteSetting(iter)

# Plugin Page
#
class PluginPage:
    def __init__(self, plugin, main):
        self.Plugin = plugin
        self.Main = main
        self.LeftWidget = gtk.VBox(False, 10)
        self.LeftWidget.set_border_width(15)
        pluginLabel = Label()
        pluginLabel.set_markup("<span color='%s' size='large' weight='800'>%s</span>" % (self.Main.Style.BrightColor, plugin.ShortDesc))
        pluginImg = Image(plugin.Name, ImagePlugin, 64)
        filterLabel = Label()
        filterLabel.set_markup("<span color='%s' size='large' weight='800'>%s</span>" % (self.Main.Style.BrightColor, _("Filter")))
        if has_sexy:
            self.FilterEntry = sexy.IconEntry()
            self.FilterEntry.add_clear_button()
        else:
            self.FilterEntry = gtk.Entry()
        self.FilterEntry.connect("changed", self.FilterChanged)
        self.LeftWidget.pack_start(pluginImg, False, False)
        self.LeftWidget.pack_start(filterLabel, False, False)
        self.LeftWidget.pack_start(self.FilterEntry, False, False)
        self.LeftWidget.pack_start(pluginLabel, False, False)
        infoLabelCont = gtk.HBox()
        infoLabelCont.set_border_width(10)
        self.LeftWidget.pack_start(infoLabelCont, False, False)
        infoLabel = Label(plugin.LongDesc, 180)
        infoLabelCont.pack_start(infoLabel, True, True)

        self.NotFoundBox = None
        
        if plugin.Name != 'core':
            Tooltips.set_tip(self.FilterEntry, _("Search %s Plugin Options") % plugin.ShortDesc)
            enableLabel = Label()
            enableLabel.set_markup("<span color='%s' size='large' weight='800'>%s</span>" % (self.Main.Style.BrightColor, _("Use This Plugin")))
            self.LeftWidget.pack_start(enableLabel, False, False)
            enableCheckCont = gtk.HBox()
            enableCheckCont.set_border_width(10)
            self.LeftWidget.pack_start(enableCheckCont, False, False)
            enableCheck = gtk.CheckButton()
            enableCheck.add(Label(_("Enable %s") % plugin.ShortDesc, 120))
            Tooltips.set_tip(enableCheck, plugin.LongDesc)
            enableCheck.set_active(plugin.Enabled)
            enableCheck.set_sensitive(plugin.Context.AutoSort)
            enableCheckCont.pack_start(enableCheck, True, True)
            enableCheck.connect('toggled', self.EnablePlugin)
        else:
            Tooltips.set_tip(self.FilterEntry, _("Search Compiz Core Options"))
        
        backButton = gtk.Button(gtk.STOCK_GO_BACK)
        backButton.set_use_stock(True)
        self.LeftWidget.pack_end(backButton, False, False)
        backButton.connect('clicked', main.BackToMain)
        self.RightWidget = gtk.Notebook()
        self.Pages = []

        groupsSorted = sorted(plugin.Groups.items(), FirstItemSortCompare)
        for name, group in groupsSorted:
            name = name or _("General")
            groupPage = GroupPage(name, group)
            if not groupPage.Empty:
                self.RightWidget.append_page(groupPage.Widget, gtk.Label(name))
                self.Pages = self.Pages + [groupPage]
        
        self.ActionPage = ActionPage(self.Main.Context, plugin)
        if not self.ActionPage.Empty:
            self.RightWidget.append_page(self.ActionPage.Widget, gtk.Label(_("Actions")))
            self.Pages = self.Pages + [self.ActionPage]
        else:
            self.ActionPage = None
            self.RightWidget.connect('size-allocate', self.ResetFocus)

        self.Block = 0

    def ResetFocus(self, widget, data):
        pos = self.FilterEntry.get_position() 
        self.FilterEntry.grab_focus()
        self.FilterEntry.set_position(pos)

    def FilterChanged(self, widget):
        filter = widget.get_text().lower()
        if filter == "":
            filter = None

        groups = []

        for name, group in self.Plugin.Groups.items():
            name = name or _("General")
            groupPage = GroupPage(name, group, filter)
            if not groupPage.Empty:
                groups.append((name, groupPage))

        for page in self.RightWidget.get_children():
            label = self.RightWidget.get_tab_label(page).get_label()
            if label != _("Actions") and label != _("Error"):
                self.RightWidget.remove_page(self.RightWidget.page_num(page))
                page.destroy()

        for name, groupPage in groups:
            self.RightWidget.append_page(groupPage.Widget, gtk.Label(name))

        if self.ActionPage:
            self.ActionPage.Filter = filter
            self.ActionPage.UpdateTreeView()
            if self.ActionPage.Empty and self.ActionPage.Widget.get_parent():
                self.RightWidget.remove_page(self.RightWidget.page_num(self.ActionPage.Widget))
            elif not self.ActionPage.Empty and not self.ActionPage.Widget.get_parent():
                self.RightWidget.append_page(self.ActionPage.Widget, gtk.Label(_("Actions")))

        # Add
        if len(self.RightWidget.get_children()) == 0 and not self.NotFoundBox:
            self.NotFoundBox = NotFoundBox(filter)
            self.RightWidget.append_page(self.NotFoundBox, gtk.Label(_("Error")))
        # Update
        elif len(self.RightWidget.get_children()) == 1 and self.NotFoundBox:
            self.NotFoundBox.update(filter)
        # Cleanup
        elif len(self.RightWidget.get_children()) > 1 and self.NotFoundBox:
            self.RightWidget.remove_page(self.RightWidget.page_num(self.NotFoundBox))
            self.NotFoundBox.destroy()
            self.NotFoundBox = None

        self.RightWidget.show_all()

    def EnablePlugin(self, widget):
        if self.Block > 0:
            return
        self.Block += 1
        # attempt to resolve conflicts...
        conflicts = self.Plugin.Enabled and self.Plugin.DisableConflicts or self.Plugin.EnableConflicts
        conflict = PluginConflict(self.Plugin, conflicts)
        if conflict.Resolve():
            self.Plugin.Enabled = widget.get_active()
        else:
            widget.set_active(self.Plugin.Enabled)
        self.Plugin.Context.Write()
        self.Block = self.Block-1

# Filter Page
#
class FilterPage:
    def __init__(self, main, context):
        self.Context = context
        self.Main = main
        self.LeftWidget = gtk.VBox(False, 10)
        self.LeftWidget.set_border_width(15)
        self.RightWidget = gtk.Notebook()
        self.RightChild = gtk.VBox()

        # Image + Label
        filterLabel = Label()
        filterLabel.set_markup("<span color='%s' size='large' weight='800'>%s</span>" % (self.Main.Style.BrightColor, _("Filter")))
        filterImg = Image("search", ImageCategory, 64)
        self.LeftWidget.pack_start(filterImg, False, False)
        self.LeftWidget.pack_start(filterLabel, False, False)
        
        # Entry
        if has_sexy:
            filterEntry = sexy.IconEntry()
            filterEntry.add_clear_button()
        else:
            filterEntry = gtk.Entry()
        filterEntry.connect("changed", self.FilterChanged)
        self.LeftWidget.pack_start(filterEntry, False, False)

        # Search in...
        filterSearchLabel = Label()
        filterSearchLabel.set_markup("<span color='%s' size='large' weight='800'>%s</span>" % (self.Main.Style.BrightColor, _("Search in...")))
        self.LeftWidget.pack_start(filterSearchLabel, False, False)

        # Options
        self.FilterName = gtk.CheckButton()
        filterLabel = Label(_("Short description and name"))
        self.FilterName.add(filterLabel)
        self.FilterName.set_active(True)
        
        self.FilterLongDesc = gtk.CheckButton()
        filterLabel = Label(_("Long description"))
        self.FilterLongDesc.add(filterLabel)
        self.FilterLongDesc.set_active(True)
        
        self.FilterValue = gtk.CheckButton()
        filterLabel = Label(_("Settings value"))
        self.FilterValue.add(filterLabel)
        self.FilterValue.set_active(False)

        self.LeftWidget.pack_start(self.FilterName, False, False)
        self.LeftWidget.pack_start(self.FilterLongDesc, False, False)
        self.LeftWidget.pack_start(self.FilterValue, False, False)

        # Back Button
        self.BackButton = gtk.Button(gtk.STOCK_GO_BACK)
        self.BackButton.set_use_stock(True)
        self.BackButton.connect('clicked', self.Main.BackToMain)
        self.LeftWidget.pack_end(self.BackButton, False, False)

        self.FilteredPlugins = []
        self.Filter = None

        # Selector
        self.CurrentPlugin = None
        self.CurrentGroup = None
        self.CurrentSubGroup = None
        self.SelectorButtons = SelectorButtons()
        self.PluginBox = SelectorBox(main.Style.BackgroundColor)
        self.GroupBox = SelectorBox(main.Style.BackgroundColor)
        self.SubGroupBox = SelectorBox(main.Style.BackgroundColor)
        self.SelectorBoxes = gtk.HBox()
        self.SelectorBoxes.set_border_width(5)
        self.SelectorBoxes.set_spacing(5)
        self.PluginBox.set_no_show_all(True)
        self.GroupBox.set_no_show_all(True)
        self.SubGroupBox.set_no_show_all(True)
        self.SelectorBoxes.pack_start(self.PluginBox, False, False)
        self.SelectorBoxes.pack_start(self.GroupBox, False, False)
        self.SelectorBoxes.pack_start(self.SubGroupBox, False, False)
        self.RightChild.pack_start(self.SelectorButtons, False, False)
        self.RightChild.pack_start(self.SelectorBoxes, False, False)
        self.SettingsArea = gtk.ScrolledWindow()
        viewport = gtk.Viewport()
        self.SettingsBox = gtk.VBox()
        self.SettingsBox.set_border_width(5)
        self.SettingsBox.set_spacing(5)
        viewport.add(self.SettingsBox)
        self.SettingsArea.props.hscrollbar_policy = gtk.POLICY_NEVER
        self.SettingsArea.props.vscrollbar_policy = gtk.POLICY_AUTOMATIC
        self.SettingsArea.set_border_width(5)
        self.SettingsArea.add(viewport)
        self.SettingsArea.set_no_show_all(True)
        self.RightChild.pack_start(self.SettingsArea, True, True)

        self.ActionPage = ActionPage(self.Context)
        self.NotFoundBox = None

        # Notebook
        self.RightWidget.append_page(self.RightChild, gtk.Label(_("Settings")))
        self.RightWidget.append_page(self.ActionPage.Widget, gtk.Label(_("Actions")))

        self.FilterChanged(filterEntry)

    def UpdateBoxes(self):
        self.PluginBox.clear_list()
        self.GroupBox.clear_list()
        self.SubGroupBox.clear_list()
        for child in self.SettingsBox.get_children():
            child.destroy()

        singleGroup = None
        singleSubGroup = None
        
        # Plugins
        for plugin, groups in self.FilteredPlugins:
            if plugin.Enabled:
                self.PluginBox.add_item(plugin.ShortDesc, self.PluginChanged, "<b>%s</b>")
            else:
                self.PluginBox.add_item(plugin.ShortDesc, self.PluginChanged)
            # Groups
            if self.CurrentPlugin == plugin.ShortDesc:
                if len(groups) == 1:
                    singleGroup = groups[0][0]

                groupsSorted = sorted(groups, FirstItemSortCompare)
                for group, subGroups in groupsSorted:
                    self.GroupBox.add_item(group, self.GroupChanged)
                    # SubGroups
                    if self.CurrentGroup == group or singleGroup == group:
                        if len(subGroups) == 1:
                            singleSubGroup = subGroups[0][0]

                        subGroupsSorted = sorted(subGroups, FirstItemSortCompare)
                        for name, subGroup, settings in subGroupsSorted:
                            self.SubGroupBox.add_item(name, self.SubGroupChanged)
                            # Settings
                            if self.CurrentSubGroup == name:
                                sga = SubGroupArea('', subGroup, self.Filter)
                                self.SettingsBox.pack_start(sga.Widget, False, False)
                            elif self.CurrentSubGroup == None:
                                sga = SubGroupArea(name, subGroup, self.Filter)
                                self.SettingsBox.pack_start(sga.Widget, False, False)

        if len(self.FilteredPlugins) == 0:
            self.SelectorButtons.clear_buttons()
            self.CurrentPlugin = None
            self.CurrentGroup = None
            self.CurrentSubGroup = None

        self.PluginBox.hide()
        self.PluginBox.set_no_show_all(len(self.FilteredPlugins) == 0)
        self.GroupBox.hide()
        self.GroupBox.set_no_show_all(self.CurrentPlugin == None or singleGroup != None)
        self.SubGroupBox.hide()
        self.SubGroupBox.set_no_show_all(self.CurrentGroup == None or singleSubGroup != None)
        self.SettingsArea.hide()
        self.SettingsArea.set_no_show_all(len(self.SettingsBox.get_children()) == 0)

        self.RightChild.show_all()

        if self.CurrentPlugin != None and singleGroup != None:
            self.CurrentGroup = singleGroup

        if self.CurrentGroup != None and singleSubGroup != None:
            self.CurrentSubGroup = singleSubGroup

    def PluginChanged(self, widget, plugin):
        if self.CurrentSubGroup != None:
            self.SelectorButtons.remove_button(2)
        if self.CurrentGroup != None:
            self.SelectorButtons.remove_button(1)
        if self.CurrentPlugin != None:
            self.SelectorButtons.remove_button(0)

        self.CurrentPlugin = plugin
        self.CurrentGroup = None
        self.CurrentSubGroup = None

        self.SelectorButtons.add_button(plugin, self.PluginChanged)
        self.UpdateBoxes()

    def GroupChanged(self, widget, group):
        if self.CurrentSubGroup != None:
            self.SelectorButtons.remove_button(2)
        if self.CurrentGroup != None:
            self.SelectorButtons.remove_button(1)

        self.CurrentGroup = group
        self.CurrentSubGroup = None

        self.SelectorButtons.add_button(group or _("General"), self.GroupChanged)
        self.UpdateBoxes()

    def SubGroupChanged(self, widget, subGroup):
        if self.CurrentGroup != None:
            self.SelectorButtons.remove_button(2)

        self.CurrentSubGroup = subGroup

        self.SelectorButtons.add_button(subGroup or _("General"), self.SubGroupChanged)
        self.UpdateBoxes()

    def FilterChanged(self, widget):
        self.Filter = widget.get_text()
        runLevels = []
        if self.FilterName.get_active():
            runLevels.append(0)
        if self.FilterLongDesc.get_active():
            runLevels.append(1)
        if self.FilterValue.get_active():
            runLevels.append(2)
        plugins = []
        for plugin in sorted(self.Context.Plugins.values(), PluginSortCompare):
            groups = []
            for group in plugin.Groups:
                subGroups = []
                for name, subGroup in plugin.Groups[group].items():
                    settings = sum((v.values() for v in [subGroup.Display]+[subGroup.Screens[CurrentScreenNum]]), [])
                    settings = sorted(settings, SettingSortCompare)
                    settings = FilterSettings(settings, self.Filter, run=runLevels, noActions=True, singleRun=True)
                    if len(settings) > 0:
                        subGroups.append((name, subGroup, settings))
                if len(subGroups) > 0:
                    groups.append((group, subGroups))
            if len(groups) > 0:
                plugins.append((plugin, groups))

        self.FilteredPlugins = plugins
        self.UpdateBoxes()

        # No settings found, remove page
        if len(self.FilteredPlugins) == 0 and self.RightChild.get_parent():
            self.RightWidget.remove_page(self.RightWidget.page_num(self.RightChild))
        # Restore page
        elif len(self.FilteredPlugins) > 0 and not self.RightChild.get_parent():
            self.RightWidget.append_page(self.RightChild, gtk.Label(_("Settings")))

        self.ActionPage.Filter = self.Filter
        self.ActionPage.UpdateTreeView()
        # No actions found, remove page
        if self.ActionPage.Empty and self.ActionPage.Widget.get_parent():
            self.RightWidget.remove_page(self.RightWidget.page_num(self.ActionPage.Widget))
        # Restore page
        elif not self.ActionPage.Empty and not self.ActionPage.Widget.get_parent():
            self.RightWidget.append_page(self.ActionPage.Widget, gtk.Label(_("Actions")))

        # Nothing found
        if not self.RightChild.get_parent() and not self.ActionPage.Widget.get_parent():
            if self.NotFoundBox:
                self.NotFoundBox.update(self.Filter)
            else:
                self.NotFoundBox = NotFoundBox(self.Filter)
                self.RightWidget.append_page(self.NotFoundBox, gtk.Label(_("Error")))
        elif self.NotFoundBox:
            self.RightWidget.remove_page(self.RightWidget.page_num(self.NotFoundBox))
            self.NotFoundBox.destroy()
            self.NotFoundBox = None

        self.RightWidget.show_all()

# Profile and Backend Page
#
class ProfileBackendPage:
    def __init__(self, main, context):
        self.Context = context
        self.Main = main
        rightChild = gtk.VBox()
        rightChild.set_border_width(10)

        # Profiles
        profileBox = gtk.HBox()
        profileBox.set_spacing(5)
        profileAdd = gtk.Button()
        Tooltips.set_tip(profileAdd, _("Add a New Profile"))
        profileAdd.set_image(gtk.image_new_from_stock(gtk.STOCK_ADD, gtk.ICON_SIZE_BUTTON))
        profileRemove = gtk.Button()
        Tooltips.set_tip(profileRemove, _("Remove This Profile"))
        profileRemove.set_image(gtk.image_new_from_stock(gtk.STOCK_REMOVE, gtk.ICON_SIZE_BUTTON))
        self.ProfileComboBox = gtk.combo_box_new_text()
        self.ProfileComboBox.set_sensitive(self.Context.CurrentBackend.ProfileSupport)
        self.ProfileComboBox.append_text(_("Default"))
        for profile in self.Context.Profiles.values():
            self.ProfileComboBox.append_text(profile.Name)
        if self.Context.CurrentProfile.Name == '' or self.Context.CurrentProfile.Name == 'Default':
            self.ProfileComboBox.set_active(0)
        else:
            name = self.Context.CurrentProfile.Name
            index = self.Context.Profiles.values().index(self.Context.Profiles[name])
            self.ProfileComboBox.set_active(index+1)
        self.ProfileComboBox.connect("changed", self.ProfileChanged)
        profileAdd.connect("clicked", self.AddProfile)
        profileRemove.connect("clicked", self.RemoveProfile)
        profileBox.pack_start(self.ProfileComboBox, True, True)
        profileBox.pack_start(profileAdd, False, False)
        profileBox.pack_start(profileRemove, False, False)
        profileLabel = Label()
        profileLabel.set_markup("<span color='%s' size='large' weight='800'>%s</span>" % (self.Main.Style.BrightColor, _("Profile")))
        self.ProfileImportExportBox = gtk.HBox()
        self.ProfileImportExportBox.set_spacing(5)
        profileImportButton = gtk.Button(_("Import"))
        Tooltips.set_tip(profileImportButton, _("Import a CompizConfig Profile"))
        profileExportButton = gtk.Button(_("Export"))
        Tooltips.set_tip(profileExportButton, _("Export your CompizConfig Profile"))
        profileResetButton = gtk.Button(_("Reset to defaults"))
        Tooltips.set_tip(profileResetButton, _("Reset your CompizConfig Profile to the global defaults"))
        profileResetButton.set_image(gtk.image_new_from_stock(gtk.STOCK_CLEAR, gtk.ICON_SIZE_BUTTON))
        profileImportButton.set_image(gtk.image_new_from_stock(gtk.STOCK_OPEN, gtk.ICON_SIZE_BUTTON))
        profileExportButton.set_image(gtk.image_new_from_stock(gtk.STOCK_SAVE, gtk.ICON_SIZE_BUTTON))
        profileImportButton.connect("clicked", self.ImportProfile)
        profileExportButton.connect("clicked", self.ExportProfile)
        profileResetButton.connect("clicked", self.ResetProfile)
        self.ProfileImportExportBox.pack_start(profileImportButton, False, False)
        self.ProfileImportExportBox.pack_start(profileExportButton, False, False)
        self.ProfileImportExportBox.pack_start(profileResetButton, False, False)
        rightChild.pack_start(profileLabel, False, False, 5)
        rightChild.pack_start(profileBox, False, False, 5)
        rightChild.pack_start(self.ProfileImportExportBox, False, False, 5)

        # Backends
        backendBox = gtk.combo_box_new_text()
        for backend in self.Context.Backends.values():
            backendBox.append_text(backend.ShortDesc)
        name = self.Context.CurrentBackend.Name
        index = self.Context.Backends.values().index(self.Context.Backends[name])
        backendBox.set_active(index)
        backendBox.connect("changed", self.BackendChanged)
        backendLabel = Label()
        backendLabel.set_markup("<span color='%s' size='large' weight='800'>%s</span>" % (self.Main.Style.BrightColor, _("Backend")))
        rightChild.pack_start(backendLabel, False, False, 5)
        rightChild.pack_start(backendBox, False, False, 5)

        # Integration
        integrationLabel = Label()
        integrationLabel.set_markup("<span color='%s' size='large' weight='800'>%s</span>" % (self.Main.Style.BrightColor, _("Integration")))
        self.IntegrationButton = gtk.CheckButton(_("Enable integration into the desktop environment"))
        self.IntegrationButton.set_active(self.Context.Integration)
        self.IntegrationButton.set_sensitive(self.Context.CurrentBackend.IntegrationSupport)
        self.IntegrationButton.connect("toggled", self.IntegrationChanged)
        rightChild.pack_start(integrationLabel, False, False, 5)
        rightChild.pack_start(self.IntegrationButton, False, False, 5)

        self.Widget = rightChild
    
    def UpdateProfiles(self, default = _("Default")):
        self.Context.Read()
        self.Context.UpdateProfiles()

        self.ProfileComboBox.get_model().clear()
        self.ProfileComboBox.append_text(_("Default"))
        for profile in self.Context.Profiles.values():
            self.ProfileComboBox.append_text(profile.Name)

        index = -1
        counter = 0
        for m in self.ProfileComboBox.get_model():
            if m[0] == default:
                index=counter
            counter += 1
        if index >= 0:
            self.ProfileComboBox.set_active(index)

    def IntegrationChanged(self, widget):
        value = widget.get_active()
        self.Context.Integration = value

    def ProfileChanged(self, widget):
        name = widget.get_active_text()
        if not name or not self.Context.Profiles.has_key(name) or name == _("Default"):
            self.Context.ResetProfile()
        else:
            self.Context.CurrentProfile = self.Context.Profiles[name]
        
        self.Context.Read()
        self.Context.Write()

    def CreateFilter(self, chooser):
        filter = gtk.FileFilter()
        filter.add_pattern("*.profile")
        filter.set_name(_("Profiles (*.profile)"))
        chooser.add_filter(filter)

        filter = gtk.FileFilter()
        filter.add_pattern("*")
        filter.set_name(_("All files"))
        chooser.add_filter(filter)

    def ResetProfile(self, widget):
        
        for plugin in self.Context.Plugins.values():
            settings = sum((v.values() for v in [plugin.Display]+[plugin.Screens[CurrentScreenNum]]), [])
            for setting in settings:
                setting.Reset()

        activePlugins = self.Context.Plugins['core'].Display['active_plugins'].Value
        for plugin in self.Context.Plugins.values():
            plugin.Enabled = plugin.Name in activePlugins
        self.Context.Write()
    
    def ExportProfile(self, widget):
        b = (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_SAVE, gtk.RESPONSE_OK)
        chooser = gtk.FileChooserDialog(title=_("Save file.."), parent=self.Main, buttons=b, action=gtk.FILE_CHOOSER_ACTION_SAVE)
        chooser.set_current_folder(os.environ.get("HOME"))
        self.CreateFilter(chooser)
        ret = chooser.run()

        path = chooser.get_filename()
        chooser.destroy()
        if ret == gtk.RESPONSE_OK:
            self.Context.Export(path)

    def ImportProfile(self, widget):
        b = (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OPEN, gtk.RESPONSE_OK)
        chooser = gtk.FileChooserDialog(title=_("Open file.."), parent=self.Main, buttons=b)
        chooser.set_current_folder(os.environ.get("HOME"))
        self.CreateFilter(chooser)
        ret = chooser.run()

        path = chooser.get_filename()
        chooser.destroy()
        if ret == gtk.RESPONSE_OK:
            self.Context.Import(path)

    def AddProfile(self, widget):
        dlg = gtk.Dialog(_("Enter a profile name"), self.Main, gtk.DIALOG_MODAL)
        dlg.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL)
        dlg.add_button(gtk.STOCK_OK, gtk.RESPONSE_OK)
        
        entry = gtk.Entry()
        label = gtk.Label(_("Please enter a name for the new profile:"))
        dlg.vbox.pack_start(label, False, False, 5)
        dlg.vbox.pack_start(entry, False, False, 5)

        dlg.set_size_request(340, 120)
        dlg.show_all()
        ret = dlg.run()
        if ret == gtk.RESPONSE_OK:
            self.Context.CurrentProfile = ccs.Profile(self.Context, entry.get_text())
            self.UpdateProfiles(entry.get_text())
        dlg.destroy()
    
    def RemoveProfile(self, widget):
        name = self.ProfileComboBox.get_active_text()
        if name != _("Default"):
            self.Context.ResetProfile()
            self.Context.Profiles[name].Delete()
            self.UpdateProfiles()
    
    def BackendChanged(self, widget):
        shortDesc = widget.get_active_text()
        name = ""
        for backend in self.Context.Backends.values():
            if backend.ShortDesc == shortDesc:
                name = backend.Name
                break
        
        if name != "":
            self.Context.ResetProfile()
            self.Context.CurrentBackend = self.Context.Backends[name]
            self.UpdateProfiles()
        else:
            raise Exception, _("Backend not found.")

        self.ProfileComboBox.set_sensitive(self.Context.CurrentBackend.ProfileSupport)
        self.IntegrationButton.set_sensitive(self.Context.CurrentBackend.IntegrationSupport)

# Plugin List Page
#
class PluginListPage:
    def __init__(self, main, context):
        self.Context = context
        self.Main = main
        self.Blocked = False
        rightChild = gtk.VBox()
        rightChild.set_border_width(10)
        
        # Auto sort
        autoSort = gtk.CheckButton(_("Automatic plugin sorting"))
        rightChild.pack_start(autoSort, False, False, 10)
        
        # Lists
        listBox = gtk.HBox()
        listBox.set_spacing(5)

        self.DisabledPluginsList = ScrolledList(_("Disabled Plugins"))
        self.EnabledPluginsList = ScrolledList(_("Enabled Plugins"))

        # Left/Right buttons
        buttonBox = gtk.VBox()
        buttonBox.set_spacing(5)
        boxAlignment = gtk.Alignment(0.0, 0.5, 0.0, 0.0)
        boxAlignment.add(buttonBox)

        rightButton = gtk.Button()
        rightImage = Image(gtk.STOCK_GO_FORWARD, ImageStock, gtk.ICON_SIZE_BUTTON)
        rightButton.set_image(rightImage)
        rightButton.connect("clicked", self.EnablePlugins)

        leftButton = gtk.Button()
        leftImage = Image(gtk.STOCK_GO_BACK, ImageStock, gtk.ICON_SIZE_BUTTON)
        leftButton.set_image(leftImage)
        leftButton.connect("clicked", self.EnabledPluginsList.delete)

        buttonBox.pack_start(rightButton, False, False)
        buttonBox.pack_start(leftButton, False, False)

        # Up/Down buttons
        enabledBox = gtk.VBox()
        enabledBox.set_spacing(10)

        enabledAlignment = gtk.Alignment(0.5, 0.0, 0.0, 0.0)
        enabledButtonBox = gtk.HBox()
        enabledButtonBox.set_spacing(5)
        enabledAlignment.add(enabledButtonBox)

        upButton = gtk.Button(gtk.STOCK_GO_UP)
        downButton = gtk.Button(gtk.STOCK_GO_DOWN)
        upButton.set_use_stock(True)
        downButton.set_use_stock(True)
        upButton.connect('clicked', self.EnabledPluginsList.move_up)
        downButton.connect('clicked', self.EnabledPluginsList.move_down)

        # Add buttons
        addButton = gtk.Button(gtk.STOCK_ADD)
        addButton.set_use_stock(True)
        addButton.connect('clicked', self.AddPlugin)

        enabledButtonBox.pack_start(addButton, False, False)
        enabledButtonBox.pack_start(upButton, False, False)
        enabledButtonBox.pack_start(downButton, False, False)

        enabledBox.pack_start(self.EnabledPluginsList, True, True)
        enabledBox.pack_start(enabledAlignment, False, False)

        listBox.pack_start(self.DisabledPluginsList, True, True)
        listBox.pack_start(boxAlignment, True, False)
        listBox.pack_start(enabledBox, True, True)

        self.UpdateEnabledPluginsList()
        self.UpdateDisabledPluginsList()

        # Connect Store
        self.EnabledPluginsList.store.connect('row-changed', self.ListChanged)
        self.EnabledPluginsList.store.connect('row-deleted', self.ListChanged)
        self.EnabledPluginsList.store.connect('rows-reordered', self.ListChanged)

        rightChild.pack_start(listBox, True, True)

        # Auto sort
        autoSort.connect('toggled', self.AutoSortChanged)
        autoSort.set_active(self.Context.AutoSort)

        self.Widget = rightChild

    def AutoSortChanged(self, widget):
        self.Context.AutoSort = widget.get_active()
        self.EnabledPluginsList.set_sensitive(not self.Context.AutoSort)
        self.DisabledPluginsList.set_sensitive(not self.Context.AutoSort)

    def UpdateEnabledPluginsList(self):
        activePlugins = self.Context.Plugins['core'].Display['active_plugins'].Value
        
        self.EnabledPluginsList.clear()

        for name in activePlugins:
            self.EnabledPluginsList.append(name)

    def UpdateDisabledPluginsList(self):
        activePlugins = self.Context.Plugins['core'].Display['active_plugins'].Value

        self.DisabledPluginsList.clear()

        for plugin in sorted(self.Context.Plugins.values(), PluginSortCompare):
            if not plugin.Name in activePlugins and plugin.Name != "core":
                self.DisabledPluginsList.append(plugin.Name)

    def AddPlugin(self, widget):
        dlg = gtk.Dialog(_("Add plugin"))
        dlg.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL)
        dlg.add_button(gtk.STOCK_OK, gtk.RESPONSE_OK).grab_default()
        dlg.set_default_response(gtk.RESPONSE_OK)
        
        ebox = gtk.EventBox()
        label = gtk.Label(_("Plugin name:"))
        ebox.add(label)
        
        Tooltips.set_tip(ebox, _("Insert plugin name"))
        dlg.vbox.pack_start(ebox)
        
        entry = gtk.Entry()
        entry.props.activates_default = True
        dlg.vbox.pack_start(entry)

        dlg.vbox.set_spacing(5)
        
        dlg.vbox.show_all()
        ret = dlg.run()
        dlg.destroy()

        if ret == gtk.RESPONSE_OK:
            self.EnabledPluginsList.append(entry.get_text())

    def EnablePlugins(self, widget):
        selectedRows = self.DisabledPluginsList.select.get_selected_rows()[1]
        for path in selectedRows:
            iter = self.DisabledPluginsList.store.get_iter(path)
            name = self.DisabledPluginsList.store.get(iter, 0)[0]
            self.EnabledPluginsList.append(name)
        self.DisabledPluginsList.delete(widget)
    
    def ListChanged(self, *args, **kwargs):
        if self.Blocked:
            return
        self.Blocked = True
        plugins = self.EnabledPluginsList.get_list()

        self.Context.Plugins['core'].Display['active_plugins'].Value = plugins
        self.Context.Write()
        self.UpdateDisabledPluginsList()
        self.Blocked = False

# Preferences Page
#
class PreferencesPage:
    def __init__(self, main, context):
        self.Context = context
        self.Main = main
        self.LeftWidget = gtk.VBox(False, 10)
        self.LeftWidget.set_border_width(15)
        self.RightWidget = gtk.Notebook()

        # Left Pane
        self.DescLabel = Label()
        self.DescLabel.set_markup("<span color='%s' size='large' weight='800'>%s</span>" % (self.Main.Style.BrightColor, _("Preferences")))
        self.DescImg = Image("profiles",ImageCategory, 64)
        self.LeftWidget.pack_start(self.DescImg, False, False)
        self.LeftWidget.pack_start(self.DescLabel, False, False)
        self.InfoLabelCont = gtk.HBox()
        self.InfoLabelCont.set_border_width(10)
        self.LeftWidget.pack_start(self.InfoLabelCont, False, False)
        self.InfoLabel = Label(_("Configure the backend, profile and other internal settings used by the Compiz Configuration System."), 180)
        self.InfoLabelCont.pack_start(self.InfoLabel, True, True)

        # About Button
        aboutLabel = Label()
        aboutLabel.set_markup("<span color='%s' size='large' weight='800'>%s</span>" % (self.Main.Style.BrightColor, _("About")))
        aboutButton = gtk.Button()
        aboutButton.set_relief(gtk.RELIEF_NONE)
        aboutImage = Image(gtk.STOCK_ABOUT, ImageStock, gtk.ICON_SIZE_BUTTON)
        aboutFrame = gtk.HBox()
        aboutFrame.set_spacing(5)
        aboutFrame.pack_start(aboutImage, False, False)
        aboutFrame.pack_start(Label(_("About CCSM...")), False, False)
        aboutButton.add(aboutFrame)
        Tooltips.set_tip(aboutButton, _("About"))
        aboutButton.connect('clicked', self.ShowAboutDialog)
        aboutBin = gtk.HBox()
        aboutBin.set_border_width(10)
        aboutBin.pack_start(aboutButton, False, False)
        self.LeftWidget.pack_start(aboutLabel, False, False)
        self.LeftWidget.pack_start(aboutBin, False, False)
    
        # Back Button
        backButton = gtk.Button(gtk.STOCK_GO_BACK)
        backButton.set_use_stock(True)
        backButton.connect('clicked', self.Main.BackToMain)
        self.LeftWidget.pack_end(backButton, False, False)

        # Profile & Backend Page
        self.ProfileBackendPage = ProfileBackendPage(main, context)
        self.RightWidget.append_page(self.ProfileBackendPage.Widget, gtk.Label(_("Profile & Backend")))

        # Plugin List
        self.PluginListPage = PluginListPage(main, context)
        self.RightWidget.append_page(self.PluginListPage.Widget, gtk.Label(_("Plugin List")))

    def ShowAboutDialog(self, widget):
        about = AboutDialog()
        about.show_all()
        about.run()
        about.destroy()

# Page
#
class Page:
    def __init__(self):
        self.Widget = gtk.VBox()
        self.SetContainer = gtk.VBox()
        
        scroll = gtk.ScrolledWindow()
        scroll.props.hscrollbar_policy = gtk.POLICY_NEVER
        scroll.props.vscrollbar_policy = gtk.POLICY_AUTOMATIC
        
        view = gtk.Viewport()
        view.set_border_width(5)
        view.set_shadow_type(gtk.SHADOW_NONE)
        
        scroll.add(view)
        view.add(self.SetContainer)
        self.Widget.pack_start(scroll, True, True)
        
        self.Empty = True

# Group Page
#
class GroupPage(Page):
    def __init__(self, name, group, filter=None):
        Page.__init__(self)

        self.subGroupAreas = []

        if (group.has_key('')):
            sga = SubGroupArea('', group[''], filter)
            if not sga.Empty:
                self.SetContainer.pack_start(sga.Widget, False, False)
                self.Empty = False
                self.subGroupAreas = self.subGroupAreas + [sga]

        subGroupsSorted = sorted(group.keys(), cmp)
        for subGroup in subGroupsSorted:
            if not subGroup == '':
                sga = SubGroupArea(subGroup, group[subGroup], filter)
                if not sga.Empty:
                    self.SetContainer.pack_start(sga.Widget, False, False)
                    self.Empty = False
                    self.subGroupAreas = self.subGroupAreas + [sga]


syntax highlighted by Code2HTML, v. 0.9.1