Re: Settings and preferences



Hello!

This is intriguing, I've poured over the code and have been thinking.
It's very important: Kupfer is simple. If you look around, the coding
style is very declarative. settings.py is very hardcoded. Even though
the most important job looks great;
get_plugins_direct, get_plugins_direct and so on is good in the
main.py module. But for plugins and the larger program, It's not
flexible enough. We have to differentiate data from the
implementation, which is why, for example, kupfer ships default
settings in a default.cfg and not coded in the data. The same should
be for all preference keys and locations. Python has a strong culture,
and part of the Zen of python is "Don't repeat yourself". Perhaps I
try to take this to the extreme with kupfer, but the truth is that we
don't want to use the kind of Template and macro code you see in a C
program.

I want to show you how I think *plugin* configuration should look
like. For the plugin, it should look like *pure magic*.

First, the changes to the google.py module to illustrate the Gconf backend:

Before:

class GoogleSearch (Action):
        def __init__(self):
                Action.__init__(self, _("Search with Google"))

        def activate(self, leaf):
                from urllib import urlencode
                search_url = "http://www.google.com/search?";
                # will encode search=text, where `text` is escaped
                query_url = search_url + urlencode({"q": leaf.object, "ie": "utf
                utils.show_url(query_url)
        def get_description(self):
                return _("Search for this term with Google")
        def get_icon_name(self):
                return "gtk-find"
        def item_types(self):
                yield TextLeaf


After:

class GoogleSearch (Action):
GCONF_INTERFACE_LANG = settings.SettingsController.GCONF_PLUGINS +
'/google/interface_lang'
                      GCONF_SEARCH_LANG =
settings.SettingsController.GCONF_PLUGINS + '/google/search_lang'

    def __init__(self):
        Action.__init__(self, _("Search with Google"))
        self.conf = settings.GetSettingsController ().get_client ()
        page_lang = self.conf.get_string (self.GCONF_INTERFACE_LANG)
        if page_lang is None:
            self.conf.set_string (self.GCONF_INTERFACE_LANG, 'en')
        search_lang = self.conf.get_string (self.GCONF_SEARCH_LANG)
        if search_lang is None:
            self.conf.set_string (self.GCONF_SEARCH_LANG, 'lang_en')

    def activate(self, leaf):
        from urllib import urlencode
        page_lang = self.conf.get_string (self.GCONF_INTERFACE_LANG)
        if page_lang is None:
            page_lang = 'en'
        search_lang = self.conf.get_string (self.GCONF_SEARCH_LANG)
        if search_lang is None:
            search_lang = 'lang_en'
        search_url = "http://www.google.com/search?";
        # will encode search=text, where `text` is escaped
        query_url = search_url + urlencode({"q": leaf.object,
                            "ie": "utf-8",
                            "hl": page_lang,
                            "meta": "lr=" + search_lang})
        utils.show_url(query_url)
    def get_description(self):
        return _("Search for this term with Google")
    def get_icon_name(self):
        return "gtk-find"
    def item_types(self):
        yield TextLeaf



Do you see how much logic has to be implemented on the plugin side?
All these smarts should be in the settings module, and the word Gconf
shouldn't exist in any other module, it's an implementation detail,
just like the fact that configuration keys are paths/with/slashes.
This is why modules exist.. they take care about something, and no
other module has to care about it. Plugins should be very simple, to
be understood by those who want to program kupfer to understand some
specific data. Plugins are very declarative, they say what they have.
Also consider the case when you duplicate a plugin to make a new one.
You should change as few lines as possible before you have a
standalone plugin. Why mention the plugin name in the plugin, when
kupfer already knows (yes kupfer.plugin.google.GoogleSearch can
coexist just fine with, for example,
kupfer.plugin.crazysearch.GoogleSearch)

For illustration here is a prototype of what I would like plugin
configuration to look like for the google module:

__kupfer_settings__ = [
    {   "key": "interface_lang",
        "label": _("Google interface language"),
        "type" : str,
        "value" : "en",
    },
    {   "key": "search_lang",
        "label": _("Google search language"),
        "type": str,
        "value": "en",
    },
)

def read_settings():
    return dict((setting["key"], setting["value"]) for setting in
__kupfer_settings__)

def get_setting(key):
    return read_settings()[key]

The plugin *declares* what it has. Kupfer can then produce a
preferences window for this plugin to set the preference keys. I'd
like this to be so magic, so that when the plugin is loaded, we modify
its __kupfer_settings__ attribute to fill in the stored config. The
read_settings and get_settings functions look clumsy, they are only
for illustration. Perhaps instead of a dicts in list structure, it's
better with dicts in dict, only that ordering is lost. (Then
get_setting = lambda key: __kupfer_settings__[key]["value"])

Now it's possible for the config to melt in in the code in a natural
way. No special backend code (gconf) and no special-casing. We declare
a default and that's good. (If need arises (If, the word is
important!), we can let plugins check the values in a callback when
they are changed later).

This should be:
    def activate(self, leaf):
        from urllib import urlencode
        search_url = "http://www.google.com/search?";
        # will encode search=text, where `text` is escaped
        query_url = search_url + urlencode({"q": leaf.object,
                            "ie": "utf-8",
                            "hl": get_setting("interface_lang"),
                            "meta": "lr=" + get_setting("search_lang")})
        utils.show_url(query_url)


I'm looking forward to developing this, but I'm wary that it wouldn't
be kupfer anymore if the simplicity disappears in the code. If you
think like a plugin, you don't care about how settings work. Neither
does any module. They just want to store settings under a key and be
done with it. (Luckily there are not so many). I suggested I could
code the get/set plugins interface to the config files system that
exists now. You obviously know lots of things I don't, like GConf and
how to build a UI file. To begin in that end I think is good, we
should make a preferences window prototype. And think about how to
store keys in gconf without having to repeat ourselves ever. (We have
to have 1. A gconf scheme to register everything when the application
is installed and 2. modules that want to store preferences. We
shouldn't have to hardcode much more than that, nothing in between,
except the defaults somewhere (gconf scheme file?)) However, it might
be easier to go with the config file solution for now.

I'm going to go away for a short week, so I will not be able to
respond at all, but enjoy this nice summer here in spain! (yes I'm
still here)

Take care
ulrik

2009/7/24 perriman <chuchiperriman gmail com>:
> Hi,
>
>        You can take a look at the upstream-integration branch of
>
> https://github.com/chuchiperriman/kupfer/tree
>
>        Now exists SettingsController in settings.py and I have removed
> GconfStore.py. The preferences dialog still not work but you can take a
> look at the SettingsController object. I have removed the get_config
> function and main.py uses the SC now.
>
>        I have added some configuration to google.py plugin to show how the
> plugins can use the gconf_client to store their configurations.
>
>        Please, take a look at the new structure and give me your opinion about
> it.
>
>        The next steps are:
>
> 1.- When the user change something in the preferences dialog, we need to
> change it in the settings controller.
> 2.- Someone (in main.py or browser.py, I need to see) will connect to
> the SettingsController signals and will change the kupfer objects on the
> fly when the configuration changes.
> 3.- We need to decide what preferences we will add to the preferences
> dialog. I don't know if directories/direct, directories/catalog etc,
> need to be shown in the preferences dialog or if they are internal.
> .
> .
> .
> n.- Show the plugin list
>
> I go slow but secure :)
>
> Perriman
>
> _______________________________________________
> kupfer-list mailing list
> kupfer-list gnome org
> http://mail.gnome.org/mailman/listinfo/kupfer-list
>


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]