# -*- coding: iso-8859-15 -*-


# Glasnost
# By: Odile Bénassy <obenassy@entrouvert.com>
#     Romain Chantereau <rchantereau@entrouvert.com>
#     Nicolas Clapiès <nclapies@easter-eggs.org>
#     Pierre-Antoine Dejace <padejace@entrouvert.be>
#     Thierry Dulieu <tdulieu@easter-eggs.com>
#     Florent Monnier <monnier@codelutin.com>
#     Cédric Musso <cmusso@easter-eggs.org>
#     Frédéric Péters <fpeters@entrouvert.be>
#     Benjamin Poussin <poussin@codelutin.com>
#     Emmanuel Raviart <eraviart@entrouvert.com>
#     Sébastien Régnier <regnier@codelutin.com>
#     Emmanuel Saracco <esaracco@easter-eggs.com>
#
# Copyright (C) 2000, 2001 Easter-eggs & Emmanuel Raviart
# Copyright (C) 2002 Odile Bénassy, Code Lutin, Thierry Dulieu, Easter-eggs,
#     Entr'ouvert, Frédéric Péters, Benjamin Poussin, Emmanuel Raviart,
#     Emmanuel Saracco & Théridion
# Copyright (C) 2003 Odile Bénassy, Romain Chantereau, Nicolas Clapiès,
#     Code Lutin, Pierre-Antoine Dejace, Thierry Dulieu, Easter-eggs,
#     Entr'ouvert, Florent Monnier, Cédric Musso, Ouvaton, Frédéric Péters,
#     Benjamin Poussin, Rodolphe Quiédeville, Emmanuel Raviart, Sébastien
#     Régnier, Emmanuel Saracco, Théridion & Vecam
#
# 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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.


from __future__ import nested_scopes


__doc__ = """Glasnost Common Kinds"""

__version__ = '$Revision$'[11:-2]


import base64
import copy
import cStringIO
import locale
import marshal
import re
import string
import sys
import time
import traceback
import types

import jails
import slots
import translation
import things
import system
from tools import *
import tools_new as commonTools

## import connectors # Do not remove!
import functions # Do not remove!
import modes # Do not remove!
import properties # Do not remove!
import uploads # Do not remove!
import values # Do not remove!
import widgets # Do not remove!


register = things.register


class BaseKind(things.BaseThing):
    balloonHelp = None
    class balloonHelp_kindClass:
        _kindName = 'String'
        label = N_('Balloon Help')
        stateInViewMode = 'read-only/hidden-if-empty'
        widget_rows = 4
        widgetName = 'TextArea'

    containerNames = None
    class containerNames_kindClass:
        _kindName = 'Sequence'
        importExport = 'private'
        itemKind_valueName = 'KindName'
        label = N_('Containers')
        stateInEditMode = 'hidden'
        stateInViewMode = 'hidden'

    defaultValue = None
    defaultValue_kind_importExport = 'private'
    defaultValue_kind_label = N_('Default Value')
    defaultValue_kind_stateInEditMode = 'hidden'
    defaultValue_kind_stateInViewMode = 'hidden'
    defaultValue_kindName = None # To override

##     helpAlias = None
##     class helpAlias_kindClass:
##         _kindName = 'Alias'
##         label = N_('Help Alias')

    getter = None

    importExport = 'public'
    class importExport_kindClass:
        _kindName = 'Choice'
        importExport = 'private'
        isRequired = 1
        label = N_('Protection')
        stateInEditMode = 'hidden'
        stateInViewMode = 'hidden'
        labels = {
                'from-server-only': N_('Read Only'),
                'private': N_('Private'),
                'public': N_('Read/Write'),
                'to-server-only': N_('Write Only'),
                }
        values = [
                'private',
                'to-server-only',
                'from-server-only',
                'public',
                ]

    isAutomaticallyModified = 0
    class isAutomaticallyModified_kindClass:
        _kindName = 'Boolean'
        importExport = 'private'
        stateInEditMode = 'hidden'
        stateInViewMode = 'hidden'

    # When isModifiable is false, the slot can only be set once and not
    # modified.
    isModifiable = 1
    class isModifiable_kindClass:
        _kindName = 'Boolean'
        importExport = 'private'
        stateInEditMode = 'hidden'
        stateInViewMode = 'hidden'

    # When false, isPractical means that the kind can not be used to describe
    # a real value, but only to describe an expected value.
    isPractical = 1
    class isPractical_kindClass:
        _kindName = 'Boolean'
        importExport = 'private'
        stateInEditMode = 'hidden'
        stateInViewMode = 'hidden'

    isRequired = 0
    class isRequired_kindClass:
        _kindName = 'Boolean'
        label = N_('Mandatory')
##         labels = {
##                 '0': N_('Optional'),
##                 '1': N_('Required'),
##                 }

    isRequiredInEditMode = 0
    class isRequiredInEditMode_kindClass:
        _kindName = 'Boolean'
        importExport = 'private'
        stateInEditMode = 'hidden'
        stateInViewMode = 'hidden'

    isTranslatable = 0
    class isTranslatable_kindClass:
        _kindName = 'Boolean'
        importExport = 'private'
        label = N_('Translatable')
##         labels = {
##                 '0': N_('Invariant'),
##                 '1': N_('Translatable'),
##                 }
        stateInEditMode = 'hidden'
        stateInViewMode = 'hidden'

    label = None
    class label_kindClass:
        _kindName = 'String'
        balloonHelp = N_('Enter a user-friendly field name')
        label = N_('Field Label')
        stateInViewMode = 'read-only/hidden-if-empty'

    labelPlural = None
    class labelPlural_kindClass:
        _kindName = 'String'
        importExport = 'private'
        label = N_('Label (plural form)')
        stateInEditMode = 'hidden'
        stateInViewMode = 'hidden'

    possibleWidgetNames = None
    class possibleWidgetNames_kindClass:
        _kindName = 'Sequence'
        importExport = 'private'
        itemKind_valueName = 'WidgetName'
        stateInEditMode = 'hidden'
        stateInViewMode = 'hidden'

    pythonStorageType = None
    # not defined as a kind since it would require a new kind PythonType that
    # would only be useful here.

    stateInCreateMode = None
    class stateInCreateMode_kindClass:
        _kindName = 'Choice'
        importExport = 'private'
        label = N_('Creation Mode')
        labels = {
                '': N_('Same As Edition Mode'),
                'hidden': N_('Hidden'),
                'read-only': N_('Read Only'),
                'read-only/hidden-if-empty': N_('Read Only / Hidden If Empty'),
                'read-write': N_('Read/Write'),
                }
        sortLabels = 0
        stateInEditMode = 'hidden'
        stateInViewMode = 'hidden'
        values = [
                None,
                'hidden',
                'read-only/hidden-if-empty',
                'read-only',
                'read-write',
                ]

    stateInEditMode = 'read-write'
    class stateInEditMode_kindClass:
        _kindName = 'Choice'
        importExport = 'private'
        isRequired = 1
        label = N_('Edition Mode')
        labels = {
                'hidden': N_('Hidden'),
                'read-only': N_('Read Only'),
                'read-only/hidden-if-empty': N_('Read Only / Hidden If Empty'),
                'read-write': N_('Read/Write'),
                }
        sortLabels = 0
        stateInEditMode = 'hidden'
        stateInViewMode = 'hidden'
        values = [
                'hidden',
                'read-only/hidden-if-empty',
                'read-only',
                'read-write',
                ]

    stateInViewMode = 'read-only/hidden-if-empty'
    class stateInViewMode_kindClass:
        _kindName = 'Choice'
        importExport = 'private'
        isRequired = 1
        label = N_('View Mode')
        labels = {
                'hidden': N_('Hidden'),
                'read-only': N_('Read Only'),
                'read-only/hidden-if-empty': N_('Read Only / Hidden If Empty'),
                }
        sortLabels = 0
        stateInEditMode = 'hidden'
        stateInViewMode = 'hidden'
        values = [
                'hidden',
                'read-only/hidden-if-empty',
                'read-only',
                ]

    tagInCreateMode = None
    class tagInCreateMode_kindClass:
        _kindName = 'Choice'
        importExport = 'private'
        label = N_('Creation Aspect')
        labels = {
                '': N_('Same As Edition Aspect'),
                'div': N_('Standard Without Label'),
                'div-with-label': N_('Standard With Label'),
                'h1': N_('Header 1'),
                'h2': N_('Header 2'),
                'h3': N_('Header 3'),
                'h4': N_('Header 4'),
                'h5': N_('Header 5'),
                'h6': N_('Header 6'),
                }
        sortLabels = 0
        stateInEditMode = 'hidden'
        stateInViewMode = 'hidden'
        values = [
                None,
                'div-with-label',
                'div',
                'h1',
                'h2',
                'h3',
                'h4',
                'h5',
                'h6',
                ]

    tagInEditMode = 'div-with-label'
    class tagInEditMode_kindClass:
        _kindName = 'Choice'
        importExport = 'private'
        isRequired = 1
        label = N_('Edition Aspect')
        labels = {
                'div': N_('Standard Without Label'),
                'div-with-label': N_('Standard With Label'),
                'h1': N_('Header 1'),
                'h2': N_('Header 2'),
                'h3': N_('Header 3'),
                'h4': N_('Header 4'),
                'h5': N_('Header 5'),
                'h6': N_('Header 6'),
                }
        sortLabels = 0
        stateInEditMode = 'hidden'
        stateInViewMode = 'hidden'
        values = [
                'div-with-label',
                'div',
                'h1',
                'h2',
                'h3',
                'h4',
                'h5',
                'h6',
                ]

    tagInViewMode = 'div-with-label'
    class tagInViewMode_kindClass:
        _kindName = 'Choice'
        importExport = 'private'
        isRequired = 1
        label = N_('View Aspect')
        labels = {
                'div': N_('Standard Without Label'),
                'div-with-label': N_('Standard With Label'),
                'h1': N_('Header 1'),
                'h2': N_('Header 2'),
                'h3': N_('Header 3'),
                'h4': N_('Header 4'),
                'h5': N_('Header 5'),
                'h6': N_('Header 6'),
                }
        sortLabels = 0
        stateInEditMode = 'hidden'
        stateInViewMode = 'hidden'
        values = [
                'div-with-label',
                'div',
                'h1',
                'h2',
                'h3',
                'h4',
                'h5',
                'h6',
                ]

    textFormat = None
    class textFormat_kindClass:
        _kindName = 'Choice'
        importExport = 'private'
        label = N_('Text Format')
        labels = {
                '': N_('Text'),
                'docbook': N_('DocBook'),
                'html': N_('HTML'),
                'rst': N_('reStructuredText'),
                'spip': N_('SPIP'),
                'text': N_('Preformatted Text'),
                }
        stateInEditMode = 'hidden'
        stateInViewMode = 'hidden'
        values = [
                None,
                'docbook',
                'html',
                'rst',
                'spip',
                'text',
                ]

    textMaxLength = None
    class textMaxLength_kindClass:
        _kindName = 'Integer'
        importExport = 'private'
        label = N_('Maximum Number of Characters')
        stateInEditMode = 'hidden'
        stateInViewMode = 'hidden'

    thingCategory = 'kind'

    thingPublicCategory_kind_stateInEditMode = 'read-only/hidden-if-empty'
    thingPublicCategory_kind_stateInViewMode = 'read-only/hidden-if-empty'

    thingPublicName_kind_label = N_('Type')
    thingPublicName_kind_stateInEditMode = 'read-only/hidden-if-empty'
    thingPublicName_kind_stateInViewMode = 'read-only/hidden-if-empty'

    useCustomStorage = 0
    class useCustomStorage_kindClass:
        _kindName = 'Boolean'
        importExport = 'private'
        stateInEditMode = 'hidden'
        stateInViewMode = 'hidden'

    useFileStorage = 0
    class useFileStorage_kindClass:
        _kindName = 'Boolean'
        importExport = 'from-server-only'
        label = N_('Storage')
        labels = {
                '0': N_('Internal'),
                '1': N_('In External File'),
                }
        stateInEditMode = 'hidden'
        stateInViewMode = 'hidden'

    widget = None
    class widget_kindClass:
        _kindName = 'Widget'
        isRequired = 1
        label = N_('Widget')

        def getGroupedValues(self, slot):
            return None

        def getter(self, slot):
            return slot.getContainer().getModelWidget(slot.parent)

        def getValues(self, slot):
            values = slot.getContainer().getPossibleWidgetNames()
            if values:
                return values
            else:
                return ['InputText']

    widgetName = 'InputText'
    class widgetName_kindClass:
        _kindName = 'WidgetName'
        importExport = 'private'
        stateInEditMode = 'hidden'
        stateInViewMode = 'hidden'

    def __init__(self, **attributes):
        things.BaseThing.__init__(self)
        for name, value in attributes.items():
            # Ensure that each argument has been declared as an attribute.
            if not hasattr(self.__class__, name):
                raise Exception('Undeclared attribute %s for %s' % (
                    name, self.__class__.__name__))
            if getattr(self, name) == value:
                continue
            setattr(self, name, value)

    def accepts(self, kind):
        """Test whether the given kind is always compatible with self.

        For example:
        - Integer accepts Integer.
        - Number accepts Integer.
        - Integer doesn't accept Number.
        """

        return self.includes(kind)

    def acquireNonCoreValue(self, slot, objectDirectoryPath):
        pass

    def buildOptions(self, options):
        for name, value in options.items():
            # Ensure that each option has been declared as an attribute.
            nameBase = name.split('_')[0]
            if not hasattr(self.__class__, nameBase):
                raise Exception('Undeclared attribute %s for %s' % (
                    name, self.__class__.__name__))
            if hasattr(self, name) and getattr(self, name) == value:
                continue
            setattr(self, name, value)

    def checkModelValue(self, slot, value):
        assert self.isPractical
        if self.isRequired and value is None:
            raise faults.MissingSlotValue(slot)
        if value is None:
            return
        if type(value) is not self.pythonStorageType:
            raise faults.BadSlotValue(slot, value)

    def checkModelValueHolder(self, slot, valueHolder):
        assert self.isPractical
        if not self.accepts(valueHolder.kind):
            raise faults.KindsIncompatibility(self, valueHolder.kind)
        self.checkModelValue(slot, valueHolder.value)

    def convertValueFromOtherType(self, value):
        if value is None and self.pythonStorageType is types.StringType:
            return ''
        if type(value) is types.InstanceType:
            raise NotImplementedError
        try:
            return self.pythonStorageType(value)
        except:
            raise 'convertValueFrom not implemented for %s' % \
                    repr((self, type(value)))
        raise NotImplementedError

    def convertValueToOtherType(self, value, otherType):
        if value is None and otherType is types.StringType:
            return ''
        raise 'convertValueTo not implemented for %s' % \
                repr((self, otherType))
        raise NotImplementedError
        
    def convertValueIds(
            self, slot, value, sourceDispatcherId, destinationDispatcherId):
        assert self.isPractical
        return value

    def equals(self, kind):
        """Test whether two kinds are equal."""

        return self.getThingName() == kind.getThingName()

    def executeModel(self, slot, when, command = None):
        pass

    def exportValueToXmlRpc(self, slot, value):
        assert self.isPractical
        if type(value) in (types.StringType, types.UnicodeType):
            value = utf8(value)
        return value

    def getBalloonHelp(self):
        return self.balloonHelp
    
    def getCmpFunction(self):
        return cmp

    def getDefaultValue(self, slot):
        return self.defaultValue

    def getModelFieldTag(self, slot):
        layoutMode = context.getVar('layoutMode')
        if layoutMode == 'edit':
            isCreateEditMode = context.getVar('isCreateEditMode', default = 0)
            if isCreateEditMode and self.tagInCreateMode is not None:
                return self.tagInCreateMode
            else:
                return self.tagInEditMode
        elif layoutMode == 'use':
            aspect = context.getVar('aspect')
            return aspect.htmlTag
        else:
            return self.tagInViewMode

    def getModelLabel(self, slot):
        if self.label:
            return self.label
        return slot.getLabel()

    def getModelLabelPlural(self, slot):
        if self.labelPlural:
            return self.labelPlural
        return self.getModelLabel(slot)

    def getModelSlotByPath(self, slot, path):
        if path.startswith('self'):
            path = path[len('self'):]
        if not path:
            return slot
        raise Exception('Path "%s" not found' % path)

    def getModelWidget(self, slot, forceEmbedding = 0):
        if self.widget is None:
            widgetOptionHeader = 'widget_'
            widgetOptionHeaderLen = len(widgetOptionHeader)
            widgetOptionNames = [
                name
                for name in self.__dict__.keys()
                if name.startswith(widgetOptionHeader) \
                and not name.startswith(widgetOptionHeader + 'kind')]
            baseClasses = self.getC3ClassLinearization()
            for baseClass in baseClasses:
                for name in baseClass.__dict__.keys():
                    if name.startswith(widgetOptionHeader) \
                       and not name.startswith(widgetOptionHeader + 'kind') \
                       and name not in widgetOptionNames:
                        widgetOptionNames.append(name)
            widgetOptions = {}
            for widgetOptionName in widgetOptionNames:
                widgetOptions[widgetOptionName[widgetOptionHeaderLen:]
                              ] = getattr(self, widgetOptionName)
            widget = self.newModelWidget(slot)
            widget.buildOptions(widgetOptions)
            self.widget = widget
        return self.widget

    def getOrderedLayoutSlotNames(self, parentSlot = None):
        slotNames = things.BaseThing.getOrderedLayoutSlotNames(
                self, parentSlot = parentSlot)
        slotNames += [
                'label', 'labelPlural', 'widgetName', 'widget', 'textFormat',
                'importExport',
                'tagInCreateMode', 'stateInCreateMode',
                'tagInEditMode', 'stateInEditMode',
                'tagInViewMode', 'stateInViewMode',
                'isRequired', 'defaultValue', 'balloonHelp', 'textMaxLength',
##                 'helpAlias',
                'useFileStorage', 'useCommonStorage']
        return slotNames

    def getPossibleWidgetNames(self):
        assert self.isPractical
        if self.possibleWidgetNames is not None:
            return self.possibleWidgetNames
        return [self.widgetName]

    def getRealKindClass(self):
        return self.__class__.__bases__[-1]

    def getTextFormat(self, slot):
        return self.textFormat

    def getTextMaxLength(self):
        return self.textMaxLength

    def importValueFromXmlRpc(self, slot, value):
        assert self.isPractical
        if type(value) in (types.StringType, types.UnicodeType):
            value = iso8859_15(value)
            value = value.replace('\r\n', '\n') # MS-DOS return character
            value = value.replace('\r', '\n') # MacOS return character
        return value

    def includes(self, kind):
        """Test whether the given kind is included in self.

        For example:
        - Integer includes Integer.
        - Number includes Integer.
        - Integer doesn't include Number.
        """

        if self.equals(kind):
            return 1
        if kind.containerNames is None:
            return 0
        for kindContainerName in kind.containerNames:
            if self.includes(commonTools.newThing(
                    'kind', kindContainerName)):
                return 1
        return 0

    def intersects(self, kind):
        """Test whether the given kind has an intersection with self.

        For example:
        - Integer has an intersection with Integer.
        - Number has an intersection with Integer.
        - Integer has an intersection with Number.
        """

        # To override, because the test below is incomplete.
        return self.includes(kind) or kind.includes(self)

    def isEmptyModelValue(self, slot, value):
        return value is None

    def isExportable(self):
        raise NotImplementedError

    def isHidden(self, slot):
        layoutMode = context.getVar('layoutMode')
        if layoutMode == 'edit':
            isCreateEditMode = context.getVar('isCreateEditMode', default = 0)
            if isCreateEditMode:
                return self.stateInCreateMode == 'hidden' \
                      or (self.stateInCreateMode == 'read-only/hidden-if-empty'
                      and self.modelValueIsEmpty(slot)) \
                      or (self.stateInCreateMode is None
                      and (self.stateInEditMode == 'hidden'
                      or (self.stateInEditMode == 'read-only/hidden-if-empty'
                      and self.modelValueIsEmpty(slot))))
            else:
                return self.stateInEditMode == 'hidden' \
                       or self.stateInEditMode == 'read-only/hidden-if-empty' \
                       and self.modelValueIsEmpty(slot)
        elif layoutMode == 'use':
            aspect = context.getVar('aspect')
            return aspect.state == 'hidden' \
                   or aspect.state == 'read-only/hidden-if-empty' \
                   and self.modelValueIsEmpty(slot)
        else:
            return self.stateInViewMode == 'hidden' \
                   or self.stateInViewMode == 'read-only/hidden-if-empty' \
                   and self.modelValueIsEmpty(slot)

    def isImportable(self):
        raise NotImplementedError

    def isInCore(self, slot):
        return not self.useCustomStorage and not self.useFileStorage

    def isReadOnly(self, slot):
        layoutMode = context.getVar('layoutMode')
        if layoutMode == 'edit':
            isCreateEditMode = context.getVar('isCreateEditMode', default = 0)
            if isCreateEditMode:
                return self.stateInCreateMode == 'read-only' \
                      or (self.stateInCreateMode == 'read-only/hidden-if-empty'
                      and not self.modelValueIsEmpty(slot)) \
                      or (self.stateInCreateMode is None
                      and (self.stateInEditMode == 'read-only'
                      or (self.stateInEditMode == 'read-only/hidden-if-empty'
                      and not self.modelValueIsEmpty(slot))))
            else:
                return self.stateInEditMode == 'read-only' \
                       or self.stateInEditMode == 'read-only/hidden-if-empty' \
                       and not self.modelValueIsEmpty(slot)
        elif layoutMode == 'use':
            aspect = context.getVar('aspect')
            return aspect.state == 'read-only' \
                   or aspect.state == 'read-only/hidden-if-empty' \
                   and not self.modelValueIsEmpty(slot)
        else:
            return self.stateInViewMode == 'read-only' \
                   or self.stateInViewMode == 'read-only/hidden-if-empty' \
                   and not self.modelValueIsEmpty(slot)

    def mayAccept(self, kind):
        """Test whether the given kind may be compatible with self.

        For example:
        - Integer always accepts Integer.
        - Number always accepts Integer.
        - Integer may accept Number, because sometimes a Number is an Integer.
        """

        return self.intersects(kind)

    def modelValueIsEmpty(self, slot):
        return self.isEmptyModelValue(slot, slot.getValue())

    def newJail(self, slot):
        return jails.Jail(slot)

    def releaseNonCoreValue(self, slot):
        pass

    def removeValueIds(self, slot, value, rolesToKeep):
        return value

    def repairValue(self, slot, value, toVersion):
        assert self.isPractical
        if toVersion == 5004:
            if type(value) in (types.StringType, types.UnicodeType):
                repairedValue = value.replace('\r\n', '\n')
                repairedValue = repairedValue.replace('\r', '\n')
                if repairedValue != value:
                    return repairedValue
        return None

    def saveNonCoreValue(self, slot, objectDirectoryPath):
        pass

    def setAutomaticalValue(self, slot, init = 0):
        raise NotImplementedError

    def setToDefaultValue(self, slot):
        defaultValue = self.getDefaultValue(slot)
        if defaultValue is not None:
            slot.setValue(defaultValue)

    def upgradeModel(self, slot, model, toVersion):
        changed = 0
        # Call upgradeModel_xxx methods.
        classes = commonTools.getC3ClassLinearization(self.__class__)
        upgradeMethodNames = []
        for class_ in classes:
            for attributeName in class_.__dict__.keys():
                if attributeName.startswith('upgradeModel_'):
                    if not attributeName in upgradeMethodNames:
                        upgradeMethodNames.append(attributeName)
        upgradeMethodNames.sort()
        for upgradeMethodName in upgradeMethodNames:
            if upgradeMethodName[len('upgradeModel_'):] < toVersion:
                continue
            modelChanged, model = getattr(self, upgradeMethodName)(slot, model)
            changed = changed or modelChanged
        return changed, model
register(BaseKind)


class AbstractSequence(BaseKind):
    canInsertOrDelete = 1

    minCount = None
    minCount_kind_label = N_('Minimum Number of Items')
    minCount_kindName = 'Integer'

    maxCount = None
    maxCount_kind_label = N_('Maximum Number of Items')
    maxCount_kindName = 'Integer'

    widgetName = 'Multi'

    def checkModelValue(self, slot, value):
        BaseKind.checkModelValue(self, slot, value)
        if value is None:
            return
        if self.minCount and len(value) < self.minCount:
            raise faults.BadSlotValue(slot, value)
        if self.maxCount and len(value) > self.maxCount:
            raise faults.BadSlotValue(slot, value)

    def convertValueIds(self, slot, value, sourceDispatcherId,
                        destinationDispatcherId):
        try:
            len(value)
        except TypeError:
            raise Exception('Unsized value (= %s) for slot %s' % (value, slot))
        for i in range(len(value)):
            itemSlot = self.getItemSlot(slot, i)
            newItem = itemSlot.getKind().convertValueIds(
                    itemSlot, value[i], sourceDispatcherId,
                    destinationDispatcherId)
            if newItem != value[i]:
                value[i] = newItem
        return value

    def executeModel(self, slot, when, command = None):
        value = slot.getValue()
        if value is None:
            return
        for i in range(len(value)):
            itemSlot = self.getItemSlot(slot, i)
            itemSlot.getKind().executeModel(itemSlot, when, command = command)

    def exportValueToXmlRpc(self, slot, value):
        if value is None:
            return []
        exportedValue = []
        for i in range(len(value)):
            itemSlot = self.getItemSlot(slot, i)
            exportedValue.append(itemSlot.getKind().exportValueToXmlRpc(
                itemSlot, value[i]))
        return exportedValue

    def getItemKind(self, index):
        raise NotImplementedError

    def getMinCount(self, slot):
        return self.minCount

    def getMaxCount(self, slot):
        return self.maxCount

    def getItemSlot(self, slot, index):
        return slots.Item(index, parent = slot)

    def getModelSlotByPath(self, slot, path):
        if path.startswith('self'):
            path = path[len('self'):]
        if not path:
            return slot
        if path[0] != '[' or not ']' in path:
            raise Exception('Path "%s" not found' % path)
        index = int(path[1:path.index(']')])
        return self.getItemKind(index).getModelSlotByPath(
            self.getItemSlot(slot, index), path[path.index(']') + 1:])

    def getOrderedLayoutSlotNames(self, parentSlot = None):
        slotNames = BaseKind.getOrderedLayoutSlotNames(
                self, parentSlot = parentSlot)
        slotNames += ['maxCount']
        return slotNames

    def importValueFromXmlRpc(self, slot, importedValue):
        if importedValue is None:
            return []
        value = []
        slot.value = value
        for i in range(len(importedValue)):
            itemSlot = self.getItemSlot(slot, i)
            value.append(itemSlot.getKind().importValueFromXmlRpc(
                itemSlot, importedValue[i]))
        del slot.value
        return value

    def newJail(self, slot):
        return jails.MutableSequence(slot)

    def removeValueIds(self, slot, value, rolesToKeep):
        indexesToDelete = []
        for i in range(len(value)):
            itemSlot = self.getItemSlot(slot, i)
            newItem = itemSlot.getKind().removeValueIds(
                itemSlot, value[i], rolesToKeep)
            if newItem != value[i]:
                indexesToDelete.append(i)
        indexesToDelete.reverse()
        for i in indexesToDelete:
            del value[i]
        if not value:
            return None
        return value

    def repairValue(self, slot, value, toVersion):
        if toVersion == 4000:
            changed = 0
            for i in range(len(value)):
                itemSlot = self.getItemSlot(slot, i)
                newItem = itemSlot.getKind().repairValue(
                    itemSlot, value[i], toVersion)
                if newItem:
                    changed = 1
                    value[i] = newItem
            if changed:
                return value
        return None

    def setToDefaultValue(self, slot):
        BaseKind.setToDefaultValue(self, slot)
        for i in range(len(slot.getValue() or [])):
            itemSlot = self.getItemSlot(slot, i)
            itemKind = itemSlot.getKind()
            itemKind.setToDefaultValue(itemSlot)

    def upgradeModel(self, slot, model, toVersion):
        changed, model = BaseKind.upgradeModel(self, slot, model, toVersion)
        # Upgrade each item.
        if model is not None:
            for i, item in zip(range(len(model)), model):
                itemSlot = self.getItemSlot(slot, i)
                itemChanged, item = itemSlot.getKind().upgradeModel(
                        itemSlot, item, toVersion)
                if itemChanged:
                    changed = 1
                    model[i] = item
        return changed, model


class Alias(BaseKind):
    defaultValue_kindName = 'Alias'
    pythonStorageType = types.StringType
    thingPublicCategory = None # N_('Glasnost')
    thingPublicName = N_('Alias')

    def checkModelValue(self, slot, value):
        BaseKind.checkModelValue(self, slot, value)
        if self.isRequired and value == '':
            raise faults.MissingSlotValue(slot)
        if value:
            if ' ' in value: # or '/' in value:
                raise faults.BadSlotValue(slot, value)
        
register(Alias)


class Any(BaseKind):
    defaultValue_kindName = 'Any'
    isPractical = 0
    thingPublicCategory = None # N_('Glasnost')
    thingPublicName = N_('Any')
register(Any)


class Boolean(BaseKind):
    containerNames = ['Any']

    defaultValue = 0
    defaultValue_kindName = 'Boolean'

    labels = {
        '0': N_('False'),
        '1': N_('True'),
        }
    labels_kind_keyKind_value_stateInEditMode = 'read-only'
    labels_kind_keyKind_valueName = 'String'
    labels_kind_label = N_('Labels')
    labels_kind_valueKind_valueName = 'String'
    labels_kindName = 'Mapping'

    possibleWidgetNames = ['InputCheckBox', 'Select']

    pythonStorageType = types.IntType
        # in Python 2.3 that could have been BooleanType

    sortLabels = 0
    sortLabels_kind_balloonHelp = N_(
            'Check the box to sort labels alphabetically')
    sortLabels_kind_importExport = 'private'
    sortLabels_kind_label = N_('Sort Labels')
    sortLabels_kind_stateInEditMode = 'hidden'
    sortLabels_kind_stateInViewMode = 'hidden'
    sortLabels_kindName = 'Boolean'

    thingPublicCategory = N_('Numbers')

    thingPublicName = N_('Boolean')

    titles = None
    titles_kind_importExport = 'private'
    titles_kind_keyKind_value_stateInEditMode = 'read-only'
    titles_kind_keyKind_valueName = 'String'
    titles_kind_label = N_('Titles')
    titles_kind_stateInEditMode = 'hidden'
    titles_kind_stateInViewMode = 'hidden'
    titles_kind_valueKind_valueName = 'String'
    titles_kindName = 'Mapping'

    values = [0, 1]
    values_kind_importExport = 'private'
    values_kind_itemKind_valueName = 'Boolean'
    values_kind_stateInEditMode = 'hidden'
    values_kind_stateInViewMode = 'hidden'
    values_kindName = 'Sequence'

    widgetName = 'InputCheckBox'

    def checkModelValue(self, slot, value):
        BaseKind.checkModelValue(self, slot, value)
        if value not in (None, 0, 1):
            raise faults.BadSlotValue(slot, value)

    def convertValueFromOtherType(self, value):
        if type(value) is types.StringType:
            if value in ('0', '1'):
                return int(value)
            raise faults.BadValue()
        return BaseKind.convertValueFromOtherType(self, value)

    def convertValueToOtherType(self, value, otherType):
        if otherType is types.StringType:
            return str(value)
        return BaseKind.convertValueToOtherType(self, value, otherType)

    def getGroupedValues(self, slot):
        return None

    def getLabels(self, slot):
        return self.labels

    def getSortedValues(self, slot):
        values = self.getValues(slot)
        if values is not None:
            values = values[:]
            labels = self.getLabels(slot)
            values.sort(lambda x, y:
                        locale.strcoll(_(labels[x]), _(labels[y])))
        return values

    def getValues(self, slot):
        return self.values

    def getOrderedLayoutSlotNames(self, parentSlot = None):
        slotNames = BaseKind.getOrderedLayoutSlotNames(
                self, parentSlot = parentSlot)
        slotNames += ['values', 'labels', 'titles']
        return slotNames

    def newJail(self, slot):
        return jails.Numeric(slot)
register(Boolean)


class Choice(BaseKind):
    defaultValue_kindName = 'Choice'

    groupedValues = None
    groupedValues_kind_importExport = 'private'
    groupedValues_kind_keyKind_valueName = 'String'
    groupedValues_kind_stateInEditMode = 'hidden'
    groupedValues_kind_stateInViewMode = 'hidden'
    groupedValues_kind_valueKind_valueName = 'String'
    groupedValues_kindName = 'Mapping'    
    
    # only use is in Metis; marked with a FIXME to remove it
    groupedValuesGetterName = None
    groupedValuesGetterName_kind_importExport = 'private'
    groupedValuesGetterName_kind_stateInEditMode = 'hidden'
    groupedValuesGetterName_kind_stateInViewMode = 'hidden'
    groupedValuesGetterName_kindName = 'PythonIdentifier'

    groupNames = None
    groupNames_kind_importExport = 'private'
    groupNames_kind_itemKind_valueName = 'String'
    groupNames_kind_stateInEditMode = 'hidden'
    groupNames_kind_stateInViewMode = 'hidden'
    groupNames_kindName = 'Sequence'    
    
    # only use is in Metis; marked with a FIXME to remove it
    groupNamesGetterName = None
    groupNamesGetterName_kind_importExport = 'private'
    groupNamesGetterName_kind_stateInEditMode = 'hidden'
    groupNamesGetterName_kind_stateInViewMode = 'hidden'
    groupNamesGetterName_kindName = 'PythonIdentifier'
    
    labels = None
    labels_kind_importExport = 'private'
    labels_kind_keyKind_valueName = 'String'
    labels_kind_label = N_('Labels')
    labels_kind_stateInEditMode = 'hidden'
    labels_kind_stateInViewMode = 'hidden'
    labels_kind_valueKind_valueName = 'String'
    labels_kindName = 'Mapping'

    possibleWidgetNames = ['Select', 'RadioButtons']
    
    pythonStorageType = types.StringType

    sortLabels = 1
    sortLabels_kind_importExport = 'private'
    sortLabels_kind_balloonHelp = N_(
            'Check the box to sort labels alphabetically')
    sortLabels_kind_label = N_('Sort Labels')
    sortLabels_kind_stateInEditMode = 'hidden'
    sortLabels_kind_stateInViewMode = 'hidden'
    sortLabels_kindName = 'Boolean'

    thingPublicCategory = N_('Data')

    thingPublicName = N_('Exclusive Choice')

    titles = None
    titles_kind_importExport = 'private'
    titles_kind_keyKind_value_stateInEditMode = 'read-only'
    titles_kind_keyKind_valueName = 'String'
    titles_kind_label = N_('Titles')
    titles_kind_stateInEditMode = 'hidden'
    titles_kind_stateInViewMode = 'hidden'
    titles_kind_valueKind_valueName = 'String'
    titles_kindName = 'Mapping'

    values = None
    values_kind_itemKind_valueName = 'String'
    values_kind_label = N_('Values')
    values_kindName = 'Sequence'

    widgetName = 'Select'

    def checkModelValue(self, slot, value):
        BaseKind.checkModelValue(self, slot, value)
        if value and self.values and value not in self.getValues(slot):
            raise faults.BadSlotValue(slot, value)

    def getGroupedValues(self, slot):
        return None

    def getGroupLabels(self, slot):
        return None

    def getLabels(self, slot):
        if self.labels:
            return self.labels
        labels = {}
        values = self.getValues(slot)
        if values is not None:
            for value in values:
                labels[str(value)] = str(value)
        return labels

    def getOrderedLayoutSlotNames(self, parentSlot = None):
        slotNames = BaseKind.getOrderedLayoutSlotNames(
               self, parentSlot = parentSlot)
        slotNames += ['values', 'labels', 'titles']
        return slotNames

    def getSortedValues(self, slot):
        values = self.getValues(slot)
        if values is None:
            return None
        values = values[:]
        sortedValues = []
        if None in values:
            sortedValues.append(None)
            values.remove(None)
        if '__all__' in values:
            sortedValues.append('__all__')
            values.remove('__all__')
        labels = self.getLabels(slot)
        values.sort(lambda x, y: locale.strcoll(_(labels[x]), _(labels[y])))
        sortedValues += values
        return sortedValues

    def getValues(self, slot):
        groupedValuesGetterName = self.groupedValuesGetterName
        if groupedValuesGetterName is not None:
            currentSlot = slot
            while currentSlot is not None:
                container = currentSlot.getContainer()
                try:
                    groupedValuesGetter = getattr(container,
                                                  groupedValuesGetterName)
                except AttributeError:
                    currentSlot = currentSlot.parent
                    continue
                groupedValues = groupedValuesGetter(slot)
                assert type(groupedValues) is type({})
                values = []
                for group in groupedValues.keys():
                    assert type(groupedValues[group]) is type([])
                    values += groupedValues[group]
                return values
            return []
        if self.groupedValues:
            result = []
            assert type(self.groupedValues) is type({})
            for key in self.groupedValues.keys():
                assert type(self.groupedValues[key]) is type([])
                for value in self.groupedValues[key]:
                    result.append(value)
            return result
        if self.values:
            return self.values
        else:
            return []
register(Choice)


class Data(BaseKind):
    defaultValue_kindName = 'Data'
    pythonStorageType = types.StringType
    thingPublicCategory = None # N_('Data')

    def convertValueFromOtherType(self, value):
        if type(value) is types.InstanceType and \
                value.getThingName() == 'Upload':
            return value.data

    def exportValueToXmlRpc(self, slot, value):
        if value:
            value = base64.encodestring(value)
        return value

    def importValueFromXmlRpc(self, slot, value):
        if value:
            value = base64.decodestring(value)
        return value
register(Data)


class Time(BaseKind):
    defaultValue_kindName = 'Time'

    formatString = '%Y-%m-%d %H:%M:%S'
    formatString_kind_label = N_('Date & Time Format')
    formatString_kindName = 'String'

    formatStringDay = '%Y-%m-%d'
    formatStringDay_kind_label = N_('Date Format')
    formatStringDay_kindName = 'String'

    pythonStorageType = types.FloatType # seconds since epoch

    textMaxLength = 19

    thingPublicCategory = N_('Date & Time')

    thingPublicName = N_('Date & Time')
    
    widget_size = 19
    widgetName = 'Time'

    def convertValueFromOtherType(self, value):
        if type(value) is types.IntType:
            return float(value)
        if type(value) is types.StringType:
            if value == '':
                return None

            formatDate = ( self.formatStringDay, '%Y-%m-%d', '%y-%m-%d', 
                           '%d/%m/%Y', '%d/%m/%y' )
            formatTime = ( '%H:%M:%S', '%H:%M', '' )
            for format in [x + ' ' + y
                           for x in formatDate for y in formatTime]:
                try:
                    t = time.strptime(value, format)
                except ValueError:
                    continue
                break
            else:
                raise faults.BadValue()
            # Set Daylight Saving Time to -1, so that it is handled
            # correctly by mktime.
            t = tuple(list(t[0:-1]) + [-1])
            value = time.mktime(t)
            return value
        return BaseKind.convertValueFromOtherType(self, value)

    def convertValueToOtherType(self, value, otherType):
        if otherType is types.StringType:
            if not value:
                return ''
            tupleTime = time.localtime(value)
            if tuple(tupleTime[3:6]) == (0, 0, 0):
                formatString = self.formatStringDay
            else:
                formatString = self.formatString
            return time.strftime(formatString, time.localtime(value))
        return BaseKind.convertValueToOtherType(self, value, otherType)
register(Time)


class DispatcherId(BaseKind):
    containerNames = ['Any', 'String']

    defaultValue_kindName = 'DispatcherId'

    pythonStorageType = types.StringType
    thingPublicCategory = None # N_('Glasnost')

    thingPublicName = N_('Glasnost Dispatcher ID')

    widget_size = 40
    widgetName = 'InputText'

    def convertValueIds(
            self, slot, value, sourceDispatcherId, destinationDispatcherId):
        return value.replace(sourceDispatcherId, destinationDispatcherId)

    def newJail(self, slot):
        return jails.String(slot)
register(DispatcherId)


class Duration(BaseKind):
    defaultValue_kindName = 'Duration'

    pythonStorageType = types.FloatType # number of seconds
    
    requiredParts = ['day', 'hour', 'minute', 'second']
    requiredParts_kind_itemKind_valueName = 'String'
    requiredParts_kindName = 'Sequence'

    thingPublicCategory = None # N_('Date & Time')

    thingPublicName = N_('Duration')

    widgetName = 'Duration'
register(Duration)


class Email(BaseKind):
    containerNames = ['Any', 'String']
    defaultValue_kindName = 'Email'
    pythonStorageType = types.StringType
    thingPublicCategory = N_('Text')
    thingPublicName = N_('Email Address')
    widgetName = 'Email'

    def checkModelValue(self, slot, value):
        BaseKind.checkModelValue(self, slot, value)
        if value is None:
            return
        if value == '':
            if self.isRequired:
                raise faults.MissingSlotValue(slot)
            return
        if not re.match(r'^\S+@\S+$', value):
            raise faults.BadSlotValue(slot, value)

    def newJail(self, slot):
        return jails.String(slot)
register(Email)


class Fields(BaseKind):
    containerNames = ['Any']
    defaultValue_kindName = 'Fields'
    pythonStorageType = types.StringType # ~
    thingPublicCategory = None # N_('Glasnost')
    thingPublicName = N_('Fields')

    def exportValueToXmlRpc(self, slot, value):
        return base64.encodestring(marshal.dumps(value))

    def importValueFromXmlRpc(self, slot, valueImport):
        return marshal.loads(base64.decodestring(valueImport))
register(Fields)


class Path(BaseKind):
    checkFilePath = 1
    checkFilePath_kindName = 'Boolean'

    containerNames = ['Any', 'String']

    defaultValue_kindName = 'Path'

    pythonStorageType = types.StringType

    thingPublicCategory = None # N_('Text')

    thingPublicName = N_('Path')

    def checkModelValue(self, slot, value):
        BaseKind.checkModelValue(self, slot, value)
        if value is None:
            return
        if self.checkFilePath:
            from glasnost.proxy.tools import getProxyForServerRole
            systemFilesProxy = getProxyForServerRole('systemfiles')
            systemFilesProxy.checkFilePath(value)

    def newJail(self, slot):
        return jails.String(slot)
register(Path)


class FilePath(Path):
    containerNames = ['Any', 'String']

    defaultValue_kindName = 'FilePath'

    thingPublicCategory = None # N_('Text')

    thingPublicName = N_('File Path')

    def newJail(self, slot):
        return jails.String(slot)
register(FilePath)


class Fingerprint(BaseKind):
    containerNames = ['Any', 'String']
    defaultValue_kindName = 'Fingerprint'
    pythonStorageType = types.StringType
    textMaxLength = 50
    thingPublicCategory = N_('Text')
    thingPublicName = N_('OpenPGP Fingerprint')

    def checkModelValue(self, slot, value):
        BaseKind.checkModelValue(self, slot, value)
        if value is None or value == '':
            return
        value = value.replace(' ', '')
        if len(value) != 40:
            raise faults.BadSlotValue(slot, value)
        for char in value:
            if char not in string.hexdigits:
                raise faults.BadSlotValue(slot, value)

    def convertValueToOtherType(self, value, otherType):
        if otherType is types.StringType:
            if value is None:
                return ''
            value = value.replace(' ', '')
            return '%s %s %s %s %s  %s %s %s %s %s' % (
                    value[ 0: 4], value[ 4: 8], value[ 8:12], value[12:16],
                    value[16:20], value[20:24], value[24:28], value[28:32],
                    value[32:36], value[36:40])
        return BaseKind.convertValueToOtherType(self, value, otherType)

    def newJail(self, slot):
        return jails.String(slot)
register(Fingerprint)


class Float(BaseKind):
    defaultValue_kindName = 'Float'
    pythonStorageType = types.FloatType
    thingPublicCategory = N_('Numbers')
    thingPublicName = N_('Float Number')

    def convertValueFromOtherType(self, value):
        if type(value) in (types.IntType, types.StringType):
            try:
                return float(value)
            except ValueError:
                pass
        return BaseKind.convertValueFromOtherType(self, value)

    def convertValueToOtherType(self, value, otherType):
        if otherType is types.StringType:
            if value is None:
                return ''
            return str(value)
        return BaseKind.convertValueToOtherType(self, value, otherType)

    def newJail(self, slot):
        return jails.Numeric(slot)
register(Float)


class Id(BaseKind):
    containerNames = ['Any']

    defaultValue_kindName = 'Id'

    permanentIds = None

    pythonStorageType = types.StringType
    
    rememberedIds = 0
    rememberedIds_kindName = 'Boolean'

    serverRoles = None
    serverRoles_kindName = 'AcceptedRoles'

    thingPublicCategory = N_('Links')

    thingPublicName = N_('Object')

    widgetName = 'SelectId'

    def checkModelValue(self, slot, value):
        BaseKind.checkModelValue(self, slot, value)
        if value:
            try:
                commonTools.extractDispatcherId(value)
            except:
                raise faults.BadSlotValue(slot, value)

    def convertValueIds(
            self, slot, value, sourceDispatcherId, destinationDispatcherId):
        if value is None:
            return None
        return value.replace(sourceDispatcherId, destinationDispatcherId)

    def equals(self, kind):
        if not BaseKind.equals(self, kind):
            return 0
        if self.getServerRoles(None) is None:
            serverRoles = None
        else:
            serverRoles = self.getServerRoles(None)[:]
            serverRoles.sort()
        if kind.serverRoles is None:
            kindServerRoles = None
        else:
            kindServerRoles = kind.getServerRoles(None)[:]
            kindServerRoles.sort()
        return serverRoles == kindServerRoles

    def getLabels(self, slot):
        values = self.getValues(slot)
        from glasnost.proxy.tools import getObjectLabelsTranslated
        labels = getObjectLabelsTranslated(values,
                context.getVar('readLanguages'))
        return labels

    def getServerRoles(self, slot):
        return self.serverRoles

    def getGroupedValues(self, slot):
        values = self.getValues(slot)
        groupedValues = {}
        for v in values:
            if v in (self.permanentIds or []):
                role = None
            else:
                role = commonTools.extractRole(v)
            if not groupedValues.has_key(role):
                groupedValues[role] = []
            groupedValues[role].append(v)
        return groupedValues

    def getGroupLabels(self, slot):
        groupNames = self.getGroupedValues(slot).keys()
        d = {}
        for role in groupNames:
            if not role:
                d[''] = None
                continue
            common = getCommonForServerRole(role)
            d[common.getTranslatedObjectsNameCapitalized()] = role
        return d

    def getValues(self, slot):
        from glasnost.proxy.tools import getProxyForServerRole
        result = []
        result.extend(self.permanentIds or [])
        currentValue = slot.getValue()
        serverRoles = self.getServerRoles(slot) or context.getVar('knownRoles')
        for role in serverRoles:
            proxy = getProxyForServerRole(role)
            if not hasattr(proxy, 'getObjectIds'):
                continue
            try:
                objectIds = None
                if self.rememberedIds:
                    user = context.getVar('user')
                    if user and user.objectsMemory and \
                            user.objectsMemory.has_key(role) and \
                            user.objectsMemory[role]:
                        objectIds = user.objectsMemory[role]
                        if currentValue and \
                                commonTools.extractRole(currentValue) == role:
                            objectIds.append(currentValue)
                if not objectIds:
                    objectIds = proxy.getObjectIds()
            except faults.UserAccessDenied:
                continue
            if role == 'groups' and serverRoles and serverRoles != ['groups']:
                objectIds = proxy.getObjectIdsWithCriteria(
                    'not object.acceptedRoles or '\
                    '    [x for x in object.acceptedRoles if x in %r]' % (
                            serverRoles))
            result.extend(objectIds)
        return result

    def includes(self, kind):
        if BaseKind.includes(self, kind):
            return 1
        if self.getThingName() != kind.getThingName():
            return 0
        if not self.getServerRoles(None):
            return 1
        if not kind.getServerRoles(None):
            return 0
        for kindServerRole in kind.serverRoles:
            if not kindServerRole in self.getServerRoles(None):
                return 0
        return 1

    def intersects(self, kind):
        if BaseKind.intersects(self, kind):
            return 1
        if self.getThingName() != kind.getThingName():
            return 0
        if not self.getServerRoles(None) or not kind.getServerRoles(None):
            return 1
        for kindServerRole in kind.serverRoles:
            if kindServerRole in self.getServerRoles(None):
                return 1
        return 0

    def isEmptyModelValue(self, slot, value):
        return not value

    def removeValueIds(self, slot, value, rolesToKeep):
        if not commonTools.extractRole(value) in rolesToKeep:
            return None
        return value

    def repairValue(self, slot, value, toVersion):
        if toVersion == 4000:
            return repairId(value)
        return None

    def upgradeModel_0001_0028(self, slot, model):
        """Convert user ids from "people" to "identities"."""

        changed = 0
        if self.serverRoles is not None and 'identities' in self.serverRoles \
                and not 'people' in self.serverRoles \
                and commonTools.extractRole(model) == 'people':
            changed = 1
            model = '%s/%s' % (
                    commonTools.makeApplicationId(model, 'identities'),
                    commonTools.extractLocalId(model))
        return changed, model
register(Id)


class Integer(BaseKind):
    containerNames = ['Any']

    defaultValue_kindName = 'Integer'

    max = None
    max_kind_label = N_('Maximum Value')
    max_kindName = 'Integer'

    min = None
    min_kind_label = N_('Minimum Value')
    min_kindName = 'Integer'

    pythonStorageType = types.IntType

    textMaxLength = 10

    thingPublicCategory = N_('Numbers')

    thingPublicName = N_('Integer Number')

    def checkModelValue(self, slot, value):
        BaseKind.checkModelValue(self, slot, value)
        if value is None:
            return
        if self.min is not None and value < self.min:
            raise faults.ValueTooSmall(slot, value)
        if self.max is not None and value > self.max:
            raise faults.ValueTooBig(slot, value)

    def convertValueFromOtherType(self, value):
        if type(value) is types.FloatType:
            return int(value)
        if type(value) is types.StringType:
            try:
                return int(value)
            except ValueError:
                raise faults.BadValue()
        return BaseKind.convertValueFromOtherType(self, value)

    def convertValueToOtherType(self, value, otherType):
        if otherType is types.StringType:
            if value is None:
                return ''
            return str(value)
        if otherType is types.FloatType:
            return float(value)
        return BaseKind.convertValueToOtherType(self, value, otherType)


    def getOrderedLayoutSlotNames(self, parentSlot = None):
        slotNames = BaseKind.getOrderedLayoutSlotNames(
            self, parentSlot = parentSlot)
        slotNames = slotNames[:]
        i = slotNames.index('defaultValue')
        slotNames[i:i] = ['min', 'max']
        return slotNames

    def getTextMaxLength(self):
        if self.min is not None and self.max is not None:
            textMaxLength = max(len(str(self.min)), len(str(self.max)))
        else:
            textMaxLength = BaseKind.getTextMaxLength(self)
        return textMaxLength

    def newJail(self, slot):
        return jails.Numeric(slot)
register(Integer)


class IntegerChoice(Choice):
    defaultValue_kindName = 'IntegerChoice'
    pythonStorageType = types.IntType
    thingPublicCategory = None # N_('Choice')
    thingPublicName = N_('Integer Choice')
    titles_kind_keyKind_valueName = 'Integer'
    values_kind_itemKind_valueName = 'Integer'
register(IntegerChoice)


class LanguageChoice(Choice):
    balloonHelp = N_('Select the [language->language] of '\
            'this [object->objects].')

    containerNames = ['Any']

    defaultValue_kindName = 'LanguageChoice'

    label = N_('Language')

    labels = translation.languageLabels

    isRequired = 1

    thingPublicCategory = N_('Data')

    thingPublicName = N_('Language')

    values = translation.languageKeys
    values_kind_importExport = 'private'
    values_kind_stateInEditMode = 'hidden'
    values_kind_stateInViewMode = 'hidden'

    def checkModelValue(self, slot, value):
        BaseKind.checkModelValue(self, slot, value)
        return 1

    def getDefaultValue(self, slot):
        languages = context.getVar('readLanguages')
        if languages and languages[0] in self.values:
            return languages[0]
        else:
            return 'en'
    
    def getValues(self, slot):
        from glasnost.proxy.tools import getProxyForServerRole
        translationsProxy = getProxyForServerRole('translations')
        if translationsProxy:
            try:
                return translationsProxy.getPossibleLanguages()
            except faults.UnknownServerId:
                return self.values
        return self.values
register(LanguageChoice)


class Link(BaseKind):
    defaultValue_kindName = 'Link'
    pythonStorageType = types.TupleType # (name, url)
    thingPublicCategory = None # N_('Data')
    thingPublicName = N_('Link')

    def checkModelValue(self, slot, value):
        BaseKind.checkModelValue(self, slot, value)
        if value is None:
            return 
        if len(value) != 2:
            raise faults.BadSlotValue(slot, value)

    def convertValueIds(
        self, slot, value, sourceDispatcherId, destinationDispatcherId):
        name, url = value
        if url.startswith('id:'):
            url = url.replace(sourceDispatcherId, destinationDispatcherId)
        return (name, url)

    def isEmptyModelValue(self, slot, value):
        return value is None or not value[0] and not value[1]

    def removeValueIds(self, slot, value, rolesToKeep):
        name, url = value
        if url.startswith('id:') \
               and not commonTools.extractRole(url[3:]) in rolesToKeep:
            return None
        return value

    def repairValue(self, slot, value, toVersion):
        if toVersion == 4000:
            name, url = value
            if url.startswith('id:'):
                newId = repairId(url[3:])
                if newId:
                    return (name, 'id:' + newId)
        return None
register(Link)


class Mapping(BaseKind):
    containerNames = ['Any']

    defaultValue_kindName = 'Mapping'

    deleteNullValues = 0
    deleteNullValues_kindName = 'Boolean'

    keyKind = None
    keyKind_kind_isRequired = 1
    keyKind_kindName = 'Kind'

    pythonStorageType = types.DictType
    
    requiredCount = 0
    requiredCount_kindName = 'Integer'

    thingPublicCategory = None # N_('Data')

    thingPublicName = N_('Mapping')

    valueKind = None
    valueKind_kind_isRequired = 1
    valueKind_kindName = 'Kind'

    widgetName = 'Mapping'

    def buildKinds(self):
        # Should be converted to a class method, when porting Glasnost to
        # Python 2.2.
        BaseKind.buildKinds(self)

        # Build keyKind from keyKind_valueName attributes.
        if hasattr(self, 'keyKind_valueName') \
                and self.keyKind_valueName is not None:
            baseClasses = self.getC3ClassLinearization()
            keyKindOptionNames = [key
                                  for key in self.__dict__.keys()
                                  if key.startswith('keyKind_value_')]
            for baseClass in baseClasses:
                for key in baseClass.__dict__.keys():
                    if key.startswith('keyKind_value_') \
                       and not key in keyKindOptionNames:
                        keyKindOptionNames.append(key)
            keyKindOptions = {}
            for keyKindOptionName in keyKindOptionNames:
                keyKindOptions[keyKindOptionName[len('keyKind_value_'):]
                    ] = getattr(self, keyKindOptionName)
            keyKind = commonTools.newThing('kind', self.keyKind_valueName)
            keyKind.buildOptions(keyKindOptions)
            self.__class__.keyKind = keyKind

        # Build valueKind from valueKind_valueName attributes.
        if hasattr(self, 'valueKind_valueName') \
                and self.valueKind_valueName is not None:
            baseClasses = self.getC3ClassLinearization()
            valueKindOptionNames = [key
                                    for key in self.__dict__.keys()
                                    if key.startswith('valueKind_value_')]
            for baseClass in baseClasses:
                for key in baseClass.__dict__.keys():
                    if key.startswith('valueKind_value_') \
                       and not key in valueKindOptionNames:
                        valueKindOptionNames.append(key)
            valueKindOptions = {}
            for valueKindOptionName in valueKindOptionNames:
                valueKindOptions[valueKindOptionName[len('valueKind_value_'):]
                    ] = getattr(self, valueKindOptionName)
            valueKind = commonTools.newThing('kind', self.valueKind_valueName)
            valueKind.buildOptions(valueKindOptions)
            self.__class__.valueKind = valueKind

        # Build keyKind from keyKind_valueClass attributes.
        if hasattr(self, 'keyKind_valueClass') \
                and self.keyKind_valueClass is not None:
            realKindClass = commonTools.getThingClass(
                    'kind', self.keyKind_valueClass._kindName)
            class class_(self.keyKind_valueClass, realKindClass):
                pass
            kind = class_()
            kind.buildOptions({})
            self.__class__.keyKind = kind

        # Build valueKind from valueKind_valueClass attributes.
        if hasattr(self, 'valueKind_valueClass') \
                and self.valueKind_valueClass is not None:
            realKindClass = commonTools.getThingClass(
                    'kind', self.valueKind_valueClass._kindName)
            class class_(self.valueKind_valueClass, realKindClass):
                pass
            kind = class_()
            kind.buildOptions({})
            self.__class__.valueKind = kind

    def buildOptions(self, options):
        baseClasses = self.getC3ClassLinearization()
        keyKindOptionNames = [key
                              for key in self.__dict__.keys()
                              if key.startswith('keyKind_value_')]
        for baseClass in baseClasses:
            for key in baseClass.__dict__.keys():
                if key.startswith('keyKind_value_') \
                   and not key in keyKindOptionNames:
                    keyKindOptionNames.append(key)
        keyKindOptions = {}
        for keyKindOptionName in keyKindOptionNames:
            keyKindOptions[keyKindOptionName[len('keyKind_value_'):]
                ] = getattr(self, keyKindOptionName)
        keyKindOptionNames = [key
                              for key in options.keys()
                              if key.startswith('keyKind_value_')]
        for keyKindOptionName in keyKindOptionNames:
            keyKindOptions[keyKindOptionName[len('keyKind_value_'):]
                ] = options[keyKindOptionName]
            del options[keyKindOptionName]
        if options.has_key('keyKind_valueName'):
            keyKindName = options['keyKind_valueName']
            del options['keyKind_valueName']
        elif hasattr(self, 'keyKind_valueName'):
            keyKindName = self.keyKind_valueName
        else:
            keyKindName = None
        if options.has_key('keyKind_valueClass'):
            keyKindClass = options['keyKind_valueClass']
            del options['keyKind_valueClass']
        elif hasattr(self, 'keyKind_valueClass'):
            keyKindClass = self.keyKind_valueClass
        else:
            keyKindClass = None

        valueKindOptionNames = [key
                                for key in self.__dict__.keys()
                                if key.startswith('valueKind_value_')]
        for baseClass in baseClasses:
            for key in baseClass.__dict__.keys():
                if key.startswith('valueKind_value_') \
                   and not key in valueKindOptionNames:
                    valueKindOptionNames.append(key)
        valueKindOptions = {}
        for valueKindOptionName in valueKindOptionNames:
            valueKindOptions[valueKindOptionName[len('valueKind_value_'):]
                ] = getattr(self, valueKindOptionName)
        valueKindOptionNames = [key
                                for key in options.keys()
                                if key.startswith('valueKind_value_')]
        for valueKindOptionName in valueKindOptionNames:
            valueKindOptions[valueKindOptionName[len('valueKind_value_'):]
                ] = options[valueKindOptionName]
            del options[valueKindOptionName]
        if options.has_key('valueKind_valueName'):
            valueKindName = options['valueKind_valueName']
            del options['valueKind_valueName']
        elif hasattr(self, 'valueKind_valueName'):
            valueKindName = self.valueKind_valueName
        else:
            valueKindName = None
        if options.has_key('valueKind_valueClass'):
            valueKindClass = options['valueKind_valueClass']
            del options['valueKind_valueClass']
        elif hasattr(self, 'valueKind_valueClass'):
            valueKindClass = self.valueKind_valueClass
        else:
            valueKindClass = None

        BaseKind.buildOptions(self, options)

        if keyKindClass is not None:
            realKindClass = commonTools.getThingClass(
                    'kind', keyKindClass._kindName)
            class class_(keyKindClass, realKindClass):
                pass
            keyKind = class_()
            keyKind.buildOptions(keyKindOptions)
            self.keyKind = keyKind
        elif keyKindName is not None:
            keyKind = commonTools.newThing('kind', keyKindName)
            keyKind.buildOptions(keyKindOptions)
            self.keyKind = keyKind

        if valueKindClass is not None:
            realKindClass = commonTools.getThingClass(
                    'kind', valueKindClass._kindName)
            class class_(valueKindClass, realKindClass):
                pass
            valueKind = class_()
            valueKind.buildOptions(valueKindOptions)
            self.valueKind = valueKind
        elif valueKindName is not None:
            valueKind = commonTools.newThing('kind', valueKindName)
            valueKind.buildOptions(valueKindOptions)
            self.valueKind = valueKind

    def checkModelValue(self, slot, value):
        BaseKind.checkModelValue(self, slot, value)
        if self.requiredCount > 0:
            if value is None:
                raise faults.MissingSlotValue(slot)
            if len(value) < self.requiredCount:
                raise faults.BadSlotValue(slot, value)

    def convertValueIds(
        self, slot, value, sourceDispatcherId, destinationDispatcherId):
        keyIndex = 0
        for key, val in value.items():
            keySlot = self.getItemKeySlot(slot, keyIndex)
            newKey = keySlot.getKind().convertValueIds(
                keySlot, key, sourceDispatcherId, destinationDispatcherId)
            if newKey != key:
                del value[key]
                value[newKey] = val
            keyIndex += 1
        for key, val in value.items():
            valueSlot = self.getItemValueSlot(slot, key)
            newVal = valueSlot.getKind().convertValueIds(
                valueSlot, val, sourceDispatcherId, destinationDispatcherId)
            if newVal != val:
                value[key] = newVal
        return value

    def executeModel(self, slot, when, command = None):
        value = slot.getValue()
        if value is None:
            return
        keyIndex = 0
        for key, itemValue in value.items():
            keySlot = self.getItemKeySlot(slot, keyIndex)
            keySlot.getKind().executeModel(keySlot, when, command = command)
            valueSlot = self.getItemValueSlot(slot, key)
            valueSlot.getKind().executeModel(
                    valueSlot, when, command = command)
            keyIndex += 1

    def exportValueToXmlRpc(self, slot, value):
        if value is None:
            return {}
        exportedValue = {}
        keyIndex = 0
        try:
            value.items()
        except:
            raise str((self, slot, value))
        for key, itemValue in value.items():
            keySlot = self.getItemKeySlot(slot, keyIndex)
            exportedKey = keySlot.getKind().exportValueToXmlRpc(keySlot, key)
            valueSlot = self.getItemValueSlot(slot, key)
            exportedItemValue = valueSlot.getKind().exportValueToXmlRpc(
                valueSlot, itemValue)
            exportedValue[exportedKey] = exportedItemValue
            keyIndex += 1
        return exportedValue

    def getItemKeyKind(self, index):
        return self.keyKind

    def getItemKeySlot(self, slot, index):
        return slots.ItemKey(index, parent = slot)

    def getItemKeyValueKind(self, key):
        return self.valueKind

    def getItemKeyValueSlot(self, slot):
        return slots.ItemKeyValue(parent = slot)

    def getItemValueKind(self, key):
        return self.valueKind

    def getItemValueSlot(self, slot, key):
        return slots.ItemValue(key, parent = slot)

    def getModelSlotByPath(self, slot, path):
        if path.startswith('self'):
            path = path[len('self'):]
        if not path:
            return slot
        if path.startswith('.keys()[') and ']' in path:
            index = int(path[len('.keys()['):path.index(']')])
            return self.getItemKeyKind(index).getModelSlotByPath(
                self.getItemKeySlot(slot, index), path[path.index(']') + 1:])
        if path[0] != '[' or not ']' in path:
            raise Exception('Path "%s" not found' % path)
        key = eval(path[1:path.index(']')], {})
        return self.getItemValueKind(key).getModelSlotByPath(
            self.getItemValueSlot(slot, key), path[path.index(']') + 1:])

    def importValueFromXmlRpc(self, slot, importedValue):
        if importedValue is None:
            return {}
        value = {}
        slot.value = value
        keyIndex = 0
        for importedKey, importedItemValue in importedValue.items():
            keySlot = self.getItemKeySlot(slot, keyIndex)
            key = keySlot.getKind().importValueFromXmlRpc(keySlot, importedKey)
            valueSlot = self.getItemValueSlot(slot, key)
            itemValue = valueSlot.getKind().importValueFromXmlRpc(
                valueSlot, importedItemValue)
            value[key] = itemValue
            keyIndex += 1
        del slot.value
        return value

    def isEmptyModelValue(self, slot, value):
        return not value

    def newJail(self, slot):
        return jails.Mapping(slot)

    def removeValueIds(self, slot, value, rolesToKeep):
        keyIndex = 0
        for key, val in value.items():
            keySlot = self.getItemKeySlot(slot, keyIndex)
            newKey = keySlot.getKind().removeValueIds(
                keySlot, key, rolesToKeep)
            if newKey != key:
                del value[key]
            keyIndex += 1
        for key, val in value.items():
            valueSlot = self.getItemValueSlot(slot, key)
            newVal = valueSlot.getKind().removeValueIds(
                valueSlot, val, rolesToKeep)
            if newVal != val:
                del value[key]
        if not value:
            return None
        return value

    def repairValue(self, slot, value, toVersion):
        if toVersion == 4000:
            changed = 0
            keyIndex = 0
            for key, val in value.items():
                keySlot = self.getItemKeySlot(slot, keyIndex)
                newKey = keySlot.getKind().repairValue(keySlot, key, toVersion)
                if newKey:
                    changed = 1
                    del value[key]
                    value[newKey] = val
                keyIndex += 1
            for key, val in value.items():
                valueSlot = self.getItemValueSlot(slot, key)
                newVal = valueSlot.getKind().repairValue(
                    valueSlot, val, toVersion)
                if newVal:
                    changed = 1
                    value[key] = newVal
            if changed:
                return value
        return None

    def upgradeModel(self, slot, model, toVersion):
        changed, model = BaseKind.upgradeModel(self, slot, model, toVersion)
        # Upgrade each key and value.
        if model is not None:
            for keyIndex, key in zip(range(len(model.keys())), model.keys()):
                keySlot = self.getItemKeySlot(slot, keyIndex)
                keyChanged, upgradedKey = keySlot.getKind().upgradeModel(
                        keySlot, key, toVersion)
                if keyChanged:
                    changed = 1
                    model[upgradedKey] = model.pop(key)
            for key, value in model.items():
                valueSlot = self.getItemValueSlot(slot, key)
                valueChanged, value = valueSlot.getKind().upgradeModel(
                        valueSlot, value, toVersion)
                if valueChanged:
                    changed = 1
                    model[key] = value
        return changed, model
register(Mapping)


class Marks(BaseKind):
    defaultValue_kindName = 'Marks'
    pythonStorageType = types.DictType
##     thingPublicCategory = N_('Other')

    def convertValueIds(
        self, slot, value, sourceDispatcherId, destinationDispatcherId):
        for id, mark in value.items():
            newId = id.replace(sourceDispatcherId, destinationDispatcherId)
            if newId != id:
                del value[id]
                value[newId] = mark
        return value

    def exportValueToXmlRpc(self, slot, value):
        exportedValue = {}
        if value is not None:
            for objectId, mark in value.items():
                if mark is None:
                    exportedMark = -1
                else:
                    exportedMark = mark
                exportedValue[objectId] = exportedMark
        return exportedValue

    def importValueFromXmlRpc(self, slot, importedValue):
        value = {}
        slot.value = value
        for objectId, importedMark in importedValue.items():
            if importedMark == -1:
                mark = None
            else:
                mark = importedMark
            value[objectId] = mark
        if not value:
            value = None
        del slot.value
        return value

    def removeValueIds(self, slot, value, rolesToKeep):
        for id in value.keys():
            if not commonTools.extractRole(id) in rolesToKeep:
                del value[id]
        if not value:
            return None
        return value

    def repairValue(self, slot, value, toVersion):
        if toVersion == 4000:
            changed = 0
            for id, mark in value.items():
                newId = repairId(id)
                if newId:
                    changed = 1
                    del value[id]
                    value[newId] = mark
            if changed:
                return value
        return None
register(Marks)


class Memory(BaseKind):
    defaultValue_kindName = 'Memory'
    pythonStorageType = types.DictType
##     thingPublicCategory = N_('Other')

    def checkModelValue(self, slot, value):
        BaseKind.checkModelValue(self, slot, value)
        if value and type(value) is not types.DictType:
            raise faults.BadSlotValue(slot, value)

    def convertValueIds(
        self, slot, value, sourceDispatcherId, destinationDispatcherId):
        for serverId, serverMemory in value.items():
            newServerId = serverId.replace(
                sourceDispatcherId, destinationDispatcherId)
            if newServerId != serverId:
                del value[serverId]
                value[newServerId] = serverMemory
            for i in range(len(serverMemory)):
                itemId = serverMemory[i]
                newItemId = itemId.replace(
                    sourceDispatcherId, destinationDispatcherId)
                if newItemId != itemId:
                    serverMemory[i] = newItemId
        return value

    def removeValueIds(self, slot, value, rolesToKeep):
        for serverId, serverMemory in value.items():
            if not commonTools.extractRole(serverId) in rolesToKeep:
                del value[serverId]
                continue
            indexesToDelete = []
            for i in range(len(serverMemory)):
                itemId = serverMemory[i]
                if not commonTools.extractRole(itemId) in rolesToKeep:
                    indexesToDelete.append(i)
            indexesToDelete.reverse()
            for i in indexesToDelete:
                del serverMemory[i]
            if not serverMemory:
                del value[serverId]
        if not value:
            return None
        return value
register(Memory)


class PairwiseMatrix(BaseKind):
    defaultValue_kindName = 'PairwiseMatrix'
    pythonStorageType = types.DictType
##     thingPublicCategory = N_('Other')

    def checkModelValue(self, slot, value):
        BaseKind.checkModelValue(self, slot, value)
        if value and type(value) is not types.DictType:
            raise faults.BadSlotValue(slot, value)

    def convertValueIds(
        self, slot, value, sourceDispatcherId, destinationDispatcherId):
        for id1, row in value.items():
            newId1 = id1.replace(sourceDispatcherId, destinationDispatcherId)
            if newId1 != id1:
                del value[id1]
                value[newId1] = row
            for id2, score in row.items():
                newId2 = id2.replace(
                    sourceDispatcherId, destinationDispatcherId)
                if newId2 != id2:
                    del row[id2]
                    row[newId2] = score
        return value

    def removeValueIds(self, slot, value, rolesToKeep):
        for id1, row in value.items():
            if not commonTools.extractRole(id1) in rolesToKeep:
                del value[id1]
                continue
            for id2, score in row.items():
                if not commonTools.extractRole(id2) in rolesToKeep:
                    del row[id2]
            if not row:
                del value[id1]
        if not value:
            return None
        return value

    def repairValue(self, slot, value, toVersion):
        if toVersion == 4000:
            changed = 0
            for id1, row in value.items():
                newId1 = repairId(id1)
                if newId1:
                    changed = 1
                    del value[id1]
                    value[newId1] = row
                for id2, score in row.items():
                    newId2 = repairId(id2)
                    if newId2:
                        changed = 1
                        del row[id2]
                        row[newId2] = score
            if changed:
                return value
        return None
register(PairwiseMatrix)


class Password(BaseKind):
    defaultValue_kindName = 'Password'

    # enterTwice is not used anymore, but should be...
    enterTwice = 0
    enterTwice_kind_importExport = 'private'
    enterTwice_kind_label = N_('Enter Twice')
    enterTwice_kind_stateInEditMode = 'hidden'
    enterTwice_kind_stateInViewMode = 'hidden'
    enterTwice_kindName = 'Boolean'

    pythonStorageType = types.StringType

    thingPublicCategory = N_('Text')
    
    thingPublicName = N_('Password')

    widgetName = 'InputPassword'
register(Password)


class PythonIdentifier(BaseKind):
    defaultValue_kindName = 'PythonIdentifier'

    invalidValues = None
    invalidValues_kind_itemKind_valueName = 'PythonIdentifier'
    invalidValues_kind_label = N_('Invalid values')
    invalidValues_kindName = 'Sequence'

    pythonStorageType = types.StringType
    
    thingPublicCategory = None # N_('Text')

    thingPublicName = N_('Python Identifier')

    def checkModelValue(self, slot, value):
        BaseKind.checkModelValue(self, slot, value)
        if value is None:
            return
        if self.invalidValues and value in self.invalidValues:
            raise faults.BadSlotValue(slot, value)
        if re.match('[a-zA-Z_][a-zA-Z0-9_]*$', value) is None:
            raise faults.BadSlotValue(slot, value)
register(PythonIdentifier)


class Rating(BaseKind):
    defaultValue_kindName = 'Rating'
    pythonStorageType = types.DictType
##     thingPublicCategory = N_('Other')

    def checkModelValue(self, slot, value):
        BaseKind.checkModelValue(self, slot, value)
        if value and type(value) is not types.DictType:
            raise faults.BadSlotValue(slot, value)

    def convertValueIds(
        self, slot, value, sourceDispatcherId, destinationDispatcherId):
        for k in ['wins', 'losses', 'ties']:
            v = value[k]
            for i in range(len(v)):
                id = v[i]
                newId = id.replace(sourceDispatcherId, destinationDispatcherId)
                if newId != id:
                    v[i] = newId
        return value

    def removeValueIds(self, slot, value, rolesToKeep):
        for k in ['wins', 'losses', 'ties']:
            v = value[k]
            r = range(len(v))
            r.reverse()
            for i in r:
                id = v[i]
                if not commonTools.extractRole(id) in rolesToKeep:
                    del v[i]
        return value

    def repairValue(self, slot, value, toVersion):
        if toVersion == 4000:
            changed = 0
            for k in ['wins', 'losses', 'ties']:
                v = value[k]
                for i in range(len(v)):
                    id = v[i]
                    newId = repairId(id)
                    if newId:
                        changed = 1
                        v[i] = newId
            if changed:
                return value
        return None
register(Rating)


class Script(BaseKind):
    builtins = {
            'abs': abs,
            'cmp': cmp,
            'complex': complex,
            'divmod': divmod,
            'float': float,
            'hash': hash,
            'hex': hex,
            'int': int,
            'len': len,
            'long': long,
            'None': None,
            'oct': oct,
            'pow': pow,
            'repr': repr,
            'str': str,
            'unicode': unicode,
            }

    containerNames = ['Any', 'String']

    defaultValue_kindName = 'Script'

    executeOnDisplay = 0
    class executeOnDisplay_kindClass:
        _kindName = 'Boolean'
        label = N_('Execute On Display')

    executeOnSubmit = 0
    class executeOnSubmit_kindClass:
        _kindName = 'Boolean'
        label = N_('Execute On Submit')
    
    sourceCode = None
    class sourceCode_kindClass:
        _kindName = 'ScriptSourceCode'
        label = N_('Source Code')
        #tagInEditMode = 'div'
        #tagInViewMode = 'div'

    stateInEditMode = 'read-only'

    stateInViewMode = 'read-only'

    textFormat = 'text'

    thingPublicCategory = N_('Data')

    thingPublicName = N_('Script')

    widgetName = 'TextArea'

    def executeModel(self, slot, when, command = None):
        if not (when == 'onDisplay' and self.executeOnDisplay) \
                and not (when == 'onSubmit' and self.executeOnSubmit):
            return
        if not self.sourceCode:
            return
        objectSlot = slots.Root(slot.getObject())
        objectJail = objectSlot.newJail()
        globals_ = {
                '__builtins__': self.builtins,
                'command': command,
                'self': objectJail,
                }
        locals_ = {}
        # stdin = sys.stdin
        stdout = sys.stdout
        stderr = sys.stderr
        sys.stdout = sys.stderr = executionOutputIO = cStringIO.StringIO()
        try:
            exec '%s\n' % self.sourceCode in globals_, locals_
        except:
##             traceback.print_exception(sys.exc_type, sys.exc_value, None)
            traceback.print_exc()
        # sys.stdin = stdin
        sys.stdout = stdout
        sys.stderr = stderr
        slot.setValue(executionOutputIO.getvalue())

    def getOrderedLayoutSlotNames(self, parentSlot = None):
        slotNames = BaseKind.getOrderedLayoutSlotNames(
                self, parentSlot = parentSlot)
        slotNames += ['sourceCode', 'executeOnDisplay', 'executeOnSubmit']
        return slotNames

    def newJail(self, slot):
        return jails.String(slot)
register(Script)


class ScriptSourceCode(BaseKind):
    containerNames = ['Any', 'String']

    defaultValue_kindName = 'ScriptSourceCode'

    pythonStorageType = types.StringType
    
    thingPublicCategory = None # N_('Data')

    thingPublicName = N_('Script Source Code')

    widget_rows = 10
    widget_viewInTextArea = 1

    widgetName = 'TextArea'

    def checkModelValue(self, slot, value):
        BaseKind.checkModelValue(self, slot, value)
        if value and type(value) is not types.StringType:
            raise faults.BadSlotValue(slot, value)
        if value and (
                 value.find ('__') >= 0
                 or value.find ('_model') >= 0):
            raise faults.BadSlotValue(slot, value)

    def newJail(self, slot):
        return jails.String(slot)
register(ScriptSourceCode)


class Sequence(AbstractSequence):
    containerNames = ['Any']

    defaultValue_kindName = 'Sequence'

    class isRequired_kindClass(AbstractSequence.isRequired_kindClass):
        importExport = 'private'
        stateInEditMode = 'hidden'
        stateInViewMode = 'hidden'

    itemKind = None
    itemKind_kind_isRequired = 1
    itemKind_kind_label = N_('Item Type')
    itemKind_kindName = 'Kind'
    itemKind_valueName = 'String'

    pythonStorageType = types.ListType
    
    thingPublicCategory = N_('Data')

    thingPublicName = N_('Sequence')

    def buildKinds(self):
        # Should be converted to a class method, when porting Glasnost to
        # Python 2.2.
        AbstractSequence.buildKinds(self)

        # Build itemKind from itemKind_valueName attributes.
        if hasattr(self, 'itemKind_valueName') \
                and self.itemKind_valueName is not None:
            baseClasses = self.getC3ClassLinearization()
            itemKindOptionNames = [key
                                   for key in self.__dict__.keys()
                                   if key.startswith('itemKind_value_')]
            for baseClass in baseClasses:
                for key in baseClass.__dict__.keys():
                    if key.startswith('itemKind_value_') \
                       and not key in itemKindOptionNames:
                        itemKindOptionNames.append(key)
            itemKindOptions = {}
            for itemKindOptionName in itemKindOptionNames:
                itemKindOptions[itemKindOptionName[len('itemKind_value_'):]
                    ] = getattr(self, itemKindOptionName)
            itemKind = commonTools.newThing('kind', self.itemKind_valueName)
            itemKind.buildOptions(itemKindOptions)
            self.__class__.itemKind = itemKind

        # Build itemKind from itemKind_valueClass attributes.
        if hasattr(self, 'itemKind_valueClass') \
                and self.itemKind_valueClass is not None:
            realKindClass = commonTools.getThingClass(
                    'kind', self.itemKind_valueClass._kindName)
            class class_(self.itemKind_valueClass, realKindClass):
                pass
            kind = class_()
            kind.buildOptions({})
            self.__class__.itemKind = kind

    def buildOptions(self, options):
        baseClasses = self.getC3ClassLinearization()
        itemKindOptionNames = [key
                               for key in self.__dict__.keys()
                               if key.startswith('itemKind_value_')]
        for baseClass in baseClasses:
            for key in baseClass.__dict__.keys():
                if key.startswith('itemKind_value_') \
                   and not key in itemKindOptionNames:
                    itemKindOptionNames.append(key)
        itemKindOptions = {}
        for itemKindOptionName in itemKindOptionNames:
            itemKindOptions[itemKindOptionName[len('itemKind_value_'):]
                ] = getattr(self, itemKindOptionName)
        itemKindOptionNames = [key
                               for key in options.keys()
                               if key.startswith('itemKind_value_')]
        for itemKindOptionName in itemKindOptionNames:
            itemKindOptions[itemKindOptionName[len('itemKind_value_'):]
                ] = options[itemKindOptionName]
            del options[itemKindOptionName]

        if options.has_key('itemKind_valueName'):
            itemKindName = options['itemKind_valueName']
            del options['itemKind_valueName']
        else:
            itemKindName = self.itemKind_valueName

        if options.has_key('itemKind_valueClass'):
            itemKindClass = options['itemKind_valueClass']
            del options['itemKind_valueClass']
        elif hasattr(self, 'itemKind_valueClass'):
            itemKindClass = self.itemKind_valueClass
        else:
            itemKindClass = None

        AbstractSequence.buildOptions(self, options)

        if itemKindClass is not None:
            realKindClass = commonTools.getThingClass(
                    'kind', itemKindClass._kindName)
            class class_(itemKindClass, realKindClass):
                pass
            itemKind = class_()
            itemKind.buildOptions(itemKindOptions)
            self.itemKind = itemKind
        elif itemKindName is not None:
            itemKind = commonTools.newThing('kind', itemKindName)
            itemKind.buildOptions(itemKindOptions)
            self.itemKind = itemKind

    #def checkModelValue(self, slot, value):
    #    AbstractSequence.checkModelValue(self, slot, value)
    #    if len(value or []) < self.requiredCount:
    #        raise faults.MissingSlotValue(slot)

    #def getDefaultValue(self, slot):
    #    if self.defaultValue:
    #        return self.defaultValue
    #    return [self.itemKind.getDefaultValue(slot) \
    #                for i in range(self.minCount or 1)]
        
    def getItemKind(self, index):
        return self.itemKind

##     def getModelLabel(self, slot):
##         if self.label:
##             return self.label
##         return self.itemKind.getModelLabelPlural(self.getItemSlot(slot, 0))

##     def getModelLabelPlural(self, slot):
##         if self.labelPlural:
##             return self.labelPlural
##         return self.itemKind.getModelLabelPlural(self.getItemSlot(slot, 0))

    def getOrderedLayoutSlotNames(self, parentSlot = None):
        slotNames = AbstractSequence.getOrderedLayoutSlotNames(
                self, parentSlot = parentSlot)
        slotNames = slotNames[:]
        i = slotNames.index('maxCount')
        slotNames[i:i] = ['itemKind']
        return slotNames

    def includes(self, kind):
        if AbstractSequence.includes(self, kind):
            return 1
        if not isinstance(kind, Sequence):
            return 0
        if self.itemKind is None:
            return 1
        if kind.itemKind is None:
            return 0
        return self.itemKind.includes(kind.itemKind)

    def intersects(self, kind):
        if AbstractSequence.intersects(self, kind):
            return 1
        if not isinstance(kind, Sequence):
            return 0
        if self.itemKind is None or kind.itemKind is None:
            return 1
        return self.itemKind.intersects(kind.itemKind)

    def isEmptyModelValue(self, slot, value):
        return not value
register(Sequence)


class AcceptedRoles(Sequence):
    balloonHelp = N_('Select accepted Glasnost objects types. '\
            'Click on "Apply" to update the list proposed below.')

    containerNames = ['Any', 'Sequence']

    defaultValue = [ '__all__' ]
    defaultValue_kindName = 'AcceptedRoles'

    itemKind_kind_importExport = 'private'
    itemKind_kind_stateInEditMode = 'hidden'
    itemKind_kind_stateInViewMode = 'hidden'
    itemKind_value_allowAll = 1
    itemKind_value_allowNone = 1
    itemKind_valueName = 'ServerRole'

    label = N_('Accepted Types')

    minCount = 1

    thingPublicCategory = None # N_('Data')

    thingPublicName = N_('Accepted Roles')
register(AcceptedRoles)


class UsersSet(Sequence):
    containerNames = ['Any', 'Sequence']

    defaultValue_kindName = 'UsersSet'

    itemKind_kind_importExport = 'private'
    itemKind_kind_stateInEditMode = 'hidden'
    itemKind_kind_stateInViewMode = 'hidden'
    itemKind_value_permanentIds = [system.generalPublicId,
                                   system.loggedUsersGroupId]
    itemKind_value_serverRoles = ['groups', 'identities']
    itemKind_valueName = 'Id'

    label = N_('Users')

    minCount = 0

    thingPublicCategory = N_('People Set')

    thingPublicName = N_('Users')

    def getDefaultValue(self, slot):
        userId = context.getVar('userId')
        if userId:
            return [userId]
        else:
            return None
register(UsersSet)


class AuthorsSet(UsersSet):
    balloonHelp = N_('Choose the author(s) for this object.')

    containerNames = ['Any', 'Sequence']

    defaultValue_kindName = 'AuthorsSet'

    # Remove generelPublic from the menu, because the authorsSet must be
    # countable.
    # FIXME: that's useless since the user may "visit" the group and it will
    # be shown nevertheless
    itemKind_value_permanentIds = None
    itemKind_value_serverRoles = ['groups', 'people']

    label = N_('Authors')

    thingPublicName = N_('Authors')

    def getDefaultValue(self, slot):
        user = context.getVar('user')
        if user is not None and user.personId is not None:
            return [user.personId]
        else:
            return None
register(AuthorsSet)


class Properties(Sequence):
    itemKind_kind_importExport = 'private'
    itemKind_kind_stateInEditMode = 'hidden'
    itemKind_kind_stateInViewMode = 'hidden'
    itemKind_value_valueThingCategory = 'other'
    itemKind_value_valueThingName = 'Property'
    itemKind_valueName = 'Thing'

    label = N_('Properties')

    stateInViewMode = 'read-only/hidden-if-empty'

    thingPublicCategory = None # N_('Glasnost')

    thingPublicName = N_('Properties')

    widget_reorderingButtons = 0
    widgetName = 'Multi'

    def exportValueToXmlRpc(self, slot, value):
        if value is None:
            return []
        exportedValue = []
        for i in range(len(value)):
            property = value[i]
            propertySlot = self.getPropertySlot(slot, property.name)
            exportedProperty = propertySlot.getKind().exportValueToXmlRpc(
                propertySlot, property)
            exportedValue.append(exportedProperty)
        return exportedValue

    def getItemSlot(self, slot, index):
        return slots.PropertiesItem(index, parent = slot)

    def getModelSlotByPath(self, slot, path):
        if path.startswith('self'):
            path = path[len('self'):]
        if not path:
            return slot
        if path[0] != '[' or not ']' in path:
            raise Exception('Path "%s" not found' % path)
        key = eval(path[1:path.index(']')], {})
        if type(key) in (types.StringType, types.UnicodeType):
            # Key is a property name.
            # FIXME: We should raise Exception('Path "%s" not found' % path),
            # when there is no property named key.
            kind = slot.getContainer().getDirectPropertyValueKind(key)
            if kind is None:
                kind = slot.getContainer().getPropertyValueKind(key)
                kind = copy.deepcopy(kind)
            property = commonTools.newThing('other', 'Property')
            property.kind = kind
            property.name = key
            return property.getSlotByPath(
                    path[path.index(']') + 1:],
                    parentSlot = slot.getContainer().getPropertySlot(
                            key, parentSlot = slot.parent))
        else:
            # Key is an integer index.
            return self.getItemKind(key).getModelSlotByPath(
                    self.getItemSlot(slot, key), path[path.index(']') + 1:])

    def getPropertySlot(self, slot, name):
        return slots.Property(name, parent = slot)

    def importValueFromXmlRpc(self, slot, importedValue):
        if importedValue is None:
            return []
        value = []
        slot.value = value
        for i in range(len(importedValue)):
            importedProperty = importedValue[i]
            propertyName = importedProperty['name']
            propertySlot = self.getPropertySlot(slot, propertyName)
            value.append(propertySlot.getKind().importValueFromXmlRpc(
                propertySlot, importedProperty))
        del slot.value
        return value
register(Properties)


class ReadersSet(UsersSet):
    balloonHelp = N_(
        'Select one or more [readers->readers-and-editors] or groups of readers for '\
        'this [object->objects] (Readers are allowed to view objects, they have '\
        'read access to them). '\
        'Click on "[Others->buttons-others]" if your choice is not proposed in the '\
        'list. Click on "Add" to add another one. '\
        'Select "None" to remove one.')

    containerNames = ['Any', 'Sequence', 'UsersSet']

    defaultValue_kindName = 'ReadersSet'

    label = N_('Readers')

    thingPublicName = N_('Readers')
register(ReadersSet)


class ServerId(Id):
    containerNames = ['Any', 'String']

    defaultValue_kindName = 'ServerId'

    thingPublicCategory = None # N_('Glasnost')

    thingPublicName = N_('Glasnost Server ID')

##     widgetName = 'SelectServerId'
    widget_size = 40
    widgetName = 'InputText'

    def newJail(self, slot):
        return jails.String(slot)

    def removeValueIds(self, slot, value, rolesToKeep):
        return value
register(ServerId)


class ServerRole(Choice):
    allowAll = 0
    allowAll_kindName = 'Boolean'

    allowNone = 0
    allowNone_kindName = 'Boolean'

    containerNames = ['Any', 'String']

    defaultValue_kindName = 'ServerRole'

    pythonStorageType = types.StringType

    thingPublicCategory = None # N_('Glasnost')

    thingPublicName = N_('Server Role')

    def getLabels(self, slot):
        roles = self.getValues(slot)
        labels = {}
        for role in roles:
            if role in [None, '__all__']:
                continue
            common = getCommonForServerRole(role)
            if common is None:
                label = role
            else:
                label = common.objectsNameCapitalized
            labels[role] = label
        return labels

    def getValues(self, slot):
        from glasnost.proxy.DispatcherProxy import getRegisteredRoles
        values = []
        if self.allowNone:
            values.append(None)
        if self.allowAll:
            values.append('__all__')
        registeredRoles = getRegisteredRoles(context.getVar('dispatcherId'))
        for role in registeredRoles:
            common = getCommonForServerRole(role)
            if common is None:
                continue
            if not hasattr(common, 'useObjectIds') or not common.useObjectIds:
                continue
            values.append(role)
        return values

    def newJail(self, slot):
        return jails.String(slot)
register(ServerRole)


class SlotName(Choice):
    defaultValue_kindName = 'SlotName'
    pythonStorageType = types.StringType
    thingPublicCategory = None # N_('Glasnost')
    thingPublicName = N_('Slot Name')
register(SlotName)


class Source(BaseKind):
    defaultValue_kindName = 'Source'
    pythonStorageType = types.StringType
    thingPublicCategory = None # N_('Glasnost')

    def convertValueIds(
        self, slot, value, sourceDispatcherId, destinationDispatcherId):
        return value.replace(sourceDispatcherId, destinationDispatcherId)

    def removeValueIds(self, slot, value, rolesToKeep):
        if not commonTools.extractRole(value) in rolesToKeep:
            return None
        return value
register(Source)


class String(BaseKind):
    containerNames = ['Any']

    defaultValue_kindName = 'String'

    isTranslatable = 1
    class isTranslatable_kindClass(BaseKind.isTranslatable_kindClass):
        importExport = 'public'
        stateInEditMode = 'read-write'
        stateInViewMode = 'read-only'

    possibleWidgetNames = ['InputText', 'TextArea']

    pythonStorageType = types.StringType

    class textFormat_kindClass(BaseKind.textFormat_kindClass):
        importExport = 'public'
        stateInEditMode = 'read-write'
        stateInViewMode = 'read-only/hidden-if-empty'

    class textMaxLength_kindClass(BaseKind.textMaxLength_kindClass):
        importExport = 'public'
        stateInEditMode = 'read-write'
        stateInViewMode = 'read-only/hidden-if-empty'

    thingPublicCategory = N_('Text')

    thingPublicName = N_('Text')

    class useFileStorage_kindClass(BaseKind.useFileStorage_kindClass):
        importExport = 'public'
        stateInEditMode = 'read-write'
        stateInViewMode = 'read-only'

    def checkModelValue(self, slot, value):
        BaseKind.checkModelValue(self, slot, value)
        if self.isRequired and value == '':
            raise faults.MissingSlotValue(slot)
        if self.getTextFormat(slot) == 'html':
            from xml.dom.minidom import parseString
            import xml
            try:
                parseString(value)
            except xml.parsers.expat.ExpatError:
                raise faults.BadSlotValue(slot, value)

    def convertValueToOtherType(self, value, otherType):
        if otherType is types.StringType:
            return value
        return BaseKind.convertValueToOtherType(self, value, otherType)

    def getCmpFunction(self):
        return locale.strcoll

    def isEmptyModelValue(self, slot, value):
        return not value

    def newJail(self, slot):
        return jails.String(slot)
register(String)


class Structure(AbstractSequence):
    canInsertOrDelete = 0
    
    containerNames = ['Any']

    defaultValue_kindName = 'Structure'

    parameters = None
    parameters_kind_itemKind_value_valueThingCategory = 'other'
    parameters_kind_itemKind_value_valueThingName = 'Parameter'
    parameters_kind_itemKind_valueName = 'Thing'
    parameters_kindName = 'Sequence'

    thingPublicCategory = None # N_('Data')

    thingPublicName = N_('Structure')

    def getItemKind(self, index):
        if self.parameters is None or index >= len(self.parameters):
            return None
        return self.parameters[index].kind

    def isEmptyModelValue(self, slot, value):
        if value is None:
            return 1
        for i in range(len(self.parameters)):
            itemSlot = self.getItemSlot(slot, i)
            if not itemSlot.getKind().isEmptyModelValue(itemSlot, value[i]):
                return 0
        return 1
register(Structure)


class Thing(Choice):
    accessInCreateMode = None
    class accessInCreateMode_kindClass:
        _kindName = 'Choice'
        importExport = 'private'
        label = N_('Access in Creation Mode')
        labels = {
                '': N_('Same As Edition Mode'),
                'embedded': N_('Embedded'),
                'pointed': N_('Link'),
                }
        sortLabels = 0
        stateInEditMode = 'hidden'
        stateInViewMode = 'hidden'
        values = [
                None,
                'embedded',
                'pointed',
                ]

    accessInEditMode = 'embedded'
    class accessInEditMode_kindClass:
        _kindName = 'Choice'
        importExport = 'private'
        isRequired = 1
        label = N_('Access in Edition Mode')
        labels = {
                'embedded': N_('Embedded'),
                'pointed': N_('Link'),
                }
        sortLabels = 0
        stateInEditMode = 'hidden'
        stateInViewMode = 'hidden'
        values = [
                'embedded',
                'pointed',
                ]

    accessInViewMode = 'embedded'
    class accessInViewMode_kindClass:
        _kindName = 'Choice'
        importExport = 'private'
        isRequired = 1
        label = N_('Access in View Mode')
        labels = {
                'embedded': N_('Embedded'),
                'pointed': N_('Link'),
                }
        sortLabels = 0
        stateInEditMode = 'hidden'
        stateInViewMode = 'hidden'
        values = [
                'embedded',
                'pointed',
                ]

    containerNames = ['Any']

    defaultValue_kindName = 'Thing'

    possibleWidgetNames = ['Thing']

    pythonStorageType = types.InstanceType
    
    thingPublicCategory = None # N_('Data')

    thingPublicName = N_('Thing')

    valueThingCategory = None
    valueThingCategory_kind_stateInEditMode = 'hidden'
    valueThingCategory_kind_stateInViewMode = 'hidden'
    valueThingCategory_kindName = 'String'

    valueThingName = None
    valueThingName_kind_stateInEditMode = 'hidden'
    valueThingName_kind_stateInViewMode = 'hidden'
    valueThingName_kindName = 'String'

    values_kind_importExport = 'private'
    values_kind_stateInEditMode = 'hidden'
    values_kind_stateInViewMode = 'hidden'

    widgetName = 'Thing'
    class widgetName_kindClass(Choice.widgetName_kindClass):
        importExport = 'private'

    def checkModelValue(self, slot, value):
        # Pay attention: Use BaseKind instead of Choice.
        BaseKind.checkModelValue(self, slot, value)
        # TODO: check value is an instance of the appropriate class

    def convertValueIds(self, slot, value, sourceDispatcherId,
                        destinationDispatcherId):
        if value is None:
            return None
        value.convertIds(
                sourceDispatcherId, destinationDispatcherId, parentSlot = slot)
        return value

    def convertValueToOtherType(self, value, otherType):
        if otherType is types.StringType:
            return value.getThingName()
        return BaseKind.convertValueToOtherType(self, value, otherType)

    def equals(self, kind):
        if not Choice.equals(self, kind):
            return 0
        return self.valueThingCategory == kind.valueThingCategory \
               and self.valueThingName == kind.valueThingName

    def executeModel(self, slot, when, command = None):
        value = slot.getValue()
        if value is None:
            return
        value.execute(when, parentSlot = slot, command = command)

    def exportValueToXmlRpc(self, slot, value):
        if value is None:
            return None
        return value.exportToXmlRpc(parentSlot = slot)

    def getGroupedValues(self, slot):
        categories = []
        groupedValues = {}
        for thingClass in commonTools.getAllThingClasses().values():
            if thingClass.thingCategory != self.valueThingCategory:
                continue
            if thingClass.thingPublicCategory is None:
                continue
            if not thingClass.thingPublicCategory in categories:
                categories.append(thingClass.thingPublicCategory)
                groupedValues[thingClass.thingPublicCategory] = []
            groupedValues[thingClass.thingPublicCategory].append(
                    thingClass.getThingName.im_func(thingClass))
        return groupedValues
        #categories.sort(lambda x, y: locale.strcoll(_(x), _(y)))
        #labels = self.getLabels(slot)
        #for values in groupedValues.values():
        #    values.sort(lambda x, y:
        #                locale.strcoll(_(labels[x]), _(labels[y])))

    def getGroupLabels(self, slot):
        groupNames = self.getGroupedValues(slot).keys()
        groupLabels = {}
        for g in groupNames:
            groupLabels[_(g)] = g
        return groupLabels

    def getDefaultValue(self, slot):
        value = commonTools.newThing(
                self.valueThingCategory, self.valueThingName)
        return value.getDefaultValue(slot)

    def getLabels(self, slot):
        if self.labels is not None:
            return self.labels
        self.labels = {}
        for thingClass in commonTools.getAllThingClasses().values():
            if thingClass.thingCategory != self.valueThingCategory:
                continue
            value = thingClass.getThingName.im_func(thingClass)
            self.labels[value] = _(
                    thingClass.getThingPublicName.im_func(thingClass) )
        return self.labels

    def getModelSlotByPath(self, slot, path):
        value = slot.getValue()
        if value is None:
            value = commonTools.newThing(
                    self.valueThingCategory, self.valueThingName)
            slot = slot.detach(value)
        return value.getSlotByPath(path, parentSlot = slot)

    def getModelWidget(self, slot, forceEmbedding = 0):
        if forceEmbedding or self.isModelEmbedded(slot):
            return Choice.getModelWidget(
                    self, slot, forceEmbedding = forceEmbedding)
        else:
            widget = commonTools.newThing('widget', 'Select')
            widgetOptionHeader = 'widget_'
            widgetOptionHeaderLen = len(widgetOptionHeader)
            widgetOptionNames = [
                name
                for name in self.__dict__.keys()
                if name.startswith(widgetOptionHeader) \
                and not name.startswith(widgetOptionHeader + 'kind')]
            baseClasses = self.getC3ClassLinearization()
            for baseClass in baseClasses:
                for name in baseClass.__dict__.keys():
                    if name.startswith(widgetOptionHeader) \
                       and not name.startswith(widgetOptionHeader + 'kind') \
                       and name not in widgetOptionNames:
                        widgetOptionNames.append(name)
            widgetOptions = {}
            for widgetOptionName in widgetOptionNames:
                widgetOptions[widgetOptionName[widgetOptionHeaderLen:]
                              ] = getattr(self, widgetOptionName)
            widget.buildOptions(widgetOptions)
            return widget

    def getValues(self, slot):
        if self.values is not None:
            return self.values
        self.values = [
                thingClass.getThingName.im_func(thingClass)
                for thingClass in commonTools.getAllThingClasses().values()
                if thingClass.thingCategory == self.valueThingCategory \
                and thingClass.thingPublicCategory is not None]
        return self.values

    def importValueFromXmlRpc(self, slot, importedValue):
        if importedValue is None:
            return None
        value = commonTools.newThing(
                importedValue['__thingCategory__'],
                importedValue['__thingName__'])
        slot.value = value
        value.importFromXmlRpc(importedValue, parentSlot = slot)
        del slot.value
        return value

    def includes(self, kind):
        if Choice.includes(self, kind):
            return 1
        if self.getThingName() != kind.getThingName():
            return 0
        if self.valueThingCategory is None:
            return 1
        if self.valueThingCategory != kind.valueThingCategory:
            return 0
        if self.valueThingName is None:
            return 1
        return self.valueThingName == kind.valueThingName

    def intersects(self, kind):
        if Choice.intersects(self, kind):
            return 1
        if self.getThingName() != kind.getThingName():
            return 0
        if self.valueThingCategory is None or kind.valueThingCategory is None:
            return 1
        if self.valueThingCategory != kind.valueThingCategory:
            return 0
        if self.valueThingName is None or kind.valueThingName is None:
            return 1
        return self.valueThingName == kind.valueThingName

    def isEmptyModelValue(self, slot, value):
        if value is None:
            return 1
        if self.isModelEmbedded(slot):
            return value.isEmpty(parentSlot = slot)
        else:
            return 0

    def isModelEmbedded(self, slot):
        layoutMode = context.getVar('layoutMode')
        if layoutMode == 'edit':
            isCreateEditMode = context.getVar('isCreateEditMode', default = 0)
            if isCreateEditMode:
                return self.accessInCreateMode == 'embedded' \
                       or self.accessInCreateMode is None \
                       and self.accessInEditMode == 'embedded'
            else:
                return self.accessInEditMode == 'embedded'
        elif layoutMode == 'use':
            return 1
        else:
            return self.accessInViewMode == 'embedded'

    def newJail(self, slot):
        if self.valueThingCategory == 'object':
            return jails.Object(slot)
        else:
            return jails.Thing(slot)

    def removeValueIds(self, slot, value, rolesToKeep):
        if value is None:
            return None
        value.removeIds(rolesToKeep, parentSlot = slot)
        return value

    def repairValue(self, slot, value, toVersion):
        if toVersion == 4000:
            changed = value.repair(toVersion, parentSlot = slot)
            if changed:
                return value
        return None

    def setToDefaultValue(self, slot):
        if self.isModelEmbedded(slot):
            slot, value = self.getCreatedModelSlotAndValue(slot)
            if self.getDefaultValue(slot):
                slot.getKind().setToDefaultValue(slot)
        else:
            BaseKind.setToDefaultValue(self, slot)

    def upgradeModel(self, slot, model, toVersion):
        changed, model = BaseKind.upgradeModel(self, slot, model, toVersion)
        if model is not None:
            if model.upgrade(toVersion, parentSlot = slot):
                changed = 1
        return changed, model
register(Thing)


class Union(BaseKind):
    containerNames = ['Any']

    defaultValue_kindName = 'Union'

    isPractical = 0

    items = None
    items_kind_itemKind_valueName = 'Kind'
    items_kind_label = N_('Items')
    items_kindName = 'Sequence'

    thingPublicCategory = None # N_('Glasnost')

    thingPublicName = N_('Union')

    def equals(self, kind):
        if not BaseKind.equals(self, kind):
            return 0
        if not self.items or not kind.items:
            return 0
        if len(self.items) != len(kind.items):
            return 0
        for item in self.items:
            for kindItem in kind.items:
                if item.equals(kindItem):
                    break
            else:
                return 0
        return 1

    def includes(self, kind):
        if BaseKind.includes(self, kind):
            return 1
        if self.items is not None:
            for item in self.items:
                if item.includes(kind):
                    return 1
        return 0

    def intersects(self, kind):
        if BaseKind.intersects(self, kind):
            return 1
        if self.getThingName() != kind.getThingName():
            if not self.items or not kind.items:
                return 0
            for item in self.items:
                for kindItem in kind.items:
                    if item.intersects(kindItem):
                        return 1
            return 0
        if self.items is not None:
            for item in self.items:
                if item.intersects(kind):
                    return 1
        return 0
register(Union)


class CreationTime(Time):
    defaultValue_kindName = 'CreationTime'

    importExport = 'from-server-only'

    isAutomaticallyModified = 1

    class isRequired_kindClass(Time.isRequired_kindClass):
        importExport = 'private'
        stateInEditMode = 'hidden'
        stateInViewMode = 'hidden'

    label = N_('Creation Time')

    stateInEditMode = 'read-only'

    thingPublicName = N_('Creation Time')

    def accepts(self, kind):
        if isinstance(kind, Time):
            return 1
        return Time.accepts(self, kind)

    def setAutomaticalValue(self, slot, init = 0):
        if init:
            slot.setValue(time.time())
register(CreationTime)


class ModificationTime(Time):
    defaultValue_kindName = 'ModificationTime'

    importExport = 'from-server-only'

    isAutomaticallyModified = 1

    class isRequired_kindClass(Time.isRequired_kindClass):
        importExport = 'private'
        stateInEditMode = 'hidden'
        stateInViewMode = 'hidden'

    label = N_('Modification Time')

    stateInEditMode = 'read-only'

    thingPublicName = N_('Modification Time')

    def setAutomaticalValue(self, slot, init = 0):
        slot.setValue(time.time())
register(ModificationTime)


class Token(BaseKind):
    containerNames = ['Any', 'String']
    defaultValue_kindName = 'Token'
    pythonStorageType = types.StringType
    thingPublicCategory = None # N_('Text')
    thingPublicName = N_('Token')

    def convertValueIds(
            self, slot, value, sourceDispatcherId, destinationDispatcherId):
        if value is None:
            return None
        return value.replace(sourceDispatcherId, destinationDispatcherId)

    def newJail(self, slot):
        return jails.String(slot)
register(Token)


class ApplicationToken(Token):
    containerNames = ['Any', 'String', 'Token']
    defaultValue_kindName = 'ApplicationToken'
    thingPublicName = N_('Application Token')
register(ApplicationToken)


class UserToken(Token):
    containerNames = ['Any', 'String', 'Token']
    defaultValue_kindName = 'UserToken'
    thingPublicName = N_('User Token')
register(UserToken)


class TranslationToAdd(BaseKind):
    defaultValue_kindName = 'TranslationToAdd'

    thingPublicCategory = None
register(TranslationToAdd)


class TranslatorsSets(BaseKind):
    defaultValue_kindName = 'TranslatorsSets'
    pythonStorageType = types.DictType
    thingPublicCategory = None # N_('Glasnost')

    def convertValueIds(
        self, slot, value, sourceDispatcherId, destinationDispatcherId):
        for translationName, translatorsSet in value.items():
            if translatorsSet:
                for i in range(len(translatorsSet)):
                    id = translatorsSet[i]
                    newId = id.replace(
                        sourceDispatcherId, destinationDispatcherId)
                    if newId != id:
                        translatorsSet[i] = newId
        return value

    def removeValueIds(self, slot, value, rolesToKeep):
        for translationName, translatorsSet in value.items():
            if translatorsSet:
                r = range(len(translatorsSet))
                r.reverse()
                for i in r:
                    id = translatorsSet[i]
                    if not commonTools.extractRole(id) in rolesToKeep:
                        del translatorsSet[i]
                if not translatorsSet:
                    del value[translationName]
        if not value:
            return None
        return value

    def repairValue(self, slot, value, toVersion):
        if toVersion == 4000:
            changed = 0
            for translationName, translatorsSet in value.items():
                if translatorsSet:
                    for i in range(len(translatorsSet)):
                        id = translatorsSet[i]
                        newId = repairId(id)
                        if newId:
                            changed = 1
                            translatorsSet[i] = newId
            if changed:
                return value
        return None

    def upgradeModel_0001_0028(self, slot, model):
        """Convert user ids from "people" to "identities"."""
        changed = 0
        model = slot.getValue()
        if model:
            changed = 1
            for key in model.keys():
                model[key] = [x.replace('/people', '/identities') \
                                for x in model[key]]
        return changed, model

register(TranslatorsSets)


class WritersSet(UsersSet):
    balloonHelp = N_(
        'Select one or more [editors->readers-and-editors] or groups of editors '\
        'for this [object->objects] (Editors can modify or delete objects). '\
        'Click on "[Others->buttons-others]" if your choice is not proposed in the '\
        'list. Click on "Add" to add another one. '\
        'Select "None" to remove one.')

    containerNames = ['Any', 'Sequence', 'UsersSet']

    defaultValue_kindName = 'WritersSet'

    label = N_('Editors')

    thingPublicName = N_('Editors')
register(WritersSet)


class XChoice(Choice):
    defaultValue_kindName = 'XChoice'
    pythonStorageType = types.StringType
    thingPublicCategory = None # N_('Choice')
    thingPublicName = N_('Extended Choice')
    widgetName = 'XSelect'
register(XChoice)


class Command(BaseKind):
    action = None
    class action_kindClass:
        _kindName = 'PythonIdentifier'
        label = N_('Action')

    defaultValue_kindName = 'Command'

    nextModeName = None
    class nextModeName_kindClass:
        _kindName = 'PythonIdentifier'
        label = N_('Next Mode')

    nextObjectId = None
    class nextObjectId_kindClass:
        _kindName = 'Id'
        label = N_('Next Object')

    possibleWidgetNames = ['PushButton']

    thingPublicCategory = N_('Links')

    thingPublicName = N_('Push Button')

    widgetName = 'PushButton'

    def getOrderedLayoutSlotNames(self, parentSlot = None):
        slotNames = BaseKind.getOrderedLayoutSlotNames(
                self, parentSlot = parentSlot)
        slotNames += ['action', 'nextObjectId', 'nextModeName']
        return slotNames
register(Command)


class Fault(Thing):
    defaultValue_kindName = 'Fault'
    thingPublicCategory = None # N_('Glasnost')
    thingPublicName = N_('Fault')
    valueThingCategory = 'other'
    valueThingName = 'Fault'
register(Fault)


## class InputConnector(Thing):
##     defaultInput_kindName = 'InputConnector'

##     thingPublicCategory = None # N_('Glasnost')

##     thingPublicName = N_('Input Connector')

##     valueThingCategory = 'other'

##     valueThingName = 'InputConnector'
## register(InputConnector)


class Kind(Thing):
    accessInEditMode = 'pointed'
    accessInViewMode = 'pointed'
    defaultValue_kindName = 'Kind'
    thingPublicCategory = None # N_('Glasnost')
    thingPublicName = N_('Kind')
    valueThingCategory = 'kind'
    valueThingName = 'BaseKind'

    def convertValueFromOtherType(self, value):
        if type(value) is types.StringType and value:
            return commonTools.newThing('kind', value)
        return BaseKind.convertValueFromOtherType(self, value)
register(Kind)


class KindName(Choice):
    defaultValue_kindName = 'KindName'
    pythonStorageType = types.StringType
    thingPublicCategory = None # N_('Glasnost')
    thingPublicName = N_('Kind Name')

##     def __init__(self, **attributes):
##         Choice.__init__(self, **attributes)
##         self.values = [
##                 kindClass.getThingName.im_func(kindClass)
##                 for kindClass in commonTools.getAllThingClasses().values()
##                 if kindClass.thingCategory == 'kind']

    def getGroupedValues(self, slot):
        categories = []
        groupedValues = {}
        for kindClass in commonTools.getAllThingClasses().values():
            if kindClass.thingCategory != 'kind':
                continue
            if kindClass.thingPublicCategory is None:
                continue
            if not kindClass.thingPublicCategory in categories:
                categories.append(kindClass.thingPublicCategory)
                groupedValues[kindClass.thingPublicCategory] = []
            groupedValues[kindClass.thingPublicCategory].append(
                kindClass.getThingName.im_func(kindClass))
        for values in groupedValues.values():
            values.sort(lambda x, y:
                        locale.strcoll(_(labels[x]), _(labels[y])))
        return groupedValues

    def getGroupLabels(self, slot):
        categories = self.getGroupedValues(slot).keys()
        groupLabels = {}
        for cat in categories:
            groupLabels[_(cat)] = cat
        return categories

    def getValues(self, slot):
        return [
                kindClass.getThingName.im_func(kindClass)
                for kindClass in commonTools.getAllThingClasses().values()
                if kindClass.thingCategory == 'kind' \
                and kindClass.thingPublicCategory is not None]
register(KindName)


class Mode(Thing):
    accessInEditMode = 'pointed'
    accessInViewMode = 'pointed'
    defaultValue_kindName = 'Mode'
    isRequired = 1 # Important for submitField2.
    thingPublicCategory = None # N_('Glasnost')
    thingPublicName = N_('Mode')
    valueThingCategory = 'mode'
    valueThingName = 'ModeAbstract'

    def convertValueFromOtherType(self, value):
        if not value:
            return None
        if type(value) is types.StringType:
            return commonTools.newThing('mode', value)
        return BaseKind.convertValueFromOtherType(self, value)

    def convertValueToOtherType(self, value, otherType):
        if otherType is types.StringType and value:
            return value.getThingName()
        return BaseKind.convertValueToOtherType(self, value, otherType)

    def getGroupedValues(self, slot):
        return None
register(Mode)


## class OutputConnector(Thing):
##     defaultOutput_kindName = 'OutputConnector'

##     thingPublicCategory = None # N_('Glasnost')

##     thingPublicName = N_('Output Connector')

##     valueThingCategory = 'other'

##     valueThingName = 'OutputConnector'
## register(OutputConnector)


class Upload(Thing):
    defaultValue_kindName = 'Upload'
    thingPublicCategory = N_('Data')
    thingPublicName = N_('File')
    valueThingCategory = 'other'
    valueThingName = 'Upload'

    widgetName = 'UploadFile'
register(Upload)


class ValueHolder(Thing):
    defaultValue_kindName = 'ValueHolder'
    thingPublicCategory = None # N_('Glasnost')
    thingPublicName = N_('Value Holder')
    valueThingCategory = 'other'
    valueThingName = 'ValueHolder'
register(ValueHolder)


class Widget(Thing):
    accessInEditMode = 'pointed'
    accessInViewMode = 'pointed'
    defaultValue_kindName = 'Widget'
    thingPublicCategory = None # N_('Glasnost')
    thingPublicName = N_('Widget')
    valueThingCategory = 'widget'
    valueThingName = 'BaseWidget'

    def convertValueFromOtherType(self, value):
        if type(value) is types.StringType and value:
            return commonTools.newThing('widget', value)
        return BaseKind.convertValueFromOtherType(self, value)
register(Widget)


class WidgetName(Choice):
    defaultValue_kindName = 'WidgetName'
    thingPublicCategory = None # N_('Glasnost')
    thingPublicName = N_('Widget Name')

    def getValues(self, slot):
        values = Choice.getValues(self, slot)
        if values:
            return values
        else:
            return ['InputText']
register(WidgetName)

