#!/usr/bin/env python
# -*- mode: python; coding: utf-8 -*-

#  PycaWM
#  pycagenconf
#  Copyright (c) 2007-2008 Vincent Rasneur, Anaël Verrier

#  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; version 3 only.

#  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 St, Fifth Floor, Boston, MA  02110-1301  USA

import sys

if sys.version_info < (2, 5):
    raise ImportError('pycagenconf needs at least Python 2.5!')

import pygtk
pygtk.require('2.0')
import gtk

# needed for using ComboBoxes
if gtk.pygtk_version < (2, 4):
    raise ImportError('pycagenconf needs at least PyGTK 2.4!')

from inspect import currentframe
from os.path import expanduser, isfile

from pycawm import PycaWM
from pycawm.misc import (get_args_names, has_default_arg, get_default_arg,
                         is_arg_varargs, is_arg_varkwargs, find_default_plugins,
                         find_default_focuses, find_default_placements,
                         find_default_buttons)

def this_list():
    """Get the list comprehension being built.
    Handle nested list comprehensions too.
    Code taken and modified from the Python Cookbook"""
    f_locals = currentframe(1).f_locals
    nested_level = 0
    while '_[%d]' % (nested_level + 1) in f_locals:
        nested_level += 1
    if not nested_level:
        raise NameError('no list comprehension being currently built!')
    return f_locals['_[%d]' % nested_level]

def run_dialog(title=None, parent=None, message_format=None,
               type_=None, buttons=None):
    dialog = gtk.MessageDialog(
        parent=parent, flags=gtk.DIALOG_DESTROY_WITH_PARENT,
        type=type_, message_format=message_format, buttons=buttons)
    dialog.set_title(title)
    response = dialog.run()
    dialog.destroy()
    return response

def run_info_dialog(parent=None, message_format=None):
    return run_dialog(title='Info', parent=parent,
                      type_=gtk.MESSAGE_INFO, message_format=message_format,
                      buttons=gtk.BUTTONS_CLOSE)

def run_error_dialog(parent=None, message_format=None):
    return run_dialog(title='Error in pycagenconf', parent=parent,
                      type_=gtk.MESSAGE_ERROR, message_format=message_format,
                      buttons=gtk.BUTTONS_CLOSE)

def run_question_dialog(parent=None, message_format=None):
    return run_dialog(title='Question', parent=parent,
                      type_=gtk.MESSAGE_QUESTION, message_format=message_format,
                      buttons=gtk.BUTTONS_YES_NO)

def make_label_with_right_alignment(title):
    label = gtk.Label(str=title + ':  ')
    label.set_alignment(1.0, 0.5)
    return label

def generate_header():
    return """#!/usr/bin/env python
# -*- coding: utf-8 -*-

# Config file generated by the program `pycagenconf'.
# Edit by hand at your own risk!

from pycawm import PycaWM, Client"""

def generate_footer():
    return """# Manage all the existing clients connected to the wm display.
%(wm_name)s.manage_existing_windows()
# Run the wm event loop.
%(wm_name)s.event_loop()
"""

def format_fun_arg(fun, arg):
    if is_arg_varargs(fun, arg):
        return ('*' + arg, '')
    elif is_arg_varkwargs(fun, arg):
        return ('**' + arg, '')
    elif not has_default_arg(fun, arg):
        return (arg, '')
    return (arg, repr(get_default_arg(fun, arg)))

def format_fun_val(fun, arg, val):
    if is_arg_varargs(fun, arg):
        return '*' + val
    elif is_arg_varkwargs(fun, arg):
        return '**' + val
    elif not has_default_arg(fun, arg):
        return val
    return arg + '=' + val


class Locations(object):
    """Location indexes which indicate where to put the code"""

    class __metaclass__(type):
        def __new__(cls, name, bases, classdict):
            for i, location in enumerate(classdict['locations']):
                classdict[location] = i
            return type.__new__(cls, name, bases, classdict)

    locations = ['imports', 'main', 'keybindings',
                 'buttons', 'menus', 'misc',
                 'plugins', 'theme', 'final']
    nb_locations = len(locations)

class BasePage(object):
    label = None
    pages = list()

    class __metaclass__(type):
        """Metaclass to add a Page in the generator notebook at class definition
        time."""
        def __new__(cls, name, bases, classdict):
            gen_cls = type.__new__(cls, name, bases, classdict)
            if gen_cls.label is not None:
                BasePage.pages.append(gen_cls())
            return gen_cls

    def __new__(cls, *args, **kwargs):
        page = object.__new__(cls, *args, **kwargs)
        # set the needed instance attributes here, to not have to call
        # BasePage.__init__ in each derived class
        page.frame = gtk.Frame()
        page.frame_vbox = gtk.VBox(False, 0)
        page.frame_vbox.set_border_width(3)
        page.frame.add(page.frame_vbox)
        return page

    # default constructor for the pages
    # will be overrided in all the pages...
    def __init__(self):
        self.frame_vbox.add(gtk.Label(str='Not implemented!'))        

    @staticmethod
    def show_obj_name(_, cell, store, iter_):
        obj = store.get_value(iter_, 0)
        cell.set_property('text', obj.__name__)

    def generate_vars(self, _):
        pass

    def generate_code(self, _):
        pass

class MainPage(BasePage):
    label = 'main'

    def __init__(self):
        self.main_vars = dict()

        table = gtk.Table(rows=4, columns=2)
        for i, title in enumerate(('config path', 'PycaWM instance name',
                                   'number of desktops', 'display name')):
            entry_label = make_label_with_right_alignment(title)
            table.attach(entry_label, 0, 1, i, i + 1,
                         xoptions=gtk.SHRINK|gtk.FILL)
        entries = (('config_path', '~/.config/PycaWM/config.py'),
                   ('wm_name', 'pika'),
                   ('nb_desktops', repr(get_default_arg(PycaWM.__init__,
                                                        'nb_desktops'))),
                   ('display_name', repr(get_default_arg(PycaWM.__init__,
                                                         'display_name'))))
        for i, (entry_name, entry_text) in enumerate(entries):
            entry = gtk.Entry()
            entry.set_text(entry_text)
            self.main_vars[entry_name] = entry.get_text
            table.attach(entry, 1, 2, i, i + 1)
        self.frame_vbox.pack_start(table, expand=False)

    def generate_vars(self, gen_vars):
        for var, gen_val in self.main_vars.iteritems():
            gen_vars[var] = gen_val()

    def generate_code(self, gen_code):
        gen_code[Locations.main].append(
            '%(wm_name)s = PycaWM(nb_desktops=%(nb_desktops)s, '
            'display_name=%(display_name)s)')

class KeyBindingsPage(BasePage):
    label = 'key bindings'
    empty_modifier = '-'
    modifiers = dict(Control='C', Mod1='M', Shift='S')

    def __init__(self):
        self.keys_vars = (list(), list())
        # keyboard shortcuts types
        self.key_types = ('root window', 'client window')
        # functions examples for the shortcuts
        self.fun_examples = (['lambda wm: wm.run_command(\'xkill\')',
                              'PycaWM.quit',
                              'PycaWM.restart'],
                             ['Client.iconify',
                              'Client.close',
                              'lambda client: client.move(dict(x=0, y=0))'])

        vpaned = gtk.VPaned()
        self.frame_vbox.add(vpaned)
        vboxes = [gtk.VBox(False, 0) for _ in range(2)]
        for i, vbox in enumerate(vboxes):
            vpaned.__getattribute__('pack%d' % (i + 1))(vbox, resize=False)
        for i, title in enumerate([('Add a %s keyboard shortcut' %
                                    key_type) for key_type in self.key_types]):
            gtk_button = gtk.Button(title)
            vboxes[i].pack_start(gtk_button, expand=False)
            scrolled = gtk.ScrolledWindow()
            scrolled.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
            keys_vbox = gtk.VBox(False, 0)
            scrolled.add_with_viewport(keys_vbox)
            gtk_button.connect('clicked', self.add_key, keys_vbox, i)
            vboxes[i].add(scrolled)

    def add_key(self, _, vbox, nb):
        frame = gtk.Frame('%s shortcut' % self.key_types[nb])
        table = gtk.Table(4, 2)
        frame.add(table)
        for i, title in enumerate(('modifiers', 'key', 'function')):
            key_label = make_label_with_right_alignment(title)
            table.attach(key_label, 0, 1, i, i + 1,
                         xoptions=gtk.SHRINK|gtk.FILL)
        modifiers_hbox = gtk.HBox(False, 0)
        modifiers_combos = [gtk.combo_box_new_text() for _ in range(3)]
        for i, combo in enumerate(modifiers_combos):
            combo.append_text(self.empty_modifier)
            for mod in self.modifiers:
                combo.append_text(mod)
            combo.set_active(0)
            combo.connect('changed', self.change_modifier, modifiers_combos, i)
            modifiers_hbox.pack_start(combo, expand=False)
        table.attach(modifiers_hbox, 1, 2, 0, 1)
        key_entry = gtk.Entry()
        table.attach(key_entry, 1, 2, 1, 2)
        fun_combobox = gtk.combo_box_entry_new_text()
        for fun in self.fun_examples[nb]:
            fun_combobox.append_text(fun)
        table.attach(fun_combobox, 1, 2, 2, 3)
        keys_vars = self.keys_vars[nb]
        key_val = ([combo.get_active_text for combo in modifiers_combos],
                   key_entry.get_text,
                   fun_combobox.get_active_text)
        keys_vars.append(key_val)
        remove = gtk.Button('remove shortcut')
        remove.connect('clicked', self.remove_key, vbox, frame,
                       keys_vars, key_val)
        table.attach(remove, 0, 2, 3, 4, 10, 0)
        vbox.pack_start(frame, expand=False)
        self.frame.show_all()

    def remove_key(self, _, vbox, frame, keys_vars, key_val):
        vbox.remove(frame)
        keys_vars.remove(key_val)

    def change_modifier(self, combo, combos, nb):
        active = combo.get_active_text()
        if active is None or active == self.empty_modifier:
            return

        remaining_combos = [combo for i, combo in enumerate(combos) if i != nb]
        for rcombo in remaining_combos:
            ractive = rcombo.get_active_text()
            if ractive is None:
                continue

            # if same modifier as the current combo, unselect it
            if ractive == active:
                rcombo.set_active(0)

    def generate_code(self, gen_code):
        for i in range(2):
            for mods, key, fun in self.keys_vars[i]:
                key = key()
                fun = fun()
                mods = [self.modifiers[mod] for mod in (mod() for mod in mods)
                        if mod is not None and mod != self.empty_modifier]
                if not (key and fun and mods):
                    continue

                key_str = ('%s.add_key(\'%s-%s\', %s)' %
                           ('%(wm_name)s' if i == 0 else 'Client',
                            '-'.join(mods), key, fun))
                gen_code[Locations.keybindings].append(key_str)

class ButtonsPage(BasePage):
    label = 'buttons'

    def __init__(self):
        self.buttons_vars = (list(), list())

        self.store = gtk.ListStore(object,)
        for button in find_default_buttons():
            self.store.append((button,))
        cell = gtk.CellRendererText()

        vpaned = gtk.VPaned()
        self.frame_vbox.add(vpaned)
        vboxes = [gtk.VBox(False, 0) for _ in range(2)]
        for i, vbox in enumerate(vboxes):
            vpaned.__getattribute__('pack%d' % (i + 1))(vbox, resize=False)
        for i, title in enumerate(('Add this button on the left',
                                   'Add this button on the right')):
            hbox = gtk.HBox(False, 0)
            vboxes[i].pack_start(hbox, expand=False)
            scrolled = gtk.ScrolledWindow()
            scrolled.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
            buttons_vbox = gtk.VBox(False, 0)
            scrolled.add_with_viewport(buttons_vbox)
            vboxes[i].add(scrolled)
            buttons = gtk.ComboBox(self.store)
            buttons.pack_start(cell, True)
            buttons.set_cell_data_func(cell, self.show_obj_name)
            buttons.set_active(0)
            hbox.add(buttons)
            gtk_button = gtk.Button(title)
            gtk_button.connect('clicked', self.add_button, buttons,
                               buttons_vbox, self.buttons_vars[i])
            hbox.add(gtk_button)

    def add_button(self, _, buttons, vbox, buttons_vars):
        active = buttons.get_active()
        if active < 0:
            return

        button = self.store[active][0]
        frame = gtk.Frame(button.__name__)
        # skip "self" in the button's _init__ args
        args = get_args_names(button.__init__)[1:]
        table_len = len(args) + 1
        table = gtk.Table(table_len, 2)
        frame.add(table)
        # I need to keep the order between the args
        # use a list instead of a dict
        button_vars = list()
        for i, arg in enumerate(args):
            label_str, entry_str = format_fun_arg(button.__init__, arg)
            entry_label = make_label_with_right_alignment(label_str)
            table.attach(entry_label, 0, 1, i, i + 1,
                         xoptions=gtk.SHRINK|gtk.FILL)
            entry = gtk.Entry()
            entry.set_text(entry_str)
            button_vars.append((arg, entry.get_text))
            table.attach(entry, 1, 2, i, i + 1)
        button_val = (button, button_vars)
        buttons_vars.append(button_val)
        remove = gtk.Button('remove button')
        remove.connect('clicked', self.remove_button, vbox, frame,
                       buttons_vars, button_val)
        table.attach(remove, 0, 2,
                     table_len - 1, table_len, 10, 0)
        vbox.pack_start(frame, expand=False)
        self.frame.show_all()

    @staticmethod
    def remove_button(_, vbox, frame, buttons_vars, button_val):
        vbox.remove(frame)
        buttons_vars.remove(button_val)

    def generate_code(self, gen_code):
        if not (self.buttons_vars[0] or self.buttons_vars[1]):
            return

        gen_code[Locations.imports].append(
            # no button duplicates in the imports
            ('from pycawm.buttons import ' +
             ', '.join([button.__name__ for i in (0, 1)
                        for button, _ in self.buttons_vars[i] 
                        if button.__name__ not in this_list()])))

        for i, side in enumerate(('left', 'right')):
            for button, button_vars in self.buttons_vars[i]:
                code_str = '%(wm_name)s.add_' + side + '_button('
                code_str += button.__name__ + '('
                code_str += ', '.join([format_fun_val(button.__init__,
                                                      var, gen_val())
                                       for var, gen_val in button_vars])
                code_str += '))'
                gen_code[Locations.buttons].append(code_str)

class MenusPage(BasePage):
    label = 'menus'

class MiscPage(BasePage):
    label = 'misc'

    def __init__(self):
        table = gtk.Table(rows=2, columns=3)
        for i, title in enumerate(('default focus', 'default placements',
                                   'wallpaper path')):
            entry_label = make_label_with_right_alignment(title)
            table.attach(entry_label, 0, 1, i, i + 1,
                         xoptions=gtk.SHRINK|gtk.FILL)

        self.focuses = gtk.combo_box_new_text()
        for focus in find_default_focuses():
            self.focuses.append_text(repr(focus.__name__,))
        self.focuses.set_active(0)
        table.attach(self.focuses, 1, 2, 0, 1)

        placements_vbox = gtk.VBox(False, 0)
        placements_hbox = gtk.HBox(False, 0)
        self.available_placements = gtk.combo_box_new_text()
        for placement in find_default_placements():
            self.available_placements.append_text(repr(placement.__name__,))
        self.available_placements.set_active(0)
        add_button = gtk.Button('add placement')
        placements_hbox.add(self.available_placements)
        placements_hbox.add(add_button)
        self.placements_store = gtk.ListStore(str,)
        placements_treeview = gtk.TreeView(self.placements_store)
        cell = gtk.CellRendererText()
        column = gtk.TreeViewColumn()
        column.pack_start(cell, True)
        column.add_attribute(cell, 'text', 0)
        placements_treeview.append_column(column)
        placements_treeview.set_headers_visible(False)
        placements_treeview.set_reorderable(True)
        remove_button = gtk.Button('remove placement')
        remove_button.set_sensitive(False)
        add_button.connect('clicked', self.add_placement, remove_button)
        remove_button.connect('clicked', self.remove_placement,
                              placements_treeview)
        placements_treeview.get_selection().connect(
            'changed', self.select_placement, remove_button)
        placements_vbox.add(placements_hbox)
        placements_vbox.add(placements_treeview)
        placements_vbox.add(remove_button)
        table.attach(placements_vbox, 1, 2, 1, 2)

        self.wallpaper_path = gtk.Entry()
        table.attach(self.wallpaper_path, 1, 2, 2, 3)

        self.frame_vbox.pack_start(table, expand=False)

    def add_placement(self, _, remove_button):
        placement = self.available_placements.get_active_text()
        active = self.available_placements.get_active()
        if placement is None or active < 0:
            return

        self.placements_store.append([placement])
        self.available_placements.remove_text(active)
        self.available_placements.set_active(0)

    def remove_placement(self, remove_button, treeview):
        _, iter_ = treeview.get_selection().get_selected()
        if iter_ is None:
            return

        placement = self.placements_store[iter_][0]
        self.placements_store.remove(iter_)
        self.available_placements.append_text(placement)
        self.available_placements.set_active(0)

    def select_placement(self, selection, remove_button):
        _, iter_ = selection.get_selected()
        remove_button.set_sensitive(iter_ is not None)

    def generate_vars(self, gen_vars):
        focus = self.focuses.get_active_text()
        if focus is None:
            return

        gen_vars['focus'] = focus
        gen_vars['placements'] = ('[%s]' %
                                  ', '.join([row[0]
                                             for row in self.placements_store]))
        gen_vars['wallpaper_path'] = self.wallpaper_path.get_text()

    def generate_code(self, gen_code):
        gen_code[Locations.misc].append('%(wm_name)s.set_focus(%(focus)s)')
        if self.placements_store.get_iter_first() is not None:
            gen_code[Locations.misc].append(
                '%(wm_name)s.set_placements(%(placements)s)')
        if self.wallpaper_path.get_text():
            gen_code[Locations.misc].append(
                '%(wm_name)s.set_wallpaper(%(wallpaper_path)s)')

class PluginsPage(BasePage):
    label = 'plugins'

    def __init__(self):
        self.plugins_vars = list()

        hbox = gtk.HBox(False, 0)
        self.frame_vbox.pack_start(hbox, expand=False)
        plugins_scrolled = gtk.ScrolledWindow()
        plugins_scrolled.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
        plugins_vbox = gtk.VBox(False, 0)
        plugins_scrolled.add_with_viewport(plugins_vbox)
        self.frame_vbox.add(plugins_scrolled)
        button = gtk.Button('Add this plugin')
        button.connect('clicked', self.add_plugin, plugins_vbox)
        self.store = gtk.ListStore(object,)
        for plugin in find_default_plugins():
            self.store.append((plugin,))
        self.plugins = gtk.ComboBox(self.store)
        cell = gtk.CellRendererText()
        self.plugins.pack_start(cell, True)
        self.plugins.set_cell_data_func(cell, self.show_obj_name)
        self.plugins.set_active(0)
        hbox.add(self.plugins)
        hbox.add(button)

    def add_plugin(self, _, vbox):
        active = self.plugins.get_active()
        if active < 0:
            return

        plugin = self.store[active][0]
        frame = gtk.Frame(plugin.__name__)
        # skip "self" and "wm" in the plugin's _init__ args
        args = get_args_names(plugin.__init__)[2:]
        table_len = len(args) + 1
        table = gtk.Table(table_len, 2)
        frame.add(table)
        plugin_vars = dict()
        for i, arg in enumerate(args):
            entry_label = make_label_with_right_alignment(arg)
            table.attach(entry_label, 0, 1, i, i + 1,
                         xoptions=gtk.SHRINK|gtk.FILL)
            entry = gtk.Entry()
            entry.set_text(repr(get_default_arg(plugin.__init__, arg)))
            plugin_vars[arg] = entry.get_text
            table.attach(entry, 1, 2, i, i + 1)
        plugin_val = (plugin.__name__, plugin_vars)
        self.plugins_vars.append(plugin_val)
        remove = gtk.Button('remove plugin')
        remove.connect('clicked', self.remove_plugin, vbox, frame, plugin_val)
        table.attach(remove, 0, 2,
                     table_len - 1, table_len, 10, 0)
        vbox.pack_start(frame, expand=False)
        self.frame.show_all()

    def remove_plugin(self, _, vbox, frame, plugin_val):
        vbox.remove(frame)
        self.plugins_vars.remove(plugin_val)

    def generate_code(self, gen_code):
        if not self.plugins_vars:
            return

        gen_code[Locations.imports].append(
            # no plugin duplicates in the imports
            ('from pycawm.plugins import ' +
             ', '.join([plugin_name for plugin_name, _ in self.plugins_vars
                        if plugin_name not in this_list()])))

        for plugin_name, plugin_vars in self.plugins_vars:
            code_str = '%(wm_name)s.add_plugin(' + plugin_name
            if plugin_vars:
                code_str += (', dict(' +
                             ', '.join([var + '=' + gen_val()
                                        for var, gen_val
                                        in plugin_vars.iteritems()]) + ')')
            code_str += ')'
            gen_code[Locations.plugins].append(code_str)

class CustomPage(BasePage):
    label = 'custom'

    def __init__(self):
        self.customs_vars = list()

        self.frame.connect('map', self.display_vars)
        self.frame_vbox.pack_start(
            gtk.Label(str='Variables that can be used are:'),
            expand=False)
        self.store = gtk.ListStore(str, str)
        self.treeview = gtk.TreeView(self.store)
        cell = gtk.CellRendererText()
        for i, title in enumerate(('variable', 'value')):
            column = gtk.TreeViewColumn(title)
            column.pack_start(cell, True)
            column.add_attribute(cell, 'text', i)
            self.treeview.append_column(column)
        vpaned = gtk.VPaned()
        self.frame_vbox.add(vpaned)
        treeview_scrolled = gtk.ScrolledWindow()
        treeview_scrolled.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
        treeview_scrolled.add_with_viewport(self.treeview)
        vpaned.pack1(treeview_scrolled, resize=False)

        customs_scrolled = gtk.ScrolledWindow()
        customs_scrolled.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
        vpaned.pack2(customs_scrolled, resize=False)
        customs_vbox = gtk.VBox(False, 0)
        customs_scrolled.add_with_viewport(customs_vbox)
        button = gtk.Button('Add a custom code snippet')
        button.connect('clicked', self.add_custom, customs_vbox)
        self.frame_vbox.pack_start(button, expand=False)

    def add_custom(self, _, vbox):
        custom_frame = gtk.Frame('code snippet')
        custom_vbox = gtk.VBox(False, 0)
        custom_vbox.set_size_request(-1, 150)
        textview = gtk.TextView()
        scrolled = gtk.ScrolledWindow()
        scrolled.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
        scrolled.add(textview)
        custom_vbox.add(scrolled)
        hbox = gtk.HBox(False, 0)
        where_box = gtk.combo_box_new_text()
        where_box.append_text('beginning')
        where_box.append_text('end')
        where_box.set_active(0)
        location_box = gtk.combo_box_new_text()
        for loc in Locations.locations:
            location_box.append_text(loc)
        location_box.set_active(0)
        hbox.add(gtk.Label(str='Add this at the '))
        hbox.add(where_box)
        hbox.add(gtk.Label(str=' of the '))
        hbox.add(location_box)
        hbox.add(gtk.Label(str=' section'))
        custom_vbox.pack_start(hbox, expand=False)
        textbuffer = textview.get_buffer()
        custom_vars = (where_box.get_active_text, location_box.get_active_text,
                       lambda: textbuffer.get_text(textbuffer.get_start_iter(),
                                                   textbuffer.get_end_iter()))
        self.customs_vars.append(custom_vars)
        button = gtk.Button('remove code snippet')
        button.connect('clicked', self.remove_custom, vbox, custom_frame,
                       custom_vars)
        custom_vbox.pack_start(button, expand=False)
        custom_frame.add(custom_vbox)
        vbox.add(custom_frame)
        self.frame.show_all()

    def remove_custom(self, _, vbox, custom_frame, custom_vars):
        vbox.remove(custom_frame)
        self.customs_vars.remove(custom_vars)

    def display_vars(self, _):
        dummy_vars = dict()
        self.store.clear()
        for page in BasePage.pages:
            page.generate_vars(dummy_vars)
        for var, val in sorted(dummy_vars.iteritems()):
            self.store.append([var, val])

    def generate_code(self, gen_code):
        gen_vars = ((w(), l(), t()) for w, l, t in self.customs_vars)
        for where, loc, text in gen_vars:
            lcode = gen_code[Locations.locations.index(loc)]
            if where == 'beginning':
                lcode.insert(0, text)
            elif where == 'end':
                lcode.append(text)

class GuiGenerator(object):
    def __init__(self):
        self.reset_generator()
        self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
        self.window.set_title('PycaWM Config Generator')
        self.window.set_default_size(500, 600)
        self.window.connect('destroy', gtk.main_quit)
        vbox = gtk.VBox(False, 10)
        self.notebook = gtk.Notebook()
        self.create_pages()
        vbox.add(self.notebook)
        gen_button = gtk.Button('generate config')
        gen_button.connect('clicked', self.generate_config)
        vbox.pack_start(gen_button, expand=False, padding=10)
        self.window.add(vbox)
        self.window.show_all()

    def reset_generator(self):
        self.gen_vars = dict()
        self.gen_code = [list() for _ in xrange(Locations.nb_locations)]

    def create_pages(self):
        for page in BasePage.pages:
            self.notebook.append_page(page.frame,
                                      tab_label=gtk.Label(page.label))

    def generate_config(self, _):
        for page in BasePage.pages:
            page.generate_vars(self.gen_vars)
        for page in BasePage.pages:
            page.generate_code(self.gen_code)
        config_path = expanduser(self.gen_vars['config_path'])
        if not config_path:
            run_error_dialog(parent=self.window,
                             message_format='Give me a valid config_path!')
            return
        if isfile(config_path):
            resp = run_question_dialog(
                parent=self.window,
                message_format=('Existing config file found at %s. '
                                'Overwrite it?' % config_path))
            if resp == gtk.RESPONSE_NO:
                return
        try:
            config = open(config_path, 'w')
        except IOError:
            run_error_dialog(parent=self.window,
                             message_format=('Cannot open %s for writing!' %
                                             config_path))
            return

        print >>config, generate_header()
        for i, codes in enumerate(self.gen_code):
            print >>config, '\n# %d- %s part' % (i, Locations.locations[i])
            for code in codes:
                print >>config, code % self.gen_vars
        print >>config, generate_footer() % self.gen_vars
        config.close()
        # empty the code and vars, so we will not generate the same code twice
        # next time
        self.reset_generator()
        run_info_dialog(
            parent=self.window,
            message_format=('The config file %s has been successfully '
                            'written!' % config_path))

    @staticmethod
    def loop():
        gtk.main()

if __name__ == '__main__':
    gui = GuiGenerator()
    try:
        gui.loop()
    except KeyboardInterrupt:
        pass
