# -*- 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 Web Widgets"""

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


import difflib
import locale
import re
import time
import types
import urllib

import glasnost.common.context as context
import glasnost.common.faults as faults
import glasnost.common.parsers as parsers
import glasnost.common.tools_new as commonTools
import glasnost.common.xhtmlgenerator as X

import glasnost.proxy.widgets as proxyWidgets

import kinds
import properties
import things

import calendaring
from tools import *
import translation


register = things.register
helpTextRegEx = re.compile(r'\[(\S+)->(\S+)\]')


class WidgetMixin(things.ThingMixin):
    def fillModelFieldChildrenLayout(self, slot, fields, layout, **keywords):
        label = self.getHtmlFieldLabel(slot)
        layout += label
        balloonHelp = slot.getKind().balloonHelp
        if balloonHelp and not self.isReadOnly(slot):
            helpMode = context.getVar('helpMode')
            if helpMode == 'none':
                regEx = r'\1'
            elif helpMode == 'local':
                regEx = r'<a href="/help/\2">\1</a>'
            elif helpMode == 'internet':
                regEx = r'<a href="http://glasnost.entrouvert.org/help/\2">\1</a>'
            layout += X.div(_class = 'formHelp')(X.asIs(
                    helpTextRegEx.sub(regEx, _(balloonHelp))))
        cell = self.getHtmlFieldValue(slot, fields, **keywords)
        if cell is not None:
            if isinstance(cell, X.array):
                tooltipCell = [x for x in cell.children if x][0]
            else:
                tooltipCell = cell
            if self.colSpan:
                cell.setAttribute('class', ' '.join(
                        [cell.getAttribute('class'), 'fullwidth']))

        if slot.getObject().getError(slot.getPath()) and \
                    not context.getVar('hideErrors'):
            layout.setAttribute('class', ' '.join(
                    [layout.getAttribute('class'), 'error']))
            layout += self.getModelErrorLayout(slot, fields)
        layout += cell

    def getHtmlColumnLabel(self, slot):
        label = self.getModelLabel(slot)
        return X.th(scope = 'col')(_(label))

    def getHtmlColumnValue(self, slot, fields, **keywords):
        tdAttributes = {}
        return X.td(**tdAttributes)(
            self.getHtmlValue(slot, fields, **keywords))

    def getHtmlCompactValue(self, slot, fields, **keywords):
        # We have to enclose the field value in a tag with class 'cell' but we
        # want a nice markup so we can't simply <div class="cell"> v </div> and
        # go away with this.
        return X.enclose(self.getHtmlValue(slot, fields, **keywords),
                         _class = 'compact-value')

    def getHtmlFieldLabel(self, slot):
        label = self.getModelLabel(slot)

        # Disabled since <label> caused some problems (with Mozilla 1.0?)
        # labelAttributes = {'for': slot.getFieldName()}
        # 
        # Seen on css-discuss:
        # > This will, however cause a problem with Netscape 6 (7 is fine),
        # > wherein the floated labels will not be visible.  To fix that,
        # > unfortunately you have to put the labels inside spans and float the
        # > spans instead.
        #
        # if self.isInForm():
        #     if slot.getKind().isRequired and self.isInForm() \
        #            and not self.isReadOnly(slot):
        #         labelAttributes['class'] = 'required'
        #     return X.label(**labelAttributes)(_(label), X.asIs(_(':') + ' '))

        label = X.span(_class = 'label')(
                X.label(_for = slot.getFieldName())(
                        _(label), X.asIs(_(':') + ' ')) )

        if slot.getKind().isRequired and self.isInForm() \
               and not self.isReadOnly(slot):
            return X.array(label,
                            X.span(_class = 'fieldRequired',
                                   title = _('Required'))(_('(Required)')))
        return label

    def getHtmlFieldValue(self, slot, fields, **keywords):
        # We have to enclose the field value in a tag with class 'cell' but we
        # want a nice markup so we can't simply <div class="cell"> v </div> and
        # go away with this.
        return X.enclose(self.getHtmlValue(slot, fields, **keywords),
                         _class = 'cell')

    def getHtmlValue(self, slot, fields, **keywords):
        # FIXME: To rename to getModelFieldValueChildrenLayout?
        if not self.isInForm():
            return self.getHtmlViewValue(slot, fields, **keywords)
        elif self.isReadOnly(slot):
            return self.getHtmlReadOnlyValue(slot, fields, **keywords)
        else:
            return self.getHtmlFormValue(slot, fields, **keywords)

    def getHtmlViewDiffValue(self, oldSlot, newSlot, **keywords):
        return X.table(_class = 'diff')(
                X.td(self.getHtmlViewValue(oldSlot, None, **keywords)),
                X.td(self.getHtmlViewValue(newSlot, None, **keywords)) )
    
    def getHtmlViewValue(self, slot, fields, **keywords):
        fieldValue = slot.getValue() or ''
        format = slot.getKind().getTextFormat(slot)
        if format == 'html':
            formattedText = parsers.makeHtmlFromHtml(fieldValue, inline = 1)
        elif format == 'text':
            formattedText = parsers.makeHtmlFromPreformattedText(
                    fieldValue, inline = 1)
        elif format == 'rst':
            formattedText = parsers.makeHtmlFromReStructuredText(
                    fieldValue, inline = 1)
        elif format == 'spip':
            formattedText = parsers.makeHtmlFromSpip(fieldValue, inline = 1)
        else:
            formattedText = parsers.makeHtmlFromUnformattedText(
                    fieldValue, inline = 1)
        formattedText = replaceSpecialTags(formattedText)
        return X.asIs(formattedText)

    def getHtmlReadOnlyValue(self, slot, fields, **keywords):
        return X.array(
                self.getModelHiddenLayout(slot, fields),
                self.getHtmlViewValue(slot, fields, **keywords) )
    
    def getHtmlFormValue(self, slot, fields, **keywords):
        fieldName = slot.getFieldName()
        fieldValue = slot.getValue() or ''
        layout = X.array()
        layout += self.getModelErrorLayout(slot, fields)
        inputAttributes = {}
        layout += X.input(name = fieldName, type = 'text',
                value = fieldValue, **inputAttributes)
        return layout

    def getModelErrorLayout(self, slot, fields):
        if context.getVar('hideErrors'):
            return None
        try:
            error = slot.getObject().getError(slot.getPath())
        except (TypeError, IndexError, AttributeError):
            error = None
        if error:
            return X.div(_class = 'field-error-message')(
                    _(error.uiFaultString))
        return None

    def getModelFieldLayout(self, slot, fields, **keywords):
        kind = slot.getKind()
        tagName = kind.getModelFieldTag(slot)
        if tagName == 'div-with-label':
            layout = X.div(_class = 'field',
                           id = 'field-%s' % slot.getFieldName())
            self.fillModelFieldChildrenLayout(slot, fields, layout, **keywords)
            return layout
        else:
            tagClass = getattr(X, tagName)
            fieldName = slot.getFieldName()
            level  = 'getModelFieldLayout %s' % fieldName
            context.push(_level = level, inline = tagClass.inlineElements)
            try:
                childrenLayout = self.getHtmlValue(slot, fields, **keywords)
            finally:
                context.pull(_level = level)
            if not childrenLayout:
                return None
            return tagClass(id = 'field-%s' % fieldName)(childrenLayout)

    def getModelHiddenLayout(self, slot, fields):
        fieldName = slot.getFieldName()
        value = slot.getValue()
        if value is None:
            value = ''
        if type(value) is not types.StringType:
            value = slot.getKind().convertValueToOtherType(
                        value, types.StringType)
        return X.input(name = fieldName, type = 'hidden', value = value)

    def getModelPageBodyLayout(self, slot, fields):
        layoutMode = context.getVar('layoutMode')
        if layoutMode != 'edit':
            layout = self.getHtmlViewValue(slot, fields)
        elif self.isReadOnly(slot):
            layout = self.getHtmlReadOnlyValue(slot, fields)
        else:
            layout = self.getHtmlFormValue(slot, fields)
        return layout

    def submitEmbedded(self, slot, fields):
        kind = slot.getKind()
        parentSlot, value = kind.getCreatedModelSlotAndValue(slot)
        value.submitFields(fields, parentSlot = parentSlot)
        return value

    def submit(self, slot, fields):
        return slot.getField(fields) or None


class ExclusiveChoiceMixin(WidgetMixin):
    def getHtmlViewValue(self, slot, fields, **keywords):
        kind = slot.getKind()
        value = slot.getValue() or ''
        valueAsString = kind.getModelValueAsString(value)
        labels = self.getLabels(slot)
        if labels.has_key(valueAsString):
            return self.makeModelTitleFromValue(slot, fields, value)
        elif value == '__all__':
            return _(self.allLabel)
        else:
            return _(self.noneLabel)

    def makeModelTitleFromValue(self, slot, fields, value):
        kind = slot.getKind()
        valueAsString = kind.getModelValueAsString(value)
        layoutMode = context.getVar('layoutMode')
        if layoutMode != 'edit':
            if self.titles and self.titles.has_key(valueAsString):
                return _(self.titles[valueAsString])
            title = kind.makeModelTitleFromValue(slot, fields, value)
            if title:
                return title
        labels = self.getLabels(slot)
        if labels and labels.has_key(valueAsString):
            return _(labels[valueAsString])
        return None


class InputTextMixin(WidgetMixin):
    def getHtmlViewValue(self, slot, fields, **keywords):
        fieldValue, translationBar = \
                translation.getFieldValueAndTranslationBar(slot)
        if not fieldValue:
            fieldValue = ''

        format = slot.getKind().getTextFormat(slot)
        if format == 'html':
            formattedText = parsers.makeHtmlFromHtml(fieldValue, inline = 1)
        elif format == 'text':
            formattedText = parsers.makeHtmlFromPreformattedText(
                    fieldValue, inline = 1)
        elif format == 'rst':
            formattedText = parsers.makeHtmlFromReStructuredText(
                    fieldValue, inline = 1)
        elif format == 'spip':
            formattedText = parsers.makeHtmlFromSpip(fieldValue, inline = 1)
        else:
            formattedText = parsers.makeHtmlFromUnformattedText(
                    fieldValue, inline = 1)
        formattedText = replaceSpecialTags(formattedText)

        layout = X.asIs(formattedText)

        if translationBar:
            layout = X.array(layout, translationBar)

        return layout

    def getHtmlReadOnlyValue(self, slot, fields, **keywords):
        fieldValue = slot.getValue() or ''

        if type(fieldValue) is not types.StringType:
            fieldValue = slot.getKind().convertValueToOtherType(fieldValue,
                    types.StringType)

        format = slot.getKind().getTextFormat(slot)
        if format == 'html':
            formattedText = parsers.makeHtmlFromHtml(fieldValue, inline = 1)
        elif format == 'text':
            formattedText = parsers.makeHtmlFromPreformattedText(
                    fieldValue, inline = 1)
        elif format == 'rst':
            formattedText = parsers.makeHtmlFromReStructuredText(
                    fieldValue, inline = 1)
        elif format == 'spip':
            formattedText = parsers.makeHtmlFromSpip(fieldValue, inline = 1)
        else:
            formattedText = parsers.makeHtmlFromUnformattedText(
                    fieldValue, inline = 1)
        formattedText = replaceSpecialTags(formattedText)

        return X.array(
                self.getModelHiddenLayout(slot, fields),
                X.asIs(formattedText))

    def getHtmlFormValue(self, slot, fields, **keywords):
        fieldName = slot.getFieldName()
        fieldValue = slot.getValue()

        if type(fieldValue) is not types.StringType:
            fieldValue = slot.getKind().convertValueToOtherType(fieldValue,
                    types.StringType)

        layout = X.array()
        #layout += self.getModelErrorLayout(slot, fields)

        inputAttributes = {}
        maxLength = slot.getKind().getTextMaxLength()
        if maxLength is not None:
            inputAttributes['maxlength'] = maxLength
        if self.size is not None:
            size = self.size
        elif maxLength is not None:
            size = min(80, maxLength)
        else:
            size = None
        if size is not None:
            inputAttributes['size'] = size
        layout += X.input(name = fieldName, type = 'text',
                          value = fieldValue, **inputAttributes)
        if self.apply:
            layout += ' '
            layout += X.buttonInForm('apply', 'applyButton')
        return layout

    def submit(self, slot, fields):
        value = WidgetMixin.submit(self, slot, fields)
        if value is None:
            return None
        value = value.replace('\r\n', '\n')
        value = value.replace(
                '\x91', "'").replace('\x92', "'").replace(
                '\x93', "'").replace('\x94', "'").replace(
                '\x81', "'").replace('\x82', "'").replace(
                '&#8217;', "'").replace('&#8230;', '...').replace(
                '&#8211;', '--').replace('&#8220;', '"').replace(
                '&#180;', "'").replace('&#8221;', '"').replace(
                '&#8216;', "'")
        return value


class ThingMixin(WidgetMixin):
    def getHtmlValue(self, slot, fields, **keywords):
        kind = slot.getKind()
        parentSlot, value = kind.getCreatedModelSlotAndValue(slot)
        if not self.isInForm():
            layout = X.div(value.getFieldValueChildrenViewLayout(
                    fields, parentSlot = parentSlot))
        elif self.isReadOnly(slot):
            layout = X.array(
                    value.getHiddenLayout(fields, parentSlot = parentSlot),
                    X.div(value.getFieldValueChildrenViewLayout(
                            fields, parentSlot = parentSlot)))
        else:
            layout = X.array(
                    self.getModelErrorLayout(slot, fields),
                    X.div(value.getFieldValueChildrenEditLayout(
                            fields, parentSlot = parentSlot)))
        return layout

    def getModelPageBodyLayout(self, slot, fields):
        kind = slot.getKind()
        parentSlot, value = kind.getCreatedModelSlotAndValue(slot)
        layoutMode = context.getVar('layoutMode')
        if layoutMode == 'edit':
            layout = value.getEditLayout(fields, parentSlot = parentSlot)
        else:
            layout = value.getViewLayout(fields, parentSlot = parentSlot)
        return layout

    def submit(self, slot, fields):
        kind = slot.getKind()
        parentSlot, value = kind.getCreatedModelSlotAndValue(slot)
        value.submitFields(fields, parentSlot)
        return value


class TimeMixin(InputTextMixin):
    pass


class BaseWidget(WidgetMixin, proxyWidgets.BaseWidget):
    pass
register(BaseWidget)


class Duration(WidgetMixin, proxyWidgets.Duration):
    partLabels = {
        'day' : N_('day'),
        'hour' : N_('hour'),
        'minute' : N_('minute'),
        'second' : N_('second'),
        }

    def getHtmlViewValue(self, slot, fields, **keywords):
        secondFieldValue = slot.getFieldOption(fields, 'second', default = 0)
        minuteFieldValue = slot.getFieldOption(fields, 'minute', default = 0)
        hourFieldValue = slot.getFieldOption(fields, 'hour', default = 0)
        dayFieldValue = slot.getFieldOption(fields, 'day', default = 0)

        partsToDisplay = slot.getKind().requiredParts
        layout = X.array()
        for partName in partsToDisplay:
            partValue = locals()['%sFieldValue' % partName]
            if partValue:
                layout += X.asIs(partValue)
                layout += X.nbsp
                if partValue == 1:
                    layout += X.asIs('%s' % _(self.partLabels[partName]))
                else:
                    layout += X.asIs('%ss' % _(self.partLabels[partName]))
                layout += X.nbsp
        return layout

    def getHtmlFormValue(self, slot, fields, **keywords):
        secondFieldName = slot.getFieldOptionName('second')
        minuteFieldName = slot.getFieldOptionName('minute')
        hourFieldName = slot.getFieldOptionName('hour')
        dayFieldName = slot.getFieldOptionName('day')

        secondFieldValue = slot.getFieldOption(fields, 'second', default = 0)
        minuteFieldValue = slot.getFieldOption(fields, 'minute', default = 0)
        hourFieldValue = slot.getFieldOption(fields, 'hour', default = 0)
        dayFieldValue = slot.getFieldOption(fields, 'day', default = 0)

        partsToDisplay = slot.getKind().requiredParts

        layout = X.array()

        for partName in partsToDisplay:
            layout += X.input(maxlength = 3,
                              name = locals()['%sFieldName' % partName],
                              size = 3, type = 'text',
                              value = locals()['%sFieldValue' % partName])
            layout += X.nbsp
            layout += X.asIs('%ss' % _(self.partLabels[partName]))
            layout += X.nbsp
        return layout

    def getModelHiddenLayout(self, slot, fields):
        secondFieldName = slot.getFieldOptionName('second')
        minuteFieldName = slot.getFieldOptionName('minute')
        hourFieldName = slot.getFieldOptionName('hour')
        dayFieldName = slot.getFieldOptionName('day')

        secondFieldValue = slot.getFieldOption(fields, 'second', default = 0)
        minuteFieldValue = slot.getFieldOption(fields, 'minute', default = 0)
        hourFieldValue = slot.getFieldOption(fields, 'hour', default = 0)
        dayFieldValue = slot.getFieldOption(fields, 'day', default = 0)

        return X.array(
            X.input(name = secondFieldName, type = 'hidden',
                    value = secondFieldValue),
            X.input(name = minuteFieldName, type = 'hidden',
                    value = minuteFieldValue),
            X.input(name = hourFieldName, type = 'hidden',
                    value = hourFieldValue),
            X.input(name = dayFieldName, type = 'hidden',
                    value = dayFieldValue),
            )

    def submit(self, slot, fields):
        factors = {
                'second' : 1,
                'minute' : 60,
                'hour' : 60*60,
                'day' : 24*60*60
            }
        try:
            return reduce(lambda x,y:x+y,
                    [int(slot.getFieldOption(
                         fields, z, default = '0')) * factors[z]
                     for z in factors.keys()] )
        except: # should be tighter
            raise faults.BadValue()
register(Duration)


class Email(WidgetMixin, proxyWidgets.Email):
    def getHtmlViewValue(self, slot, fields, **keywords):
        fieldValue = slot.getValue()
        if fieldValue:
            fieldValueMailTo = fieldValue.replace('@', '%40')
            fieldValueHtml = fieldValue.replace('@', ' [at] ')
            return X.a(href = 'mailto:%s' % fieldValueMailTo)(
                            fieldValueHtml)
        else:
            return X.asIs(_('undefined'))

    def getHtmlFormValue(self, slot, fields, **keywords):
        fieldName = slot.getFieldName()
        fieldValue = slot.getValue() or ''

        layout = X.array()

        inputAttributes = {}
        maxLength = slot.getKind().getTextMaxLength()
        if maxLength is not None:
            inputAttributes['maxlength'] = maxLength
        if self.size is not None:
            size = self.size
        elif maxLength is not None:
            size = min(80, maxLength)
        else:
            size = None
        if size is not None:
            inputAttributes['size'] = size
        layout += X.input(name = fieldName, type = 'text',
                          value = fieldValue, **inputAttributes)
        if self.apply:
            layout += ' '
            layout += X.buttonInForm('apply', 'applyButton')
        return layout
register(Email)


class InputCheckBox(WidgetMixin, proxyWidgets.InputCheckBox):
    def getHtmlViewValue(self, slot, fields, **keywords):
        kind = slot.getKind()
        value = slot.getValue()
        return self.makeModelTitleFromValue(slot, fields, value)

    def getHtmlFormValue(self, slot, fields,  **keywords):
        fieldName = slot.getFieldName()
        kind = slot.getKind()
        value = slot.getValue() or 0

        layout = X.array()
        inputAttributes = {}
        if value:
            inputAttributes['checked'] = 'checked'
        trueValue = kind.getValues(slot)[1]
        layout += X.input(
                _class = 'checkbox', name = fieldName, type = 'checkbox',
                value = kind.getModelValueAsString(trueValue),
                **inputAttributes)
        layout += X.nbsp
        labels = self.getLabels(slot)
        label = labels[kind.getModelValueAsString(trueValue)]
        layout += _(label)
        if self.apply:
            layout += ' '
            layout += X.buttonInForm('apply', 'applyButton')
        return layout
    
    def makeModelTitleFromValue(self, slot, fields, value):
        kind = slot.getKind()
        valueAsString = kind.getModelValueAsString(value)
        layoutMode = context.getVar('layoutMode')
        if layoutMode != 'edit':
            if self.titles and self.titles.has_key(valueAsString):
                return _(self.titles[valueAsString])
            title = kind.makeModelTitleFromValue(slot, fields, value)
            if title:
                return title
        labels = self.getLabels(slot)
        if labels and labels.has_key(valueAsString):
            return _(labels[valueAsString])
        return None

    def submit(self, slot, fields):
        value = WidgetMixin.submit(self, slot, fields)
        if value is None:
            value = 0
        return int(value)
register(InputCheckBox)


class InputPassword(WidgetMixin, proxyWidgets.InputPassword):
    def getHtmlViewValue(self, slot, fields, **keywords):
        fieldValue = slot.getValue() or ''
        cryptedValue = '*' * len(fieldValue)
        return cryptedValue

    def getHtmlFormValue(self, slot, fields, **keywords):
        fieldName = slot.getFieldName()
        fieldValue = slot.getValue() or ''

        layout = X.array()

        inputAttributes = {}
        maxLength = slot.getKind().getTextMaxLength()
        if maxLength is not None:
            inputAttributes['maxlength'] = maxLength
        if self.size is not None:
            size = self.size
        elif maxLength is not None:
            size = min(80, maxLength)
        else:
            size = None
        if size is not None:
            inputAttributes['size'] = size
        layout += X.input(name = fieldName, type = 'password',
                          value = fieldValue, **inputAttributes)
        if self.apply:
            layout += ' '
            layout += X.buttonInForm('apply', 'applyButton')
        return layout
register(InputPassword)


class InputText(InputTextMixin, proxyWidgets.InputText):
    pass
register(InputText)


class Link(WidgetMixin, proxyWidgets.Link):
    def getHtmlViewValue(self, slot, fields, **keywords):
        nameFieldValue = slot.getFieldOption(fields, 'name', default = '')
        urlFieldValue = slot.getFieldOption(fields, 'url', default = '')

        nameFieldValue, translationBar = \
                translation.getFieldValueAndTranslationBar(slot, fields,
                        fieldValue = nameFieldValue)

        if urlFieldValue.startswith('glasnost://'):
            url = X.idUrl(urlFieldValue)
        else:
            url = urlFieldValue
        layout = X.a(href = url)(nameFieldValue)

        if translationBar:
            layout = X.array(layout, translationBar)

        return layout

    def getHtmlReadOnlyValue(self, slot, fields, **keywords):
        nameFieldValue = slot.getFieldOption(fields, 'name', default = '')
        urlFieldValue = slot.getFieldOption(fields, 'url', default = '')
        return X.array(
                self.getModelHiddenLayout(slot, fields),
                X.a(href = X.absoluteUrl(urlFieldValue))(nameFieldValue))

    def getHtmlFormValue(self, slot, fields,  **keywords):
        nameFieldName = slot.getFieldOptionName('name')
        urlFieldName = slot.getFieldOptionName('url')
        nameFieldValue = slot.getFieldOption(fields, 'name', default = '')
        urlFieldValue = slot.getFieldOption(fields, 'url', default = '')

        layout = X.array()
        layout += X.table(
            X.tr(
                X.td(_class = 'field-label')(_('Title'), X.asIs(_(':') + \
                                                                ' ')),
                X.td(X.input(
                    maxlength = 80, name = nameFieldName, size = 80,
                    type = 'text', value = nameFieldValue))),
            X.tr(
                X.td(_class = 'field-label')(_('URL'), X.asIs(_(':') + \
                                                              ' ')),
                X.td(X.input(
                    maxlength = 80, name = urlFieldName, size = 80,
                    type = 'text', value = urlFieldValue))),
            )
        return layout

    def getModelHiddenLayout(self, slot, fields):
        nameFieldName = slot.getFieldOptionName('name')
        urlFieldName = slot.getFieldOptionName('url')
        nameFieldValue = slot.getFieldOption(fields, 'name', default = '')
        urlFieldValue = slot.getFieldOption(fields, 'url', default = '')
        return X.array(
            X.input(name = nameFieldName, type = 'hidden',
                    value = nameFieldValue),
            X.input(name = urlFieldName, type = 'hidden',
                    value = urlFieldValue),
            )

    def submit(self, slot, fields):
        return (slot.getFieldOption(fields, 'name', default = ''),
                slot.getFieldOption(fields, 'url', default = '') )
register(Link)


class Mapping(WidgetMixin, proxyWidgets.Mapping):
    def getHtmlValue(self, slot, fields, **keywords):
        kind = slot.getKind()
        countName = slot.getFieldOptionName('count')
        count = kind.getItemsCount(slot, fields)
        layout = X.array()
        fieldset = X.div(_class = 'fieldset')
        layout += fieldset
        if self.isInForm():
            layout += X.input(name = countName, type = 'hidden', value = count)
        for i in range(count):
            keySlot = kind.getItemKeySlot(slot, i)
            keyWidget = keySlot.getWidget()
            if keyWidget is None:
                raise Exception('Missing key widget for %s in %s' % (
                    slot.getPath(), str(keySlot.getKind().__dict__)))
            valueSlot = kind.getItemKeyValueSlot(keySlot)
            valueWidget = kind.valueKind.getModelWidget(valueSlot)
            if valueWidget is None:
                raise Exception('Missing value widget for %s in %s' % (
                    slot.getPath(), str(valueSlot.getKind().__dict__)))
            fieldset += X.div(_class = 'field',
                              id = 'field-%s' % slot.getFieldName())(
                    keyWidget.getHtmlFieldValue(keySlot, fields),
                    valueWidget.getHtmlFieldValue(valueSlot, fields))
        if self.isInForm() and not self.isReadOnly(slot):
            if kind.canAddItem(slot, fields):
                addButtonName = slot.getFieldOptionName('addButton')
                layout += X.buttonInForm('add', addButtonName)
            if self.apply:
                layout += X.buttonInForm('apply', 'applyButton')
        return layout

    def getModelHiddenLayout(self, slot, fields):
        countName = slot.getFieldOptionName('count')
        count = slot.getKind().fieldsCountMin
        try:
            count = max(count, int(slot.getFieldOption(
                fields, 'count', default = '0')))
        except ValueError:
            pass
        layout = X.array()
        for i in range(count):
            keySlot = slot.getKind().getItemKeySlot(slot, i)
            keyWidget = keySlot.getWidget()
            if keyWidget is None:
                raise Exception('Missing key widget for %s in %s' % (
                    slot.getPath(), str(keySlot.getKind().__dict__)))
            layout += keyWidget.getModelHiddenLayout(keySlot, fields)
            valueSlot = slot.getKind().getItemKeyValueSlot(keySlot)
            valueWidget = valueSlot.getWidget()
            if valueWidget is None:
                raise Exception('Missing value widget for %s in %s' % (
                    slot.getPath(), str(valueSlot.getKind().__dict__)))
            layout += valueWidget.getModelHiddenLayout(valueSlot, fields)
        return layout
    
    def submit(self, slot, fields):
        count = slot.getKind().fieldsCountMin
        try:
            count = max(count,
                    int(slot.getFieldOption(fields, 'count', default = '0')))
        except ValueError:
            pass

        value = {}
        for i in range(count):
            keySlot = slot.getKind().getItemKeySlot(slot, i)
            try:
                keyKind = keySlot.getKind()
                keyValue = keySlot.getWidget().submit(keySlot, fields)
                if keyValue is None:
                    continue
                if type(keyValue) != keyKind.pythonStorageType:
                    keyValue = keyKind.convertValueFromOtherType(keyValue)
                if keyKind.isEmptyModelValue(keySlot, keyValue):
                    continue
                keyKind.checkModelValue(keySlot, keyValue)
            except faults.BaseFault, f:
                slot.getObject().setError(keySlot.getPath(), f)

            itemSlot = slot.getKind().getItemKeyValueSlot(keySlot)
            try:
                itemKind = itemSlot.getKind()
                itemValue = itemSlot.getWidget().submit(itemSlot, fields)
                if itemValue is None and kind.deleteNullValues:
                    continue
                if type(itemValue) != itemKind.pythonStorageType:
                    itemValue = itemKind.convertValueFromOtherType(itemValue)
                if itemKind.isEmptyModelValue(itemSlot, itemValue) and \
                            kind.deleteNullValues:
                    continue
            except faults.BaseFault, f:
                slot.getObject().setError(itemSlot.getPath(), f)
            
            value[keyValue] = itemValue

        addButtonName = slot.getFieldOptionName('addButton')
        if isButtonSelected(addButtonName, fields):
            # TODO: fill with default value
            count = count + 1
            slot.setFieldOption(fields, 'count', str(count))
            context.setVar('again', 1)
            context.setVar('hideErrors', 1)
        return value
register(Mapping)


class Multi(WidgetMixin, proxyWidgets.Multi):
    def getHtmlViewValue(self, slot, fields, **keywords):
        kind = slot.getKind()
        layout = X.ul(_class = 'multi')
        value = slot.getValue() or []
                
        for i, v in zip(range(len(value)), value):
            itemSlot = kind.getItemSlot(slot, i)
            itemWidget = itemSlot.getWidget()
            if kind.isLabelImportant(slot):
                layout += X.li(itemWidget.getModelFieldLayout(
                        itemSlot, fields))
            else:
                layout += X.li(itemWidget.getHtmlValue(itemSlot, fields))
        return layout

    def getHtmlFormValue(self, slot, fields, **keywords):
        kind = slot.getKind()
        countName = slot.getFieldOptionName('count')
        count = kind.getItemsCount(slot, fields)
        value = slot.getValue() or []
        if value:
            count = max(count, len(value))
        if count == 0:
            count = 1
        layout = X.array()

        changed = 0
        while len(value) < kind.getMinCount(slot) or 0:
            itemSlot = kind.getItemSlot(slot, 0)
            value.append(itemSlot.getKind().getDefaultValue(itemSlot))
            changed = 1
        if changed:
            slot.setValue(value)

        if self.inline:
            fieldset = X.ul(_class = 'multi inline')
        else:
            fieldset = X.ul(_class = 'multi')

        layout += X.input(name = countName, type = 'hidden', value = count)
        layout += X.div(fieldset)

        webTools.addContextualHeader('jsform')

        for i in range(count):
            itemSlot = kind.getItemSlot(slot, i)
            itemWidget = itemSlot.getWidget()
            if itemWidget is None:
                raise Exception('Missing item widget for %s in %s' % (
                    slot.getPath(), str(itemSlot.getKind().__dict__)))
            if kind.isLabelImportant(slot):
                fieldset += X.li(itemWidget.getModelFieldLayout(
                        itemSlot, fields))
            else:
                childWidget = X.array(
                    itemWidget.getHtmlValue(itemSlot, fields))
                if count > 1 and self.reorderingButtons:
                    childWidget.append(
                        X.button(type = 'button',
                            onclick = 'moveItemUp(this.parentNode)')(
                            X.asIs('&uarr;')))
                    #if i == 0:
                    #    childWidget.children[-1].setAttribute(
                    #            'disabled', 'disabled')
                    childWidget.append(
                        X.button(type = 'button',
                            onclick = 'moveItemDown(this.parentNode)')(
                            X.asIs('&darr;')))
                    #if i == count-1:
                    #    childWidget.children[-1].setAttribute(
                    #            'disabled', 'disabled')
                    
                fieldset += X.li(childWidget)
        if kind.canAddItem(slot, fields):
            addButtonName = slot.getFieldOptionName('addButton')
            layout += X.buttonInForm('add', addButtonName)
        if self.apply:
            layout += X.buttonInForm('apply', 'applyButton')
        return layout

    def getHtmlReadOnlyValue(self, slot, fields, **keywords):
        return X.array(
                self.getModelHiddenLayout(slot, fields, children = 0),
                self.getHtmlViewValue(slot, fields, **keywords) )
        
    def getModelHiddenLayout(self, slot, fields, children = 1):
        countName = slot.getFieldOptionName('count')
        kind = slot.getKind()
        count = kind.getItemsCount(slot, fields)
        layout = X.array()
        if count:
            layout += X.input(name = countName, type = 'hidden', value = count)
        if children:
            for i in range(count):
                itemSlot = slot.getKind().getItemSlot(slot, i)
                itemWidget = itemSlot.getWidget()
                if itemWidget is None:
                    raise Exception('Missing item widget for %s in %s' % (
                        slot.getPath(), str(itemSlot.getKind().__dict__)))
                layout += itemWidget.getModelHiddenLayout(itemSlot, fields)
        return layout

    def submit(self, slot, fields):
        kind = slot.getKind()
        count = kind.getMinCount(slot)
        try:
            count = max(count,
                    int(slot.getFieldOption(fields, 'count', default = '0')))
        except ValueError:
            pass

        value = []
        for i in range(count):
            itemSlot = kind.getItemSlot(slot, i)
            itemWidget = itemSlot.getWidget()
            itemKind = itemSlot.getKind()
            try:
                itemValue = itemWidget.submit(itemSlot, fields)
                if itemValue is None:
                    # what if emptyValue != None ?
                    continue
                if type(itemValue) != itemKind.pythonStorageType:
                    itemValue = itemKind.convertValueFromOtherType(itemValue)
                if itemKind.isEmptyModelValue(itemSlot, itemValue):
                    continue
                itemKind.checkModelValue(itemSlot, itemValue)
            except faults.BaseFault, f:
                itemSlot.getObject().setError(slot.getPath(), f)
                context.setVar('again', 1)
                context.setVar('error', 1)
                continue
            value.append(itemValue)

        addButtonName = slot.getFieldOptionName('addButton')
        if isButtonSelected(addButtonName, fields):
            itemSlot = kind.getItemSlot(slot, count)
            itemKind = itemSlot.getKind()
            value.append(itemKind.getDefaultValue(itemSlot))
            context.setVar('again', 1)
            context.setVar('hideErrors', 1)
        return value
register(Multi)


class MultiCheck(WidgetMixin, proxyWidgets.MultiCheck):
    def getHtmlViewValue(self, slot, fields, **keywords):
        kind = slot.getKind()
        layout = X.ul()
        value = slot.getValue() or []
        for i, v in zip(range(len(value)), value):
            itemSlot = kind.getItemSlot(slot, i)
            itemWidget = itemSlot.getWidget()
            layout += X.li(itemWidget.getHtmlValue(itemSlot, fields))
        return layout

    def getHtmlFormValue(self, slot, fields, **keywords):
        kind = slot.getKind()
        layout = X.ul()
        values = kind.itemKind.getSortedValues(slot)
        labels = kind.itemKind.getLabels(slot)
        for i in range(len(values)):
            itemSlot = kind.getItemSlot(slot, i)
            itemWidget = itemSlot.getWidget()
            fieldName = slot.getFieldOptionName(values[i])
            attrs = {}
            if values[i] in (slot.getValue() or []):
                attrs = {'checked': 'checked'}
            layout += X.li(
                    X.input(type = 'checkbox', name = fieldName, **attrs),
                    _(labels[values[i]]))
        return layout

    def submit(self, slot, fields):
        kind = slot.getKind()
        value = []
        values = kind.itemKind.getValues(slot)
        for i in range(len(values)):
            fieldName = slot.getFieldOptionName(values[i])
            itemValue = slot.getFieldOption(fields, values[i])
            if itemValue == 'on':
                value.append(values[i])
        return value
register(MultiCheck)


class Path(WidgetMixin, proxyWidgets.Path):
    def getHtmlViewValue(self, slot, fields, **keywords):
        fieldValue = slot.getValue() or ''
        return X.a(href = X.idUrl(object.id).add('path', fieldValue))(
                fieldValue)

    def getHtmlFormValue(self, slot, fields, **keywords):
        fieldName = slot.getFieldName()
        fieldValue = slot.getValue() or ''

        layout = X.array()
        layout += X.input(
                maxlength = 80, name = fieldName, size = 80,
                type = 'text', value = fieldValue)
        return layout
register(Path)


class PushButton(WidgetMixin, proxyWidgets.PushButton):
    def getHtmlViewValue(self, slot, fields, **keywords):
        label = self.getModelLabel(slot)
        kind = slot.getKind()
        # FIXME: To improve using a class Command, like CardsWeb.
        nextObjectId = kind.nextObjectId
        if nextObjectId is None:
            nextObjectId = slot.getObject().id
        url = X.idUrl(nextObjectId, action = kind.nextModeName)
        return X.buttonStandalone(_(label), url)

    def getHtmlReadOnlyValue(self, slot, fields, **keywords):
        return self.getHtmlFormValue(slot, fields, **keywords)

    def getHtmlFormValue(self, slot, fields,  **keywords):
        label = self.getModelLabel(slot)
        layout = X.array()
        buttonName = slot.getFieldName()
        layout += X.buttonInForm(_(label), buttonName)
        return layout
register(PushButton)


class RadioButtons(ExclusiveChoiceMixin, proxyWidgets.RadioButtons):
    def getHtmlFormValue(self, slot, fields, **keywords):
        fieldName = slot.getFieldName()
        kind = slot.getKind()
        try:
            value = slot.getValue() or ''
        except IndexError:
            value = None

        if type(value) is types.StringType:
            valueAsString = value
        else:
            try:
                valueAsString = kind.convertValueToOtherType(value, types.StringType)
            except: # happens with class_ instance
                valueAsString = ''

        if hasattr(kind, 'sortLabels') and kind.sortLabels:
            values = kind.getSortedValues(slot)
        else:
            values = kind.getValues(slot)
        if values is None:
            raise Exception('Kind "%s" of slot "%s" has no values' % (
                    kind, slot))
        labels = self.getLabels(slot)

        layout = X.array()

        group = X.span(_class = 'radio-buttons-group')
        itemsCount = len(values)
        if not value in values:
            if labels.has_key(valueAsString):
                noneLabel = _(labels[valueAsString])
            else:
                noneLabel = _(self.noneLabel)
            radioButton = X.span(_class = 'radio-button')
            radioButton += X.input(checked = 'checked', name = fieldName,
                                   type = 'radio', value = '')
            radioButton += X.nbsp
            radioButton += noneLabel
            group += radioButton
            itemsCount += 1
        elif not kind.isRequired and not None in values:
            noneAsString = kind.getModelValueAsString(None)
            if labels.has_key(noneAsString):
                noneLabel = _(labels[noneAsString])
            else:
                noneLabel = _(self.noneLabel)
            radioButton = X.span(_class = 'radio-button')
            radioButton += X.input(name = fieldName, type = 'radio',
                                   value = '')
            radioButton += X.nbsp
            radioButton += noneLabel
            group += radioButton
            itemsCount += 1
        if itemsCount == 1:
            # The menu has only one item. Replace it by a read-only value.
            layout += self.getHtmlReadOnlyValue(slot, fields, **keywords)
            return layout

        for itemValue in values:
            inputAttributes = {}
            itemValueAsString = kind.getModelValueAsString(itemValue)
            if itemValue == value:
                inputAttributes['checked'] = 'checked'
            if labels.has_key(itemValueAsString):
                label = _(labels[itemValueAsString])
            elif itemValue == '__all__':
                label = _(self.allLabel)
            elif itemValueAsString:
                label = _(itemValueAsString)
            else:
                label = _(self.noneLabel)
            radioButton = X.span(_class = 'radio-button')
            radioButton += X.input(
                    name = fieldName, type = 'radio',
                    value = itemValueAsString, **inputAttributes)
            radioButton += X.nbsp
            radioButton += label
            group += radioButton
        layout += group
        if self.apply:
            layout += ' '
            layout += X.buttonInForm('apply', 'applyButton')
        return layout
register(RadioButtons)


class Select(ExclusiveChoiceMixin, proxyWidgets.Select):
    def getHtmlFormValue(self, slot, fields, **keywords):
        fieldName = slot.getFieldName()
        kind = slot.getKind()
        try:
            value = slot.getValue() or ''
        except IndexError:
            value = None

        if type(value) is types.StringType:
            valueAsString = value
        else:
            try:
                valueAsString = kind.convertValueToOtherType(value, types.StringType)
            except: # happens with class_ instance
                valueAsString = ''

        if hasattr(kind, 'sortLabels') and kind.sortLabels:
            values = kind.getSortedValues(slot)
        else:
            values = kind.getValues(slot)
        if values is None:
            raise Exception('Kind "%s" of slot "%s" has no values' % (
                    kind, slot))
        
        labels = self.getLabels(slot)

        layout = X.array()

        selectAttributes = {}
        selectAttributes['name'] = fieldName
        select = X.select(**selectAttributes)
        itemsCount = len(values)
        if not valueAsString in [kind.getModelValueAsString(x) for x in values]:
            if labels.has_key(valueAsString):
                noneLabel = _(labels[valueAsString])
            else:
                noneLabel = _(self.noneLabel)
            select += X.option(selected = 'selected',
                                value = valueAsString)(noneLabel)
            itemsCount += 1
        elif not kind.isRequired and not None in values:
            noneAsString = kind.getModelValueAsString(None)
            if labels.has_key(noneAsString):
                noneLabel = _(labels[noneAsString])
            else:
                noneLabel = _(self.noneLabel)
            select += X.option(value = '')(noneLabel)
            itemsCount += 1

        if itemsCount == 1:
            # The menu has only one item. Replace it by a read-only value.
            layout += self.getHtmlReadOnlyValue(slot, fields, **keywords)
            return layout

        groupedValues = {}
        if hasattr(kind, 'getGroupedValues'):
            groupedValues = kind.getGroupedValues(slot)

        if groupedValues:
            groupLabelsDict = kind.getGroupLabels(slot)
            groupLabels = groupLabelsDict.keys()
            groupLabels.sort(lambda x,y: locale.strcoll(x, y))
            for label in groupLabels:
                role = groupLabelsDict[label]
                groupValues = groupedValues[role]
                if role is None:
                    optgroup = select
                else:
                    optgroup = X.optgroup(label = label)
                    select += optgroup
                for value in groupValues[:]:
                    if not value in labels.keys():
                        groupValues.remove(value)
                groupValues.sort(lambda x,y: locale.strcoll(
                        labels[x], labels[y]))
                for itemValue in groupValues:
                    optionAttributes = {}
                    itemValueAsString = kind.getModelValueAsString(itemValue)
                    if itemValueAsString == valueAsString:
                        optionAttributes['selected'] = 'selected'
                    if labels.has_key(itemValueAsString):
                        label = labels[itemValueAsString]
                    elif itemValue == '__all__':
                        label = _(self.allLabel)
                    elif itemValueAsString:
                        label = itemValueAsString
                    else:
                        label = _(self.noneLabel)
                    optgroup += X.option(value = itemValueAsString,
                                         **optionAttributes)(label)
        else:
            for itemValue in values:
                optionAttributes = {}
                itemValueAsString = kind.getModelValueAsString(itemValue)
                if itemValue == value:
                    optionAttributes['selected'] = 'selected'
                if labels.has_key(itemValueAsString):
                    label = _(labels[itemValueAsString])
                elif itemValue == '__all__':
                    label = _(self.allLabel)
                elif itemValueAsString:
                    label = _(itemValueAsString)
                else:
                    label = _(self.noneLabel)
                select += X.option(value = itemValueAsString,
                                   **optionAttributes)(label)
        layout += select

        if self.apply:
            layout += ' '
            layout += X.buttonInForm('apply', 'applyButton')
        return layout
register(Select)


class SelectId(Select, proxyWidgets.SelectId):
    def getHtmlViewValue(self, slot, fields, **keywords):
        fieldValue = slot.getValue()
        if fieldValue:
            if self.provideHypertextLink:
                return X.objectHypertextLabel(fieldValue)
            else:
                try:
                    return getObjectLabelTranslated(
                        fieldValue, context.getVar('readLanguages'))
                except faults.MissingItem:
                    return X.span(_class = 'deleted')(_('Missing Object (%s)') % objectId)
        else:
            return _(self.noneLabel)


    def getHtmlFormValue(self, slot, fields, **keywords):
        layout = Select.getHtmlFormValue(self, slot, fields, **keywords)
        kind = slot.getKind()
        if hasattr(kind, 'rememberedIds') and kind.rememberedIds:
            webTools.addContextualHeader('jsform')
            roles = []
            for role in (kind.serverRoles or context.getVar('knownRoles')):
                web = getWebForServerRole(role)
                if hasattr(web, 'useObjectIds') and web.useObjectIds:
                    roles.append(role)
            roles = ','.join(roles)
            url = X.url('/selectOthers').add('roles', roles)
            layout += X.input(
                    _class = 'button others', type = 'button',
                    onClick = 'selectOthers(this.parentNode, "%s")' % url,
                    value = _('Others'))
        return layout
register(SelectId)


class TextArea(WidgetMixin, proxyWidgets.TextArea):
    def getHtmlViewDiffValue(self, oldSlot, newSlot, **keywords):
        newValue = newSlot.getValue().split('\n')
        oldValue = oldSlot.getValue().split('\n')
        try:
            difflib.ndiff
        except AttributeError:
            # no ndiff function in Python 2.1
            return WidgetMixin.getHtmlViewDiffValue(
                    self, oldSlot, newSlot, **keywords)
        
        result = []
        for line in difflib.ndiff(oldValue, newValue):
            marker, line = line[0], line[2:]
            if marker == ' ':
                result.append(line)
            elif marker == '-':
                result.append('<span class="diff-old">%s</span>' % line)
            elif marker == '+':
                result.append('<span class="diff-new">%s</span>' % line)
            elif marker == '?':
                continue

        return X.pre(_class = 'diff')(X.asIs('\n'.join(result)))

    def getHtmlViewValue(self, slot, fields, **keywords):
        fieldValue, translationBar = \
                translation.getFieldValueAndTranslationBar(slot, fields)
        if not fieldValue:
            fieldValue = ''
        if self.viewInTextArea:
            textareaAttributes = self.getTextAreaAttributes()
            layout = X.textarea(readonly = 'readonly',
                                **textareaAttributes)(fieldValue)
        else:
            virtualHost = context.getVar('virtualHost')
            sectionLevel = context.getVar('sectionLevel') + 1
            format = slot.getKind().getTextFormat(slot)
            inline = context.getVar('inline', default = 0)
            if format == 'docbook':
                formattedText = parsers.makeHtmlFromDocBook(
                        fieldValue, inline = inline)
            elif format == 'html':
                formattedText = parsers.makeHtmlFromHtml(
                        fieldValue, inline = inline)
            elif format == 'text':
                formattedText = parsers.makeHtmlFromPreformattedText(
                        fieldValue, inline = inline)
            elif format == 'rst':
                formattedText = parsers.makeHtmlFromReStructuredText(
                        fieldValue, inline = inline,
                        sectionLevel = sectionLevel)
            elif format == 'spip':
                formattedText = parsers.makeHtmlFromSpip(
                        fieldValue, inline = inline,
                        sectionLevel = sectionLevel)
            else:
                formattedText = parsers.makeHtmlFromUnformattedText(
                        fieldValue, inline = inline)
            formattedText = replaceSpecialTags(formattedText)
            layout = X.asIs(formattedText)

        if translationBar:
            layout = X.array(layout, translationBar)

        return layout

    def getHtmlReadOnlyValue(self, slot, fields, **keywords):
        fieldValue = slot.getValue() or ''
        layout = X.array(self.getModelHiddenLayout(slot, fields))
        if self.viewInTextArea:
            textareaAttributes = self.getTextAreaAttributes()
            layout += X.textarea(readonly = 'readonly',
                                 **textareaAttributes)(fieldValue)
            return layout
        else:
            sectionLevel = context.getVar('sectionLevel') + 1
            format = slot.getKind().getTextFormat(slot)
            inline = context.getVar('inline', default = 0)
            if format == 'docbook':
                formattedText = parsers.makeHtmlFromDocBook(
                        fieldValue, inline = inline)
            elif format == 'html':
                formattedText = parsers.makeHtmlFromHtml(
                        fieldValue, inline = inline)
            elif format == 'text':
                formattedText = parsers.makeHtmlFromPreformattedText(
                        fieldValue, inline = inline)
            elif format == 'rst':
                formattedText = parsers.makeHtmlFromReStructuredText(
                        fieldValue, inline = inline,
                        sectionLevel = sectionLevel)
            elif format == 'spip':
                formattedText = parsers.makeHtmlFromSpip(
                        fieldValue, inline = inline,
                        sectionLevel = sectionLevel)
            else:
                formattedText = parsers.makeHtmlFromUnformattedText(
                        fieldValue, inline = inline)
            formattedText = replaceSpecialTags(formattedText)
            return X.array(
                    self.getModelHiddenLayout(slot, fields),
                    X.asIs(formattedText))
        return layout

    def getTextAreaAttributes(self):
        textareaAttributes = {}
        if self.cols:
            textareaAttributes['cols'] = self.cols
        else:
            textareaAttributes['cols'] = 80
        if self.rows:
            textareaAttributes['rows'] = self.rows
        else:
            textareaAttributes['rows'] = 10
        return textareaAttributes

    def getHtmlFormValue(self, slot, fields, **keywords):
        fieldName = slot.getFieldName()
        fieldValue = slot.getValue() or ''

        textareaAttributes = self.getTextAreaAttributes()

        layout = X.array()
        layout += X.textarea(
                name = fieldName, **textareaAttributes)(fieldValue)

        if self.preview or self.apply:
            buttonsBar = X.div(_class = 'field-value-buttons-bar')
            layout += buttonsBar
            if self.preview:
                buttonsBar += X.buttonInForm('preview', 'applyButton')
            if self.apply:
                buttonsBar += X.buttonInForm('apply', 'applyButton')

        if self.preview and fieldValue:
            format = slot.getKind().getTextFormat(slot)
            if format == 'docbook':
                formattedText = parsers.makeHtmlFromDocBook(fieldValue)
            elif format == 'html':
                if not slot.getObject().getError(slot.getPath()):
                    formattedText = parsers.makeHtmlFromHtml(fieldValue)
                else:
                    formattedText = '<strong>error in HTML</strong>'
            elif format == 'text':
                formattedText = parsers.makeHtmlFromPreformattedText(
                        fieldValue)
            elif format == 'rst':
                formattedText = parsers.makeHtmlFromReStructuredText(
                        fieldValue)
            elif format == 'spip':
                formattedText = parsers.makeHtmlFromSpip(fieldValue)
            else:
                formattedText = parsers.makeHtmlFromUnformattedText(
                        fieldValue)
            formattedText = replaceSpecialTags(formattedText)
            user = context.getVar('user')
            if user is not None and user.spellcheckEntries \
                    and slot.getObject().hasSlotName('language'):
                languageSlot = slot.getObject().getSlot('language')
                formattedText = spellcheck(formattedText,
                                           languageSlot.getValue())
            if formattedText:
                layout += X.div(_class = 'preview')(X.asIs(formattedText))

        return layout

    def submit(self, slot, fields):
        value = WidgetMixin.submit(self, slot, fields)
        if value is None:
            return None
        value = value.replace('\r\n', '\n')
        value = value.replace(
                '\x91', "'").replace('\x92', "'").replace(
                '\x93', "'").replace('\x94', "'").replace(
                '\x81', "'").replace('\x82', "'").replace(
                '&#8217;', "'").replace('&#8230;', '...').replace(
                '&#8211;', '--').replace('&#8220;', '"').replace(
                '&#180;', "'").replace('&#8221;', '"').replace(
                '&#8216;', "'")
        return value

register(TextArea)


class Thing(ThingMixin, proxyWidgets.Thing):
    pass
register(Thing)


class Time(TimeMixin, proxyWidgets.Time):
    def getHtmlFormValue(self, slot, fields, **keywords):
        fieldName = slot.getFieldName()
        fieldValue = slot.getValue()

        if type(fieldValue) is not types.StringType:
            fieldValue = slot.getKind().convertValueToOtherType(fieldValue,
                    types.StringType)

        layout = X.array()

        layout += X.input(name = fieldName, type = 'text',
                          id = fieldName, value = fieldValue)
        webTools.addContextualHeader('jscalendar')
        #
        # TODO: ifFormat should be based on kind dateFormat
        layout.append(X.img(_class = 'button date-picker',
                src = X.fileUrl('/javascript/jscalendar/img.png'),
                id = '%s_trigger' % fieldName,
                value = '...',
                onload = """Calendar.setup({inputField: "%s",
                        ifFormat: "%s", button: "%s_trigger",
                        singleClick: true })""" % (
                            fieldName, slot.getKind().formatStringDay,
                            fieldName)))
        return layout

register(Time)


class Token(WidgetMixin, proxyWidgets.Token):
    pass
register(Token)


class UploadFile(WidgetMixin, proxyWidgets.UploadFile):
    def getContentPathString(self, contentSlot):
        contentPath = contentSlot.getPath()
        return contentPath[5:].replace("']", '').replace('.', '/')

    def getHtmlViewValue(self, slot, fields, **keywords):
        value = slot.getValue()
        object = slot.getObject()

        legacyObject = 0
        if value and type(value) is type(''):
            legacyObject = 1
            value = self.forgetLegacyUploadFile(slot, value)

        if not value:
            return ''

        addedToUrl = ''
        if value.dataFileName and '.' in value.dataFileName:
            addedToUrl = '/%s' % urllib.quote(value.dataFileName)

        if isTypeOfMimeType(value.dataType, 'image'):
            uri = X.idUrl(object.id, 'thumbnail')
            if slot.parent is not None:
                uri.add('path', slot.getPath())
            uri.add('width', 256)
            uri.add('height', 256)
            image = X.img(src = uri)
            if legacyObject:
                uri = X.idUrl(object.id, 'image%s' % addedToUrl)
            else:
                uri = X.idUrl(object.id, 'image%s%s' % (
                        self.getContentPathString(slot), addedToUrl))
            return X.a(_class = 'image', href = uri)(image)
        elif value:
            if legacyObject:
                uri = X.idUrl(object.id, 'download%s' % addedToUrl)
            else:
                uri = X.idUrl(object.id, 'download%s%s' % (
                        self.getContentPathString(slot), addedToUrl))
            return X.a(href = uri)(_('download'))
        return ''

    def getHtmlReadOnlyValue(self, slot, fields, **keywords):
        return X.array(
            self.getModelHiddenLayout(slot, fields),
            self.getRepresentation(slot, fields, **keywords) )

    def forgetLegacyUploadFile(self, slot, value):
        v = commonTools.newThing('other', 'Upload')
        fileNameSlot = slot.getContainer().getSlot(
                slot.name + 'FileName', parentSlot = slot.parent)
        v.dataFileName = fileNameSlot.getValue()
        v.data = value
        typeSlot = slot.getContainer().getSlot(
                slot.name + 'Type', parentSlot = slot.parent)
        v.dataType = typeSlot.getValue()
        return v

    def getModelHiddenLayout(self, slot, fields):
        value = slot.getValue()
        if value and type(value) is type(''):
            value = self.forgetLegacyUploadFile(slot, value)

        if not value:
            value = commonTools.newThing('other', 'Upload')

        data = {
            'data': value.data,
            'type': value.dataType,
            'fileName': value.dataFileName
        }

        sessionsProxy = getProxyForServerRole('sessions')
        if context.getVar('session') is None:
            req = context.getVar('req')
            session = sessionsProxy.newSession(req.connection.remote_ip,
                    serverId = context.getVar('virtualHost').defaultDispatcherId)
            sessionToken = session['sessionToken']
            session['isDirty'] = 1
            context.setVar('sessionToken', sessionToken)
            context.setVar('session', session)
            # TODO: how to deal here with cookies support ?

        self.dataToken = sessionsProxy.addTemporaryData(data)
        return X.input(name = slot.getFieldOptionName('dataToken'),
                       type = 'hidden', value = self.dataToken)

    def getHtmlFormValue(self, slot, fields, **keywords):
        fieldName = slot.getFieldName()
        fieldValue = slot.getValue() or ''

        layout = X.array()
        layout += self.getModelHiddenLayout(slot, fields)
        representation = self.getRepresentation(slot, fields, **keywords)
        if representation:
            layout += representation
            layout += X.br()
        layout += X.input(name = fieldName, type = 'file')

        if self.apply:
            layout += ' '
            layout += X.buttonInForm('apply', 'applyButton')

        return layout

    def getRepresentation(self, slot, fields, **keywords):
        value = slot.getValue()
        if not value:
            return ''

        if type(value) is type(''):
            value = self.forgetLegacyUploadFile(slot, value)

        if not isTypeOfMimeType(value.dataType, 'image'):
            return ''

        uri = X.actionUrl('imageEdit/%s' % self.dataToken)
        return X.img(src = uri)

    def submit(self, slot, fields):
        value = slot.getField(fields)
        upload = commonTools.newThing('other', 'Upload')
        dataToken = slot.getFieldOption(fields, 'dataToken')
        sessionsProxy = getProxyForServerRole('sessions')
        if value.value:
            upload.data = value.value
            upload.dataFileName = value.filename
            if '\\' in upload.dataFileName:
                upload.dataFileName = upload.dataFileName[
                        upload.dataFileName.rindex('\\')+1:]
            upload.dataType = value.type
        else:
            data = sessionsProxy.getTemporaryData(dataToken)
            if not data:
                return None
            upload.data = data['data']
            upload.dataFileName = data['fileName']
            upload.dataType = data['type']
        
        # UploadFiles use Upload widget but not Upload kind
        # (and it wants information about type and filename)
        #
        # This line (+ more in UploadFilesWeb.py (submitFields))
        # is there while waiting for UploadFiles conversion
        # to Upload kind.
        context.setVar('UploadFilesXXX', upload)

        sessionsProxy.delTemporaryData(dataToken)
        return upload
register(UploadFile)


class Url(WidgetMixin, proxyWidgets.Url):
    def getHtmlViewValue(self, slot, fields, **keywords):
        fieldValue = slot.getValue() or ''
        if fieldValue:
            if fieldValue.startswith('http://'):
                href = fieldValue
            else:
                href = 'http://%s' % fieldValue
            return X.a(href = href)(fieldValue)
        else:
            return X.nbsp

    def getHtmlFormValue(self, slot, fields, **keywords):
        fieldName = slot.getFieldName()
        fieldValue = slot.getValue() or ''

        layout = X.array()

        inputAttributes = {}
        maxLength = slot.getKind().getTextMaxLength()
        if maxLength is not None:
            inputAttributes['maxlength'] = maxLength
        if self.size is not None:
            size = self.size
        elif maxLength is not None:
            size = min(80, maxLength)
        else:
            size = None
        if size is not None:
            inputAttributes['size'] = size
        layout += X.input(name = fieldName, type = 'text',
                          value = fieldValue, **inputAttributes)
        if self.apply:
            layout += ' '
            layout += X.buttonInForm('apply', 'applyButton')
        return layout
register(Url)


class XSelect(Select, proxyWidgets.XSelect):
    otherFieldLabel_defaultValue = N_('other:')
    otherFieldLabel_kind_widget_fieldLabel = N_('Label')

    def getHtmlValue(self, slot, fields, **keywords):
        fieldName = slot.getFieldName()
        fieldValue = slot.getValue()
        if fieldValue is None:
            fieldValue = ''
        else:
            fieldValue = str(fieldValue)
        otherFieldLabel = self.otherFieldLabel or \
                          self.otherFieldLabel_defaultValue 
        otherFieldValue = slot.getFieldOption(fields, 'other', default = '')
        kind = slot.getKind()
        if kind.sortLabels:
            values = kind.getSortedValues(slot)
        else:
            values = kind.getValues(slot)
        groupNames, groupedValues = kind.getGroupedValues(slot)
        if values is None:
            raise Exception('Kind "%s" of slot "%s" has no values' % (
                    kind, slot))
        if not fieldValue in values and (fieldValue or None not in values):
            fieldValue = kind.defaultValue
            if fieldValue is None:
                fieldValue = ''
            else:
                fieldValue = str(fieldValue)
        labels = self.getLabels(slot)

        if not self.isInForm():
            if labels.has_key(str(fieldValue)):
                layout = self.makeModelTitleFromValue(slot, fields, fieldValue)
            elif fieldValue == '__all__':
                layout = _(self.allLabel)
            elif otherFieldValue:
                layout = X.asIs(_(str(otherFieldValue)))
            else:
                layout = None
        elif self.isReadOnly(slot):
            layout = X.array(
                self.getModelHiddenLayout(slot, fields),
                )
            if labels.has_key(str(fieldValue)):
                displayFieldValue = self.makeModelTitleFromValue(
                    slot, fields, fieldValue)
            else:
                displayFieldValue = _(str(fieldValue))
            layout += displayFieldValue
        else:
            layout = X.array()
            table = X.table(_class = 'field-value', id = '')
            layout += table
            selectAttributes = {}
            selectAttributes['name'] = fieldName
            select = X.select(**selectAttributes)
            if not fieldValue in values and (fieldValue or None not in values):
                if labels.has_key(fieldValue):
                    noneLabel = _(labels[fieldValue])
                else:
                    noneLabel = _(self.noneLabel)
                select += X.option(selected = 'selected', value = '')(
                        noneLabel)
            groupNames = None
            if hasattr(kind, 'getGroupedValues'):
                groupNames, groupedValues = kind.getGroupedValues(slot)
            if groupNames is not None:
                for groupName in groupNames:
                    groupValues = groupedValues[groupName]
                    optgroup = X.optgroup(label = _(groupName))
                    select += optgroup
                    for value in groupValues:
                        optionAttributes = {}
                        if value is None:
                            value = ''
                        valueAsString = str(value)
                        if valueAsString == fieldValue:
                            optionAttributes['selected'] = 'selected'
                        if labels.has_key(valueAsString):
                            label = _(labels[valueAsString])
                        elif not value:
                            label = _(self.noneLabel)
                        elif value == '__all__':
                            label = _(self.allLabel)
                        else:
                            label = _(value)
                        optgroup += X.option(
                                value = value, **optionAttributes)(label)
            else:
                for value in values:
                    optionAttributes = {}
                    if value is None:
                        value = ''
                    valueAsString = str(value)
                    if valueAsString == fieldValue:
                        optionAttributes['selected'] = 'selected'
                    if labels.has_key(valueAsString):
                        label = _(labels[valueAsString])
                    elif not value:
                        label = _(self.noneLabel)
                    elif value == '__all__':
                        label = _(self.allLabel)
                    else:
                        label = _(value)
                    select += X.option(value = value, **optionAttributes)(
                            label)
            table += X.tr(
                    _class = 'field-value', id = 'tooltip-%s' % fieldName)(
                            X.td(_class = 'field-value', colspan="2")(select))
            inputAttributes = {}
            maxLength = slot.getKind().getTextMaxLength()
            if maxLength is not None:
                inputAttributes['maxlength'] = maxLength
            if self.size is not None:
                size = self.size
            elif maxLength is not None:
                size = min(80, maxLength)
            else:
                size = None
            if size is not None:
                inputAttributes['size'] = size
            table += X.tr(_class = 'field-value')(
                    X.td(_class = 'field-label')(X.asIs(_(otherFieldLabel))),
                    X.td(_class = 'field-value')(
                    X.input(
                    name = '%s_other' % fieldName, type = 'text',
                    value = otherFieldValue, **inputAttributes)))
            if self.apply:
                layout += ' '
                layout += X.buttonInForm('apply', 'applyButton')
        return layout

    def getModelHiddenLayout(self, slot, fields):
        fieldName = slot.getFieldName()
        fieldValue = slot.getValue() or ''
        return X.input(name = fieldName, type = 'hidden', value = fieldValue)

    def makeModelTitleFromValue(self, slot, fields, value):
        if value is None:
            valueAsString = ''
        else:
            valueAsString = str(value)
        layoutMode = context.getVar('layoutMode')
        if layoutMode != 'edit':
            if self.titles and self.titles.has_key(valueAsString):
                return _(self.titles[valueAsString])
            kind = slot.getKind()
            title = kind.makeModelTitleFromValue(slot, fields, value)
            if title:
                return title
        labels = self.getLabels(slot)
        if labels and labels.has_key(valueAsString):
            return _(labels[valueAsString])
        return valueAsString

    def submit(self, slot, fields):
        otherFieldValue = slot.getFieldOption(fields, 'other', default = '')
        if otherFieldValue:
            return otherFieldValue
        return WidgetMixin.submit(self, slot, fields)
register(XSelect)

