diff --git a/cream/config/Makefile b/cream/config/Makefile new file mode 100644 index 0000000..ec1b27b --- /dev/null +++ b/cream/config/Makefile @@ -0,0 +1,3 @@ +settings: + sudo cp org.cream.melange.gschema.xml /usr/share/glib-2.0/schemas/ + sudo glib-compile-schemas /usr/share/glib-2.0/schemas/ diff --git a/cream/config/__init__.py b/cream/config/__init__.py index a3b970e..7bf996a 100644 --- a/cream/config/__init__.py +++ b/cream/config/__init__.py @@ -15,302 +15,27 @@ # along with this library; if not, write to the Free Software Foundation, Inc., # 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -# TODO: Rewrite this. +from cream.config.frontend import Frontend +from cream.config.backend import Backend -from gpyconf import Configuration as _Configuration -from gpyconf.fields import Field -from .backend import CreamXMLBackend, CONFIGURATION_SCHEME_FILE -from cream.util import flatten, cached_property +class Configuration(object): -PROFILE_EXISTS_MARKUP = ''' \ -Sorry! A profile with the name {0} already exists! + def __init__(self): -Please choose a different name!''' + self.backend = Backend('org.cream.melange') + self.frontend = Frontend(self.backend.profiles) -class MissingConfigurationDefinitionFile(Exception): - """ - Raised if one tries to access a module's configuration - but that module hasn't defined any. - """ - pass + self.frontend.connect('profile-selected', lambda f, p: self.backend.set_profile(p)) + self.frontend.connect('profile-added', lambda f, p: self.backend.add_profile(p)) + self.frontend.connect('profile-removed', lambda f, p: self.backend.remove_profile(p)) + self.frontend.connect('value-changed', lambda f, k, v: self.backend.set_value(k, v)) -class ProfileNotEditable(Exception): - pass + self.frontend.run() -class ConfigurationProfile(object): - """ A configuration profile. Holds name and assigned values. """ - is_editable = True + self.backend.save() - def __init__(self, name, values, editable=True): - self.name = name - self.default_values = values - self.is_editable = editable - self._values = values - @classmethod - def fromdict(cls, dct, default_profile): - values = default_profile.values.copy() - values.update(dct.get('values', ())) - return cls(dct.pop('name'), values, dct.pop('editable', True)) - - @property - def values(self): - return self._values - - # TODO: Very repetitive - @values.setter - def values(self, value): - if not self.is_editable: - raise ProfileNotEditable(self) - else: - self._values = value - - def update(self, iterable): - if not self.is_editable: - raise ProfileNotEditable(self) - self.values.update(iterable) - - def set(self, name, value): - if not self.is_editable: - raise ProfileNotEditable(self) - self.values[name] = value - - def __repr__(self): - return "" % (self.name, - not self.is_editable and ' (not editable)' or '') - -class DefaultProfile(ConfigurationProfile): - """ Default configuration profile (using in-code defined values) """ - def __init__(self, values): - ConfigurationProfile.__init__(self, 'Default Profile', - values, editable=False) - - -class ProfileExistsError(Exception): - def __init__(self, name): - Exception.__init__(self, "A profile named '%s' already exists" % name) - -class ProfileList(list): - """ List of profiles """ - default = None - active = None - active_index = 0 - - def __init__(self, default_profile): - list.__init__(self) - list.append(self, default_profile) - self.default = default_profile - - def insert(self, index, profile, overwrite=False): - assert index - - if not isinstance(profile, ConfigurationProfile): - profile = ConfigurationProfile.fromdict(profile, self.default) - - _, old_profile = self.find_by_name(profile.name) - if old_profile is not None: - if not overwrite: - raise ProfileExistsError(profile) - else: - old_profile.values = profile.values - else: - list.insert(self, index, profile) - - def append(self, *args, **kwargs): - self.insert(len(self), *args, **kwargs) - add = append - - def find_by_name(self, name): - """ - Returns a `(index, profile)` tuple being the `Profile` instance holding - `name` as `name` attribute and its index in this list. - - If no such profile exists, returns a `(None, None)` tuple instead. - """ - for index, profile in enumerate(self): - if profile.name == name: - return index, profile - return None, None - - def _use(self, profile): - if isinstance(profile, int): - try: - self.active = self[profile] - self.active_index = profile - except IndexError: - self._use(0) - else: - self.active = profile - self.active_index = self.index(profile) - - -class Configuration(_Configuration): - """ - Base class for all cream configurations. - """ - backend = CreamXMLBackend - profiles = () - - @cached_property - def frontend(self): - from .frontend import CreamFrontend - return CreamFrontend - - - def __init__(self, scheme_path, path, **kwargs): - # Make sure this instance's `fields` dict is *not* the classes' - # `fields` dict (hence, the `fields` attribute of class `cls`), - # but a copy of it. - # TODO: There has to be a better way. - self.fields = self.fields.copy() - - backend = CreamXMLBackend(scheme_path, path) - - try: - configuration_scheme = backend.read_scheme() - for name, field in configuration_scheme.iteritems(): - self._add_field(name, field) - except MissingConfigurationDefinitionFile: - pass - - _Configuration.__init__(self, backend_instance=backend, **kwargs) - - - def _add_field(self, name, field): - field.field_var = name - self.fields[name] = field - field.connect('value-changed', self.on_field_value_changed) - - def read(self): - if not self.initially_read: - predefined_profiles = self.profiles - else: - predefined_profiles = () - - self.profiles = ProfileList(DefaultProfile(self.fields.name_value_dict)) # TODO: remove static options - - static_options, profiles = self.backend_instance.read() - - for field_name, value in static_options.iteritems(): - setattr(self, field_name, value) - - active_profile = 0 - for profile in flatten((profiles, predefined_profiles)): - position = profile.pop('position') - self.profiles.insert(position, profile, overwrite=True) - if profile.pop('selected', False): - active_profile = position - - self.use_profile(active_profile) - - self.initially_read = True - - def __setattr__(self, attr, value): - new_value = super(Configuration, self).__setattr__(attr, value) - if new_value is not None and not self.fields[attr].static: - self.profiles.active.set(attr, new_value) - - def __getattr__(self, name): - field = self.fields.get(name) - if field is not None: - if field.static: - return field.value - else: - return self.profiles.active.values[name] - else: - raise AttributeError("No such attribute '%s'" % name) - - def use_profile(self, profile): - self.profiles._use(profile) - for name, instance in self.fields.iteritems(): - if instance.static: continue - instance.value = self.profiles.active.values[name] - self.profiles.active.values[name] = instance.value - - - # FRONTEND: - def _init_frontend(self, fields): - _Configuration._init_frontend(self, fields) - self.window = self.frontend_instance - - self.window.add_profiles(self.profiles) - - self.window.connect('profile-changed', self.frontend_profile_changed) - self.window.connect('add-profile', self.frontend_add_profile) - self.window.connect('remove-profile', self.frontend_remove_profile) - - self.window.set_active_profile_index(self.profiles.active_index) - - def frontend_field_value_changed(self, *args): - if not self._ignore_frontend: - _Configuration.frontend_field_value_changed(self, *args) - - - def frontend_profile_changed(self, sender, profile_name, index): - """ Profile selection was changed by the frontend (user) """ - self._ignore_frontend = True - self.use_profile(index) - self._ignore_frontend = False - self.window.editable = self.profiles.active.is_editable - self.save() - - def frontend_add_profile(self, sender, profile_name, position): - """ User added a profile using the "add profile" button """ - profile = ConfigurationProfile(profile_name, self.fields.name_value_dict) - try: - self.profiles.insert(position, profile) - except ProfileExistsError: - import gtk - dialog = gtk.MessageDialog( - parent=None, - flags=gtk.DIALOG_MODAL, - type=gtk.MESSAGE_ERROR, - buttons=gtk.BUTTONS_CLOSE - ) - dialog.set_markup(PROFILE_EXISTS_MARKUP.format(profile.name)) - dialog.run() - dialog.destroy() - else: - self.window.insert_profile(profile, position) - self.window.set_active_profile_index(position) - self.save() - - def frontend_remove_profile(self, sender, position): - """ User removed a profile using the "remove profile" button """ - del self.profiles[position] - self.window.remove_profile(position) - self.save() - - - def run_frontend(self): - _Configuration.run_frontend(self) - del self.frontend_instance - - def show_dialog(self): - self.run_frontend() - - - # BACKEND - def save(self): - self.emit('pre-save') - self.backend_instance.save(self.profiles, self.fields) - - - -# Patch the `Field` class to make it accept the -# cream-specific `static` keyword: -def inject_method(klass, method_name): - def wrapper(func): - original_meth = getattr(klass, method_name) - def chained_method(*args, **kwargs): - original_meth(*args, **kwargs) - func(*args, **kwargs) - setattr(klass, method_name, chained_method) - return func - return wrapper - -@inject_method(Field, '_external_on_initialized') -def on_initialized(self, kwargs): - self.static = kwargs.pop('static', False) +if __name__ == '__main__': + Configuration() diff --git a/cream/config/backend.py b/cream/config/backend.py index b3be734..966efbb 100644 --- a/cream/config/backend.py +++ b/cream/config/backend.py @@ -15,158 +15,211 @@ # along with this library; if not, write to the Free Software Foundation, Inc., # 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -import os +from gi.repository import GObject as gobject, Gio as gio, GLib as glib -from lxml.etree import XMLSyntaxError, parse as parse_xml -from cream.util.string import slugify +from cream.util.dicts import ordereddict -from gpyconf.backends import Backend -from xmlserialize import unserialize_file, unserialize_atomic, serialize_to_file -import gpyconf.fields -import gpyconf.contrib.gtk -import cream.config.fields -from cream.util.dicts import ordereddict -FIELD_TYPE_MAP = { - 'char' : 'str', - 'color' : 'str', - 'font' : 'str', - 'file' : 'tuple', - 'integer' : 'int', - 'hotkey' : 'str', - 'boolean' : 'bool', - 'multioption' : 'tuple' -} +NAME_DEFAULT = 'Default' + +KEY_PROFILES = 'profiles' +KEY_SELECTED = 'profile-selected' + +IGNORE_KEYS = [KEY_PROFILES, KEY_SELECTED] + + +def variant_from_python(obj): + + if isinstance(obj, bool): + return glib.Variant('b', obj) + elif isinstance(obj, int): + return glib.Variant('i', obj) + elif isinstance(obj, float): + return glib.Variant('d', obj) + elif isinstance(obj, basestring): + return glib.Variant('s', obj) + + + +def variant_to_python(variant): + + if variant.get_type_string() == 'b': + return variant.get_boolean() + elif variant.get_type_string() == 'i': + return variant.get_int32() + elif variant.get_type_string() == 'd': + return variant.get_double() + elif variant.get_type_string() == 's': + return variant.get_string() + elif variant.get_type_string() == 'as': + return list(variant) + + + +class Profiles(object): + + def __init__(self, schema): + + self.schema = schema + self.profiles = ordereddict() + self._selected_profile = None + + self.default_profile = self.add_profile(NAME_DEFAULT) + + profile_names = self.default_profile.settings.get_value(KEY_PROFILES) + profile_names = filter(lambda n: n != NAME_DEFAULT, list(profile_names)) + + if not profile_names: + # only default profile available + self.default_profile.selected = True + + + for name in profile_names: + self.add_profile(name) + + for profile in self.profiles.itervalues(): + if profile.selected: + self.selected_profile = profile + + + def __iter__(self): + return iter(self.profiles.itervalues()) + + + def __len__(self): + return len(self.profiles) + + + @property + def selected_profile(self): + return self._selected_profile + + @selected_profile.setter + def selected_profile(self, profile): + + if self._selected_profile: + self._selected_profile.selected = False + + profile.selected = True + self._selected_profile = profile + + + def add_profile(self, name): + + if name == NAME_DEFAULT: + profile = DefaultProfile(name, self.schema) + else: + profile = Profile(name, self.schema) + self.profiles[name] = profile + + return profile + + + def remove_profile(self, name): + + profile = self.profiles[name] + if profile == self.selected_profile: + index = self.profiles.values().index(profile) + self.selected_profile = self.profiles.values()[index-1] + + return self.profiles.pop(name) + + + def set_profile(self, name): + + profile = self.profiles[name] + self.selected_profile = profile + + + def set_value(self, key, value): + + variant = variant_from_python(value) + self.selected_profile.set_value(key, variant) + + + def save(self): + + profiles = glib.Variant('as', self.profiles.keys()) + self.default_profile.set_value(KEY_PROFILES, profiles) + + +class Profile(object): + + def __init__(self, name, schema): + + self.name = name + self.schema = schema + self.path = '/' + name.lower() + self.settings = gio.Settings.new_with_path(self.schema, self.path) + + self.is_default = False + self._selected = self.settings.get_boolean(KEY_SELECTED) + + + @property + def keys(self): + return (key for key in self.settings.list_keys() if key not in IGNORE_KEYS) + + + @property + def writeable(self): + return self.name != NAME_DEFAULT + + + @property + def selected(self): + return self._selected + + @selected.setter + def selected(self, value): + self._selected = value + self.settings.set_boolean(KEY_SELECTED, value) + + + def get_value(self, key): + return variant_to_python(self.settings.get_value(key)) + + + def set_value(self, key, value): + self.settings.set_value(key, value) + + +class DefaultProfile(Profile): + + def __init__(self, name, schema): + + Profile.__init__(self, name, schema) + + self.is_default = True + + +class Backend(gobject.GObject): + def __init__(self, schema): -CONFIGURATION_DIRECTORY = 'configuration' -STATIC_OPTIONS_FILE = 'static-options.xml' -CONFIGURATION_SCHEME_FILE = 'scheme.xml' -PROFILE_ROOT_NODE = 'configuration_profile' -STATIC_OPTIONS_ROOT_NODE = 'static_options' -PROFILE_DIR = 'profiles' + gobject.GObject.__init__(self) + self.profiles = Profiles(schema) -def get_field(name): - if not name.endswith('Field'): - name = name.title() + 'Field' - try: return getattr(cream.config.fields, name) - except AttributeError: pass - try: return getattr(gpyconf.fields, name) - except AttributeError: pass - try: return getattr(gpyconf.contrib.gtk, name) - except AttributeError: - raise FieldNotFound(name) + def set_profile(self, profile): + self.profiles.set_profile(profile) -class FieldNotFound(Exception): - pass + def add_profile(self, profile): + self.profiles.add_profile(profile) -class CreamXMLBackend(dict, Backend): - compatibility_mode = False - - def __init__(self, scheme_path, path): - Backend.__init__(self, None) - dict.__init__(self) - self.scheme_path = scheme_path - self.path = path - - self.profile_dir = os.path.join(self.path, PROFILE_DIR) - - - def read_scheme(self): - - if not os.path.isfile(self.scheme_path): - from . import MissingConfigurationDefinitionFile - raise MissingConfigurationDefinitionFile("Could not find %r." % self.scheme_path) - - tree = parse_xml(self.scheme_path) - root = tree.getroot() - scheme = ordereddict() - - for child in root.getchildren(): - option_name = child.tag - attributes = dict(child.attrib) - option_type = attributes.pop('type') - if option_type.startswith('multioption'): - # TODO: Hrm - attributes['default'] = child.attrib.pop('default', None) - attributes['options'] = unserialize_atomic(child, FIELD_TYPE_MAP) - else: - if not ( - FIELD_TYPE_MAP.get(option_type) in ('list', 'tuple', 'dict') - and not child.getchildren() - ): - attributes['default'] = unserialize_atomic(child, FIELD_TYPE_MAP) - scheme[option_name] = get_field(option_type)(**attributes) - - return scheme - - - def read(self): - - static_options = {} - profiles = [] - - try: - obj = unserialize_file(os.path.join(self.path, 'static-options.xml')) - static_options.update(obj) - except: - pass - - if not os.path.exists(self.profile_dir): - return dict(), tuple() - - for profile in os.listdir(self.profile_dir): - if os.path.isdir(os.path.join(self.profile_dir, profile)): - continue - try: - obj = unserialize_file(os.path.join(self.profile_dir, profile)) - except XMLSyntaxError, err: - self.warn("Could not parse XML configuration file '{file}': {error}".format( - file=profile, error=err)) - else: - profiles.append(obj) - - return static_options, profiles - - - def save(self, profile_list, fields): - - if not os.path.exists(self.profile_dir): - os.makedirs(self.profile_dir) - # get all saved profiles - saved_profiles = {} - for profile in os.listdir(self.profile_dir): - name = os.path.splitext(profile)[0] - saved_profiles[name] = os.path.join(self.profile_dir, profile) + def remove_profile(self, profile): + self.profiles.remove_profile(profile) - for index, profile in enumerate(profile_list): - if not profile.is_editable: continue - filename = os.path.join(os.path.join(self.path, PROFILE_DIR), slugify(profile.name)+'.xml') - - serialize_to_file({ - 'name' : profile.name, - 'values' : profile.values, - 'position' : index, - 'selected' : profile_list.active == profile - }, filename, tag=PROFILE_ROOT_NODE) + def set_value(self, key, value): + self.profiles.set_value(key, value) - if profile.name.lower() in saved_profiles: - del saved_profiles[profile.name.lower()] - # `saved_profiles` now contains profiles, which have been removed - # but are still present in the filesystem. Remove them. - for profile in saved_profiles.values(): - os.remove(profile) + def save(self): - static_options = dict((name, field.value) for name, field in - fields.iteritems() if field.static) - if static_options: - serialize_to_file(static_options, - os.path.join(self.path, STATIC_OPTIONS_FILE), - tag=STATIC_OPTIONS_ROOT_NODE) + self.profiles.save() + gio.Settings.sync() diff --git a/cream/config/fields.py b/cream/config/fields.py deleted file mode 100644 index fea1fd6..0000000 --- a/cream/config/fields.py +++ /dev/null @@ -1,45 +0,0 @@ -#! /usr/bin/env python -# -*- coding: utf-8 -*- - -# This library is free software; you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation; either version 2.1 of the License, or -# (at your option) any later version. - -# This library 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 Lesser General Public License for more details. - -# You should have received a copy of the GNU Lesser General Public License -# along with this library; if not, write to the Free Software Foundation, Inc., -# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -from gpyconf.fields import FontField as _FontField, MultiOptionField -from gpyconf.frontends.gtk import font_description_to_dict, dict_to_font_description - - -MultioptionField = MultiOptionField - -class FontDict(dict): - def to_string(self): - return dict_to_font_description(self) - - @classmethod - def fromstring(cls, string): - d = font_description_to_dict(string) - d['color'] = '#000000' - return cls(d) - -class FontField(_FontField): - def custom_default(self): - return FontDict(_FontField.custom_default(self)) - - def to_python(self, value): - if isinstance(value, basestring): - return FontDict.fromstring(value) - else: - return FontDict(_FontField.to_python(self, value)) - - def conf_to_python(self, value): - return FontDict(_FontField.conf_to_python(self, value)) \ No newline at end of file diff --git a/cream/config/frontend.py b/cream/config/frontend.py index 12efcf2..653ca0c 100644 --- a/cream/config/frontend.py +++ b/cream/config/frontend.py @@ -15,156 +15,230 @@ # along with this library; if not, write to the Free Software Foundation, Inc., # 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -import os -import gtk -from gpyconf.frontends.gtk import ConfigurationDialog + +from gi.repository import GObject as gobject, Gtk as gtk + +from cream.config import widgets from cream.util import joindir + MODE_NORMAL = 1 -MODE_EDIT = 2 - -class CreamFrontend(ConfigurationDialog): - _editable = True - _new_events = ('profile-changed', 'add-profile', 'remove-profile') - - def __init__(self, *args, **kwargs): - - self.profiles = [] - self._mode = MODE_NORMAL - - ConfigurationDialog.__init__(self, title='Configuration', *args, **kwargs) - self.add_events(self._new_events) - - self.interface = gtk.Builder() - self.interface.add_from_file(joindir(__file__, 'interface/profiles.ui')) - - self.profile_box_edit = self.interface.get_object('profile_box_edit') - self.profile_entry = self.interface.get_object('profile_entry') - self.profile_save = self.interface.get_object('profile_save') - self.profile_cancel = self.interface.get_object('profile_cancel') - - self.profile_box_normal = self.interface.get_object('profile_box_normal') - self.profile_selector = self.interface.get_object('profile_selector') - self.profile_add = self.interface.get_object('profile_add') - self.profile_remove = self.interface.get_object('profile_remove') - - self.profiles_storage = self.interface.get_object('profiles_storage') - - self.profile_selector.connect('changed', self.on_profile_changed) - self.profile_entry.connect('activate', self.change_mode) - self.profile_entry.connect('activate', self.on_new_profile_added) - self.profile_add.connect('clicked', self.change_mode) - self.profile_save.connect('clicked', self.change_mode) - self.profile_save.connect('clicked', self.on_new_profile_added) - self.profile_cancel.connect('clicked', self.change_mode) - self.profile_remove.connect('clicked', self.on_remove_profile) - - self.alignment = gtk.Alignment(1, 0.5, 1, 1) - self.alignment.add(self.profile_box_normal) - - self.layout.pack_start(self.alignment, False, False, 0) - self.layout.reorder_child(self.alignment, 0) - - def add_profiles(self, profiles): - """ Add a list or tuple of `Profile`s to the profile selector """ - for profile in profiles: - self.add_profile(profile) - - def add_profile(self, profile): - """ Add a `Profile` instance to the profile selector """ - self.profiles_storage.append([profile.name]) - self.profiles.append(profile.name) - - def insert_profile(self, profile, position): - """ Insert `profile` at `position` into the profile selector """ - self.profiles_storage.insert(position, [profile.name]) - self.profiles.insert(position, profile.name) - - def remove_profile(self, position): - """ Remove entry at `position` from the profile selector """ - iter = self.profiles_storage.get_iter_from_string(str(position)) - # huaa? - self.profiles_storage.remove(iter) - self.profile_selector.set_active(position-1) - - - # CALLBACKS - def on_profile_changed(self, widget): - """ User changed the profile-selector dropdown """ - index = widget.get_active() - if index < 0: - # empty profile selector (should only happen at startup) - return - self.emit('profile-changed', - self.profiles_storage.get_value(widget.get_active_iter(), 0), - index) - - - def on_remove_profile(self, sender): - """ User clicked the "remove profile" button """ - - dialog = gtk.MessageDialog( - parent=None, - flags=gtk.DIALOG_MODAL, - type=gtk.MESSAGE_QUESTION, - buttons=gtk.BUTTONS_YES_NO) - - dialog.set_markup("Are you sure that you want to remove profile {0}?\n\nYou will lose all data connected to this profile and won't be able to restore a previously removed profile!".format(self.profiles[self.profile_selector.get_active()])) - - res = dialog.run() - dialog.destroy() - if res == gtk.RESPONSE_YES: - self.emit('remove-profile', self.profile_selector.get_active()) - - def on_new_profile_added(self, sender): - """ User is done with editing the Entry """ - name = self.profile_entry.get_text() - index = self.profile_selector.get_active() + 1 - if name: - self.emit('add-profile', name, index) - - - def change_mode(self, sender): - """ User clicked on add or save button. Change the mode. """ - max_height = max(self.profile_entry.get_allocation()[3], - self.profile_selector.get_allocation()[3] - ) - self.alignment.set_size_request(-1, max_height) - - box = [widget for widget in self.alignment][0] - if self._mode == MODE_NORMAL: +MODE_ADD = 2 + + +def get_widget_for_value(value): + + if isinstance(value, bool): + return widgets.BooleanWidget + elif isinstance(value, int): + return widgets.IntegerWidget + elif isinstance(value, float): + return widgets.FloatWidget + elif isinstance(value, basestring): + return widgets.CharWidget + elif isinstance(value, list): + return widgets.MultiOptionWidget + + +class ConfigurationDialog(gobject.GObject): + + __gsignals__ = { + 'value-changed': (gobject.SignalFlags.RUN_LAST, None, (str, object)), + 'profile-selected': (gobject.SignalFlags.RUN_LAST, None, (str, )), + 'profile-added': (gobject.SignalFlags.RUN_LAST, None, (str, )), + 'profile-removed': (gobject.SignalFlags.RUN_LAST, None, (str, )) + } + + def __init__(self, profiles): + + gobject.GObject.__init__(self) + + self.profiles = profiles + self.widgets = {} + + interface = gtk.Builder() + interface.add_from_file(joindir(__file__, 'interface/dialog.ui')) + + self.dialog = interface.get_object('dialog') + self.profile_box = interface.get_object('profile_box') + self.settings_grid = interface.get_object('settings_grid') + self.profile_store = interface.get_object('profile_store') + + self.profile_normal = interface.get_object('profile_normal') + self.profile_selector = interface.get_object('profile_selector') + self.button_add = interface.get_object('button_add') + self.button_remove = interface.get_object('button_remove') + + self.profile_add = interface.get_object('profile_add') + self.profile_entry = interface.get_object('profile_entry') + self.button_save = interface.get_object('button_save') + self.button_cancel = interface.get_object('button_cancel') + + + # TODO: do this in Glade, stupid thing is crashing + cell = gtk.CellRendererText() + self.profile_selector.pack_start(cell, False) + self.profile_selector.add_attribute(cell, "text", 0) + + + self.dialog.connect('delete-event', lambda *x: self.dialog.hide()) + + self.profile_selector.connect('changed', self.on_profile_selected) + self.button_add.connect('clicked', self.change_mode, MODE_ADD) + self.button_remove.connect('clicked', self.on_profile_removed) + self.button_save.connect('clicked', self.on_profile_added) + self.button_cancel.connect('clicked', self.change_mode, MODE_NORMAL) + self.profile_entry.connect('activate', self.on_profile_added) + + + self.profile_box.pack_start(self.profile_normal, True, True, 0) + + self.dialog.set_default_size(250, 150) + + + for row, key in enumerate(sorted(self.profiles.default_profile.keys)): + widget = self.init_widget(key) + + widget.connect('value-changed', self.on_value_changed, key) + self.widgets[key] = widget + + self.settings_grid.attach(gtk.Label(key), 0, row+1, 1, 1) + self.settings_grid.attach(widget.widget, 1, row+1, 1, 1) + + self.settings_grid.show_all() + self.update_profile_list() + + + def init_widget(self, key): + + value = self.profiles.selected_profile.get_value(key) + widget = get_widget_for_value(value) + + return widget(value) + + + @property + def selected_profile(self): + index = self.profile_selector.get_active() + return list(self.profiles)[index] + + + def change_mode(self, widget, mode): + + if mode == MODE_NORMAL: self.profile_entry.set_text('') - self.alignment.remove(box) - self.alignment.add(self.profile_box_edit) + self.profile_box.remove(self.profile_add) + self.profile_box.pack_start(self.profile_normal, True, True, 0) + else: + self.profile_box.remove(self.profile_normal) + self.profile_box.pack_start(self.profile_add, True, True, 0) self.profile_entry.grab_focus() - self._mode = MODE_EDIT + + + def update_profile_list(self): + + self.profile_store.clear() + + for i, profile in enumerate(self.profiles): + if profile.selected: + selected_index = i + self.profile_store.append((profile.name,)) + self.profile_selector.set_active(selected_index) + + if len(self.profiles) == 1: + self.profile_selector.set_sensitive(False) else: - self.alignment.remove(box) - self.alignment.add(self.profile_box_normal) - self._mode = MODE_NORMAL + self.profile_selector.set_sensitive(True) - @property - def editable(self): - """ - `True` if the window is 'editable' (an editable profile is selected) - """ - return self._editable - - @editable.setter - def editable(self, value): - # set widgets sensitive (or not) - if value: - if not self.editable: - self.content.set_sensitive(True) - self.profile_remove.set_sensitive(True) + + def update_values(self): + + for key in self.profiles.selected_profile.keys: + value = self.profiles.selected_profile.get_value(key) + self.widgets[key].set_value(value) + + + def update_sensitivity(self): + + if self.profiles.selected_profile.is_default: + self.button_remove.set_sensitive(False) + self.settings_grid.set_sensitive(False) else: - self.content.set_sensitive(False) - self.profile_remove.set_sensitive(False) - self._editable = value + self.button_remove.set_sensitive(True) + self.settings_grid.set_sensitive(True) + + + def on_profile_selected(self, widget): + + self.emit('profile-selected', self.selected_profile.name) + + self.update_sensitivity() + self.update_values() + + + def on_profile_added(self, widget): + + self.emit('profile-added', self.profile_entry.get_text()) + + self.change_mode(None, MODE_NORMAL) + self.update_profile_list() + + + def on_profile_removed(self, widget): + + self.emit('profile-removed', self.selected_profile.name) + self.update_profile_list() + + + def on_value_changed(self, widget, value, key): + self.emit('value-changed', key, value) - def set_active_profile_index(self, index): - self.profile_selector.set_active(index) def run(self): - ConfigurationDialog.run(self) - self.dialog.destroy() + return self.dialog.run() + + +class Frontend(gobject.GObject): + + __gsignals__ = { + 'value-changed': (gobject.SignalFlags.RUN_LAST, None, (str, object)), + 'profile-selected': (gobject.SignalFlags.RUN_LAST, None, (str, )), + 'profile-added': (gobject.SignalFlags.RUN_LAST, None, (str, )), + 'profile-removed': (gobject.SignalFlags.RUN_LAST, None, (str, )), + } + + def __init__(self, profiles): + + gobject.GObject.__init__(self) + + + self.profiles = profiles + self.dialog = ConfigurationDialog(profiles) + + self.dialog.connect('profile-selected', self.on_profile_selected) + self.dialog.connect('profile-added', self.on_profile_added) + self.dialog.connect('profile-removed', self.on_profile_removed) + self.dialog.connect('value-changed', self.on_value_changed) + + + def run(self): + return self.dialog.run() + + + def on_profile_selected(self, dialog, profile): + self.emit('profile-selected', profile) + + + def on_profile_added(self, dialog, profile): + + profiles = map(lambda p: p.name, self.profiles) + if profile not in profiles: + self.emit('profile-added', profile) + + + def on_profile_removed(self, dialog, profile): + self.emit('profile-removed', profile) + + + def on_value_changed(self, dialog, key, value): + self.emit('value-changed', key, value) diff --git a/cream/config/interface/dialog.ui b/cream/config/interface/dialog.ui new file mode 100644 index 0000000..143a984 --- /dev/null +++ b/cream/config/interface/dialog.ui @@ -0,0 +1,215 @@ + + + + + False + 5 + Configuration + dialog + + + False + vertical + 2 + + + False + end + + + + + + gtk-close + False + True + True + True + False + True + + + False + True + 1 + + + + + False + True + end + 0 + + + + + True + False + + + True + False + + + + + + False + True + 0 + + + + + True + False + 5 + 15 + True + + + + + + True + True + 1 + + + + + True + True + 1 + + + + + + button1 + + + + True + False + + + True + True + + + + False + True + 0 + + + + + False + True + True + True + False + + + True + False + gtk-ok + + + + + False + True + 1 + + + + + False + True + True + True + False + + + True + False + gtk-cancel + + + + + False + True + 2 + + + + + + + + + + + True + False + + + True + False + profile_store + + + True + True + 0 + + + + + False + True + True + True + False + + + True + False + gtk-add + + + + + False + True + 1 + + + + + False + True + True + True + False + + + True + False + gtk-remove + + + + + False + True + 2 + + + + diff --git a/cream/config/interface/profiles.ui b/cream/config/interface/profiles.ui deleted file mode 100644 index 076c1a3..0000000 --- a/cream/config/interface/profiles.ui +++ /dev/null @@ -1,110 +0,0 @@ - - - - - - True - - - True - True - - - - 0 - - - - - True - True - True - - - True - gtk-ok - - - - - False - 1 - - - - - True - True - True - - - True - gtk-cancel - - - - - False - 2 - - - - - True - - - True - profiles_storage - - - - 0 - - - - - 0 - - - - - True - True - True - - - True - gtk-add - - - - - False - 1 - - - - - True - True - True - - - True - gtk-remove - - - - - False - 2 - - - - - - - - - - diff --git a/cream/config/org.cream.melange.gschema.xml b/cream/config/org.cream.melange.gschema.xml new file mode 100644 index 0000000..32d1a64 --- /dev/null +++ b/cream/config/org.cream.melange.gschema.xml @@ -0,0 +1,26 @@ + + + + + [] + + + false + + + + + false + + + + 10 + + + 'Text' + + + 3.14 + + + diff --git a/cream/config/widgets.py b/cream/config/widgets.py new file mode 100644 index 0000000..bae5a46 --- /dev/null +++ b/cream/config/widgets.py @@ -0,0 +1,125 @@ +from gi.repository import Gtk as gtk, GObject as gobject + + +class Widget(gobject.GObject): + + __gsignals__ = { + 'value-changed': (gobject.SignalFlags.RUN_LAST, None, (object,)), + } + + def __init__(self, gtk_widget): + + gobject.GObject.__init__(self) + + self.widget = gtk_widget + + + def on_value_changed(self, *args): + + self.emit('value-changed', self.get_value()) + + + +class BooleanWidget(Widget): + + def __init__(self, value): + + Widget.__init__(self, gtk.CheckButton()) + + self.widget.connect('toggled', self.on_value_changed) + + self.set_value(value) + + + def get_value(self): + return self.widget.get_active() + + def set_value(self, value): + self.widget.set_active(value) + + +class IntegerWidget(Widget): + + def __init__(self, value): + + Widget.__init__(self, gtk.SpinButton()) + + self.widget.set_range(0, 100) + self.widget.set_increments(1, self.widget.get_increments()[0]) + self.widget.connect('value-changed', self.on_value_changed) + + self.set_value(value) + + + def get_value(self): + return int(self.widget.get_value()) + + def set_value(self, value): + self.widget.set_value(value) + + +class FloatWidget(Widget): + + def __init__(self, value): + + Widget.__init__(self, gtk.SpinButton()) + + self.widget.set_range(0, 100) + self.widget.set_increments(0.01, self.widget.get_increments()[0]) + self.widget.set_digits(2) + self.widget.connect('value-changed', self.on_value_changed) + + self.set_value(value) + + + def get_value(self): + return self.widget.get_value() + + def set_value(self, value): + self.widget.set_value(value) + + +class CharWidget(Widget): + + def __init__(self, value): + + Widget.__init__(self, gtk.Entry()) + + self.widget.connect('changed', self.on_value_changed) + + self.set_value(value) + + + def get_value(self): + return self.widget.get_text() + + def set_value(self, value): + self.widget.set_text(value) + + +"""class MultiOptionWidget(Widget): + + def __init__(self, value): + + class ComboBoxText(gtk.ComboBox): + def __init__(self): + gtk.ComboBox.__init__(self) + self.liststore = gtk.ListStore(str) + self.set_model(self.liststore) + cell = gtk.CellRendererText() + self.pack_start(cell, True) + self.add_attribute(cell, 'text', 0) + + Widget.__init__(self, ComboBoxText()) + + for v in value: + self.widget.liststore.append((v,)) + + self.widget.connect('changed', self.on_value_changed) + + + def get_value(self): + pass + + def set_value(self, value): + pass"""