# Copyright (C) 2009, Tutorius.org # # 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 St, Fifth Floor, Boston, MA 02110-1301 USA """ This module contains properties class that can be included in other types. TutoriusProperties have the same behaviour as python properties (assuming you also use the TPropContainer), with the added benefit of having builtin dialog prompts and constraint validation. """ from sugar.tutorius.constraints import Constraint, \ UpperLimitConstraint, LowerLimitConstraint, \ MaxSizeConstraint, MinSizeConstraint, \ ColorConstraint, FileConstraint, BooleanConstraint, EnumConstraint from copy import copy class TPropContainer(object): """ A class containing properties. This does the attribute wrapping between the container instance and the property value. As properties are on the containing classes, they allow static introspection of those types at the cost of needing a mapping between container instances, and property values. This is what TPropContainer does. """ def __init__(self): """ Prepares the instance for property value storage. This is done at object initialization, thus allowing initial mapping of properties declared on the class. Properties won't work correctly without this call. """ # create property value storage object.__setattr__(self, "_props", {}) for attr_name in dir(type(self)): propinstance = object.__getattribute__(self, attr_name) if isinstance(propinstance, TutoriusProperty): # only care about TutoriusProperty instances propinstance.tname = attr_name self._props[attr_name] = propinstance.validate( copy(propinstance.default)) def __getattribute__(self, name): """ Process the 'fake' read of properties in the appropriate instance container. Pass 'real' attributes as usual. """ try: props = object.__getattribute__(self, "_props") except AttributeError: # necessary for deepcopy as order of init can't be guaranteed object.__setattr__(self, "_props", {}) props = object.__getattribute__(self, "_props") try: # try gettin value from property storage # if it's not in the map, it's not a property or its default wasn't # set at initialization. return props[name] except KeyError: return object.__getattribute__(self, name) def __setattr__(self, name, value): """ Process the 'fake' write of properties in the appropriate instance container. Pass 'real' attributes as usual. @param name the name of the property @param value the value to assign to name @return the setted value """ props = object.__getattribute__(self, "_props") try: # We attempt to get the property object with __getattribute__ # to work through inheritance and benefit of the MRO. return props.__setitem__(name, object.__getattribute__(self, name).validate(value)) except AttributeError: return object.__setattr__(self, name, value) def get_properties(self): """ Return the list of property names. """ return object.__getattribute__(self, "_props").keys() # Providing the hash methods necessary to use TPropContainers # in a dictionary, according to their properties def __hash__(self): #Return a hash of properties (key, value) sorted by key #We need to transform the list of property key, value lists into # a tuple of key, value tuples return hash(tuple(map(tuple,sorted(self._props.items(), cmp=lambda x, y: cmp(x[0], y[0]))))) def __eq__(self, e2): return isinstance(e2, type(self)) and self._props == e2._props # Adding methods for pickling and unpickling an object with # properties def __getstate__(self): return self._props.copy() def __setstate__(self, dict): self._props.update(dict) class TutoriusProperty(object): """ The base class for all actions' properties. The interface is the following : value : the value of the property type : the type of the property get_contraints() : the constraints inserted on this property. They define what is acceptable or not as values. """ def __init__(self): super(TutoriusProperty, self).__init__() self.type = None self._constraints = None self.default = None def get_constraints(self): """ Returns the list of constraints associated to this property. """ if self._constraints is None: self._constraints = [] for i in dir(self): typ = getattr(self, i) if isinstance(typ, Constraint): self._constraints.append(i) return self._constraints def validate(self, value): """ Validates the value of the property. If the value does not respect the constraints on the property, this method will raise an exception. The exception should be of the type related to the constraint that failed. E.g. When a int is to be set with a value that """ for constraint_name in self.get_constraints(): constraint = getattr(self, constraint_name) constraint.validate(value) return value class TAddonListProperty(TutoriusProperty): """ Stores an addon component list as a property. The purpose of this class is to allow correct mapping of properties through encapsulated hierarchies. """ pass class TIntProperty(TutoriusProperty): """ Represents an integer. Can have an upper value limit and/or a lower value limit. """ def __init__(self, value, lower_limit=None, upper_limit=None): TutoriusProperty.__init__(self) self.type = "int" self.upper_limit = UpperLimitConstraint(upper_limit) self.lower_limit = LowerLimitConstraint(lower_limit) self.default = self.validate(value) class TFloatProperty(TutoriusProperty): """ Represents a floating point number. Can have an upper value limit and/or a lower value limit. """ def __init__(self, value, lower_limit=None, upper_limit=None): TutoriusProperty.__init__(self) self.type = "float" self.upper_limit = UpperLimitConstraint(upper_limit) self.lower_limit = LowerLimitConstraint(lower_limit) self.default = self.validate(value) class TStringProperty(TutoriusProperty): """ Represents a string. Can have a maximum size limit. """ def __init__(self, value, size_limit=None): TutoriusProperty.__init__(self) self.type = "string" self.size_limit = MaxSizeConstraint(size_limit) self.default = self.validate(value) class TArrayProperty(TutoriusProperty): """ Represents an array of properties. Can have a maximum number of element limit, but there are no constraints on the content of the array. """ def __init__(self, value, min_size_limit=None, max_size_limit=None): TutoriusProperty.__init__(self) self.type = "array" self.max_size_limit = MaxSizeConstraint(max_size_limit) self.min_size_limit = MinSizeConstraint(min_size_limit) self.default = tuple(self.validate(value)) #Make this thing hashable def __setstate__(self, state): self.max_size_limit = MaxSizeConstraint(state["max_size_limit"]) self.min_size_limit = MinSizeConstraint(state["min_size_limit"]) self.value = state["value"] def __getstate__(self): return dict( max_size_limit=self.max_size_limit.limit, min_size_limit=self.min_size_limit.limit, value=self.value, ) class TColorProperty(TutoriusProperty): """ Represents a RGB color with 3 8-bit integer values. The value of the property is the array [R, G, B] """ def __init__(self, red=None, green=None, blue=None): TutoriusProperty.__init__(self) self.type = "color" self.color_constraint = ColorConstraint() self._red = red or 0 self._green = green or 0 self._blue = blue or 0 self.default = self.validate([self._red, self._green, self._blue]) class TFileProperty(TutoriusProperty): """ Represents a path to a file on the disk. """ def __init__(self, path): """ Defines the path to an existing file on disk file. For now, the path may be relative or absolute, as long as it exists on the local machine. TODO : Make sure that we have a file scheme that supports distribution on other computers (LP 355197) """ TutoriusProperty.__init__(self) self.type = "file" self.file_constraint = FileConstraint() self.default = self.validate(path) class TEnumProperty(TutoriusProperty): """ Represents a value in a given enumeration. This means that the value will always be one in the enumeration and nothing else. """ def __init__(self, value, accepted_values): """ Creates the enumeration property. @param value The initial value of the enum. Must be part of accepted_values @param accepted_values A list of values that the property can take """ TutoriusProperty.__init__(self) self.type = "enum" self.enum_constraint = EnumConstraint(accepted_values) self.default = self.validate(value) class TBooleanProperty(TutoriusProperty): """ Represents a True or False value. """ def __init__(self, value=False): TutoriusProperty.__init__(self) self.type = "boolean" self.boolean_constraint = BooleanConstraint() self.default = self.validate(value) class TUAMProperty(TutoriusProperty): """ Represents a widget of the interface by storing its UAM. """ def __init__(self, value=None): TutoriusProperty.__init__(self) self.type = "uam" class TAddonProperty(TutoriusProperty): """ Reprensents an embedded tutorius Addon Component (action, trigger, etc.) The purpose of this class is to flag the container for proper serialization, as the contained object can't be directly dumped to text for its attributes to be saved. """ class NullAction(TPropContainer): def do(self): pass def undo(self): pass def __init__(self): super(TAddonProperty, self).__init__() self.type = "addon" self.default = self.NullAction() def validate(self, value): if isinstance(value, TPropContainer): return super(TAddonProperty, self).validate(value) raise ValueError("Expected TPropContainer instance as TaddonProperty value") class TEventType(TutoriusProperty): """ Represents an GUI signal for a widget. """ def __init__(self, value): super(TEventType, self).__init__() self.type = "gtk-signal" self.default = self.validate(value) class TAddonListProperty(TutoriusProperty): """ Reprensents an embedded tutorius Addon List Component. See TAddonProperty """ def __init__(self): TutoriusProperty.__init__(self) self.type = "addonlist" self.default = [] def validate(self, value): if isinstance(value, list): for component in value: if not (isinstance(component, TPropContainer)): raise ValueError("Expected a list of TPropContainer instances inside TAddonListProperty value, got a %s" % (str(type(component)))) return value raise ValueError("Value proposed to TAddonListProperty is not a list")