~ubuntu-branches/debian/wheezy/phatch/wheezy

« back to all changes in this revision

Viewing changes to phatch/core/lib/formField.py

  • Committer: Bazaar Package Importer
  • Author(s): Emilio Pozuelo Monfort
  • Date: 2008-02-13 23:48:47 UTC
  • Revision ID: james.westby@ubuntu.com-20080213234847-mp6vc4y88a9rz5qz
Tags: upstream-0.1
ImportĀ upstreamĀ versionĀ 0.1

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2007-2008 www.stani.be
 
2
#
 
3
# This program is free software: you can redistribute it and/or modify
 
4
# it under the terms of the GNU General Public License as published by
 
5
# the Free Software Foundation, either version 3 of the License, or
 
6
# (at your option) any later version.
 
7
#
 
8
# This program is distributed in the hope that it will be useful,
 
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
11
# GNU General Public License for more details.
 
12
 
13
# You should have received a copy of the GNU General Public License
 
14
# along with this program.  If not, see http://www.gnu.org/licenses/
 
15
 
 
16
"""
 
17
Store internally as a string.
 
18
Provide validation routines.
 
19
"""
 
20
 
 
21
#---import modules
 
22
 
 
23
#standard library
 
24
import os, textwrap, types
 
25
 
 
26
if '_' not in dir():
 
27
    _ = str
 
28
 
 
29
#gui independent (core.lib)
 
30
from odict import odict as Fields
 
31
 
 
32
NO_FIELDS               = Fields()
 
33
_t                      = unicode
 
34
#---image
 
35
ALIGN_HORIZONTAL        = [_t('left'),_t('center'),_t('right')]
 
36
ALIGN_VERTICAL          = [_t('top'),_t('middle'),_t('bottom')]
 
37
 
 
38
FONT_EXTENSIONS         = ['ttf','otf','ttc']
 
39
 
 
40
IMAGE_EXTENSIONS        = ['bmp','dib','gif','jpe','jpeg','jpg','im','msp',
 
41
                        'pcx','png','pbm','pgm','ppm','tif','tiff','xbm']
 
42
IMAGE_READ_EXTENSIONS   = IMAGE_EXTENSIONS + ['cur','dcx','fli','flc','fpx',
 
43
                        'gbr','gd','ico','imt','mic','mcidas','pcd',
 
44
                        'psd','bw','rgb','cmyk','sun','tga','xpm']
 
45
IMAGE_READ_EXTENSIONS.sort()
 
46
IMAGE_READ_MIMETYPES    = ['image/'+ext for ext in IMAGE_READ_EXTENSIONS]
 
47
IMAGE_WRITE_EXTENSIONS  = IMAGE_EXTENSIONS + ['eps','ps','pdf']
 
48
IMAGE_WRITE_EXTENSIONS.sort()
 
49
IMAGE_MODES             = [_t('Monochrome (1-bit pixels, black and white)'),
 
50
                        _t('Grayscale (8-bit pixels, black and white)'),
 
51
                        _t('RGB (3x8-bit pixels, true colour)'),
 
52
                        _t('RGBA (4x8-bit pixels, RGB with transparency mask)'),
 
53
                        _t('CMYK (4x8-bit pixels, colour separation)'),
 
54
                        _t('P (8-bit pixels, mapped using a colour palette)'),
 
55
                        _t('YCbCr (3x8-bit pixels, colour video format)'),
 
56
                        _t('I (32-bit integer pixels)'),
 
57
                        _t('F (32-bit floating point pixels)')]
 
58
IMAGE_EFFECTS           = [_t('blur'), _t('contour'), _t('detail'), 
 
59
                            _t('edge enhance'), _t('edge enhance more'), 
 
60
                            _t('emboss'), _t('find edges'), _t('smooth'), 
 
61
                            _t('smooth more'), _t('sharpen')]
 
62
IMAGE_FILTERS           = [_t('nearest'),_t('bilinear'),_t('bicubic')]
 
63
IMAGE_RESAMPLE_FILTERS  = IMAGE_FILTERS + [_t('antialias')]
 
64
IMAGE_TRANSPOSE         = [_t('Rotate 90'), _t('Rotate 180'), _t('Rotate 270'),
 
65
                            _t('Flip Left Right'),_t('Flip Top Bottom')]
 
66
 
 
67
TEXT_ORIENTATION        = [_t('Normal')] + IMAGE_TRANSPOSE
 
68
 
 
69
IMAGE_MODELS_WRITE_EXTENSIONS = ['<type>']+IMAGE_WRITE_EXTENSIONS
 
70
 
 
71
IMAGE_READ_EXTENSIONS.sort()
 
72
IMAGE_WRITE_EXTENSIONS.sort()
 
73
 
 
74
RANK_SIZES              = [3,5]
 
75
 
 
76
#---os
 
77
def ensure_path(path):
 
78
    """Ensure a path exists, create all not existing paths."""
 
79
    if not os.path.exists(path):
 
80
        parent  = os.path.dirname(path)
 
81
        if parent:
 
82
            ensure_path(parent)
 
83
            os.mkdir(path)
 
84
        else:
 
85
            raise OSError, "The path '%s' is not valid."%path
 
86
 
 
87
def is_www_file(value):
 
88
    return value.startswith('http://') or value.startswith('ftp://')
 
89
 
 
90
def is_file(value):
 
91
    return os.path.isfile(value) or is_www_file(value)
 
92
 
 
93
#---form
 
94
class Form(object):
 
95
    #todo: move this as instance attributes
 
96
    dpi     = 'dpi'
 
97
    label   = 'label'
 
98
    icon    = 'ART_TIP'
 
99
    tags    = []
 
100
    __doc__ = ''
 
101
    
 
102
    def __init__(self,**options):
 
103
        """For the possible options see the source code."""
 
104
        fields  = Fields()
 
105
        fields['__enabled__']   = BooleanField(True,visible=False)
 
106
        self.interface(fields)
 
107
        self._fields            = fields
 
108
        self._fields.update(options)
 
109
        
 
110
    def interface(self,fields):
 
111
        pass
 
112
        
 
113
    def __cmp__(self, other):
 
114
        label       = _(self.label)
 
115
        other_label = _(other_label)
 
116
        if label < other_label: return -1
 
117
        elif label == other_label: return 0
 
118
        else: return 1
 
119
        
 
120
    def _get_fields(self):
 
121
        return self._fields
 
122
    
 
123
    def get_field_labels(self):
 
124
        return self._get_fields().keys()
 
125
    
 
126
    def _get_field(self,label):
 
127
        return self._fields[label]
 
128
    
 
129
    def get_field(self,label,info={}):
 
130
        return self._get_field(label).get(info,label)
 
131
    
 
132
    def get_fields(self,info,convert=False,pixel_fields={}):
 
133
        result  = {}
 
134
        for label in self.get_field_labels():
 
135
            if label[:2] != '__':
 
136
                param = None
 
137
                #skip hidden fields such as __enabled__
 
138
                if label in pixel_fields:
 
139
                    #pixel size -> base, dpi needed
 
140
                    param       = pixel_fields[label]
 
141
                    if type(param) != types.TupleType:
 
142
                        param   = (param,info[self.dpi])
 
143
                elif self._get_field(label).__class__ == PixelField:
 
144
                    param       = (1,1)
 
145
                if param:
 
146
                    value       = self.get_field_size(label,info,*param)
 
147
                else:
 
148
                    #retrieve normal value
 
149
                    value       = self.get_field(label,info)
 
150
                #convert field labels to function parameters
 
151
                if convert:
 
152
                    label       = label.lower().replace(' ','_')
 
153
                result[label]   = value
 
154
        return result
 
155
    
 
156
    def get_field_size(self,label,info,base,dpi):
 
157
        return self._get_field(label).get_size(info,base,dpi,label)
 
158
    
 
159
    def get_field_filesize(self,label,info,base):
 
160
        return self._get_field(label).get_size(info,base,label)
 
161
    
 
162
    def get_field_string(self,label):
 
163
        return self._get_field(label).get_as_string()
 
164
    
 
165
    def is_enabled(self):
 
166
        return self.get_field('__enabled__',None)
 
167
    
 
168
    def _set_field(self,label,field):
 
169
        self._fields[label] = field
 
170
        
 
171
    def set_field(self,label,value):
 
172
        self._get_field(label).set(value)
 
173
        return self
 
174
        
 
175
    def set_fields(self,**options):
 
176
        for label, value in options.items():
 
177
            self.set_field(label, value)
 
178
    
 
179
    def set_field_as_string(self,label,value_as_string):
 
180
        self._get_field(label).set_as_string(value_as_string)
 
181
        return self
 
182
        
 
183
    def load(self,fields):
 
184
        """Load dumped, raw strings."""
 
185
        invalid_labels  = []
 
186
        for label, value in fields.items():
 
187
            if self._fields.has_key(label):
 
188
                self.set_field_as_string(label,value)
 
189
            else:
 
190
                invalid_labels.append(label)
 
191
        return invalid_labels
 
192
            
 
193
    def dump(self):
 
194
        """Dump as raw strings"""
 
195
        fields_as_strings  = {}
 
196
        for label in self.get_field_labels():
 
197
            fields_as_strings[label]  = self.get_field_string(label)
 
198
        return {'label':self.label,'fields':fields_as_strings}
 
199
    
 
200
    #tools
 
201
    def ensure_path(self,path):
 
202
        return ensure_path(path)
 
203
    
 
204
    def is_www_file(self,path):
 
205
        return is_www_file(path)
 
206
    
 
207
    def is_file(self,path):
 
208
        return is_file(path)
 
209
 
 
210
    
 
211
#---errors
 
212
class ValidationError(Exception):
 
213
    
 
214
    def __init__(self, expected, message, details=None):
 
215
        """ValidationError for invalid input.
 
216
        
 
217
        expected - description of the expected value
 
218
        message  - message why validation failed
 
219
        details  - eg. which variables are allowed"""
 
220
        self.expected       = expected
 
221
        self.message        = message
 
222
        self.details        = details
 
223
        
 
224
    def __str__(self):
 
225
        return self.message
 
226
    
 
227
#---field mixins
 
228
class PilConstantMixin:
 
229
    def to_python(self,x,label):
 
230
        return x.upper().replace(' ','_')
 
231
    
 
232
class TestFieldMixin:
 
233
    """ Mixin class, the to_python method should
 
234
    
 
235
    def to_python(self,x,label,test=False):
 
236
        "test parameter to signal test-validate"
 
237
        return x
 
238
    
 
239
    """
 
240
        
 
241
    def get(self,info=None,label='?',value_as_string=None,test=False):
 
242
        """Use this method to test-validate the user input, for example:
 
243
            field.get(IMAGE_TEST_INFO, value_as_string, label, test=True)"""
 
244
        if value_as_string is None:
 
245
            value_as_string = self.value_as_string
 
246
        return self.to_python(self.interpolate(value_as_string,info,label),
 
247
                label,test)
 
248
        
 
249
#---fields
 
250
class Field(object):
 
251
    """Base class for fields. This will be subclassed but, 
 
252
    never used directly.
 
253
    
 
254
    Required to overwrite:
 
255
    description - describes the expected value
 
256
    
 
257
    Optional to overwrite
 
258
    to_python   - raise here exceptions in case of validation errors (defaults 
 
259
                  to string). 
 
260
    to_string   - (defaults to string)
 
261
    
 
262
    Never overwrite:
 
263
    validate    - will work right out of the box as exceptions are raised by
 
264
                  the to_python method
 
265
    get         - gets the current value as a string
 
266
    set         - sets the current value as a string
 
267
    
 
268
    You can access the value by self.value_as_string
 
269
    
 
270
    This field interpolates <variables> within a info.
 
271
    << or >> will be interpolated as < or >
 
272
    """
 
273
    
 
274
    description             = '<?>'
 
275
    
 
276
    def __init__(self,value,visible=True):
 
277
        self.visible    = visible
 
278
        if isinstance(value, (str, unicode)):
 
279
            self.set_as_string(value)
 
280
        else:
 
281
            self.set(value)
 
282
        
 
283
    def interpolate(self,x,info,label):
 
284
        if info == None:
 
285
            return self.value_as_string
 
286
        else:
 
287
            try:
 
288
                return x.replace('%','%%')\
 
289
                    .replace('<','%(').replace('>',')s')\
 
290
                    .replace('%(%(','<').replace(')s)s','>')%info
 
291
            except KeyError, variable:
 
292
                raise ValidationError(self.description,
 
293
                "%s: %s '%s' %s."%(_(label),_("the variable"),
 
294
                    variable.message,_("doesn't exist")),
 
295
                    _('Use the Image Inspector to list all the variables.'))
 
296
 
 
297
    def to_python(self,x,label):
 
298
        return x
 
299
    
 
300
    def to_string(self,x):
 
301
        return unicode(x)
 
302
    
 
303
    def get_as_string(self):
 
304
        """For GUI: Translation, but no interpolation here"""
 
305
        return self.value_as_string
 
306
    
 
307
    def set_as_string(self,x):
 
308
        """For GUI: Translation, but no interpolation here"""
 
309
        self.value_as_string    = x
 
310
        
 
311
    def get(self,info=None,label='?',value_as_string=None,test=False):
 
312
        """For code: Interpolated, but not translated
 
313
        - value_as_string can be optionally provided to test the expression
 
314
        
 
315
        Ignore test parameter (only for compatiblity with TestField)"""
 
316
        if value_as_string is None:
 
317
            value_as_string = self.value_as_string
 
318
        return self.to_python(self.interpolate(value_as_string,info,label),
 
319
                label)
 
320
   
 
321
    def set(self,x):
 
322
        """For code: Interpolated, but not translated"""
 
323
        self.value_as_string  = self.to_string(x)
 
324
        
 
325
    def eval(self,x,label):
 
326
        try:
 
327
            return eval(x)
 
328
        except SyntaxError:
 
329
            pass
 
330
        except NameError:
 
331
            pass
 
332
        raise ValidationError(self.description, 
 
333
            '%s: %s "%s" %s.'%(_(label),_('invalid syntax'),x,
 
334
                _('for integer')))
 
335
            
 
336
class IntegerField(Field):
 
337
    """"""
 
338
    description             = _('integer')
 
339
    
 
340
    def to_python(self,x,label):
 
341
        error   = ValidationError(self.description, 
 
342
            '%s: %s "%s" %s.'%(_(label),_('invalid literal'),x,
 
343
                _('for integer')))
 
344
        try:
 
345
            return int(round(self.eval(x,label)))
 
346
        except ValueError:
 
347
            raise error
 
348
        except TypeError:
 
349
            raise error
 
350
 
 
351
class PositiveIntegerField(IntegerField):
 
352
    """"""
 
353
    description = _('positive integer')
 
354
 
 
355
    def to_python(self,x,label):
 
356
        value = super(PositiveIntegerField, self).to_python(x,label)
 
357
        if value < 0:
 
358
            raise ValidationError(self.description,
 
359
            '%s: %s "%s" %s.'%(_(label),('the integer value'),x,
 
360
                _('is negative, but should be positive')))
 
361
        return value
 
362
    
 
363
class PositiveNonZeroIntegerField(PositiveIntegerField):
 
364
    """"""
 
365
    
 
366
    description = _('positive, non-zero integer')
 
367
 
 
368
    def to_python(self,x,label):
 
369
        value = super(PositiveNonZeroIntegerField, self).to_python(x,label)
 
370
        if value == 0:
 
371
            raise ValidationError(self.description,
 
372
                '%s: %s "%s" %s.'%(_(label),_('the integer value'),x,
 
373
                    _('is zero, but should be non-zero.')))
 
374
        return value
 
375
                
 
376
class DpiField(PositiveNonZeroIntegerField):
 
377
    """PIL defines the resolution in two dimensions as a tuple (x,y).
 
378
    Phatch ignores this possibility and simplifies by using only one resolution
 
379
    """
 
380
    
 
381
    description = _('resolution')
 
382
    
 
383
class FloatField(Field):
 
384
    description             = _('float')
 
385
    
 
386
    def to_python(self,x,label):
 
387
        try:
 
388
            return float(self.eval(x,label))
 
389
        except ValueError, message:
 
390
            raise ValidationError(self.description, 
 
391
            '%s: %s "%s" %s.'%(_(label),_('invalid literal'),x,_('for float')))
 
392
 
 
393
class PositiveFloatField(FloatField):
 
394
    """"""
 
395
    description = _('positive integer')
 
396
 
 
397
    def to_python(self,x,label):
 
398
        value = super(PositiveFloatField, self).to_python(x,label)
 
399
        if value < 0:
 
400
            raise ValidationError(self.description,
 
401
            '%s: %s "%s" %s.'%(_(label),('the float value'),x,
 
402
                _('is negative, but should be positive')))
 
403
        return value
 
404
    
 
405
class PositiveNonZeroFloatField(PositiveIntegerField):
 
406
    """"""
 
407
    
 
408
    description = _('positive, non-zero integer')
 
409
 
 
410
    def to_python(self,x,label):
 
411
        value = super(PositiveNonZeroIntegerField, self).to_python(x,label)
 
412
        if value == 0:
 
413
            raise ValidationError(self.description,
 
414
                '%s: %s "%s" %s.'%(_(label),_('the float value'),x,
 
415
                    _('is zero, but should be non-zero.')))
 
416
        return value
 
417
                
 
418
class BooleanField(Field):
 
419
    description             = _('boolean')
 
420
    
 
421
    def to_string(self,x):
 
422
        return super(BooleanField,self).to_string(x).lower()
 
423
    
 
424
    def to_python(self,x,label):
 
425
        if x.lower() in ['1','true','yes']: return True
 
426
        if x.lower() in ['0','false','no']: return False
 
427
        raise ValidationError(self.description, 
 
428
            '%s: %s "%s" %s (%s,%s).'%(_(label),_('invalid literal'), x,
 
429
                _('for boolean'),_('true'),_('false')))
 
430
    
 
431
class CharField(Field):
 
432
    description             = _('string')
 
433
    pass
 
434
    
 
435
class ChoiceField(CharField):
 
436
    description             = _('choice')
 
437
    def __init__(self,value,choices,**keyw):
 
438
        super(ChoiceField,self).__init__(value,**keyw)
 
439
        self.choices    = choices
 
440
        
 
441
class FileField(CharField):
 
442
    extensions  = []
 
443
    allow_empty = False
 
444
    
 
445
    def to_python(self,x,label):
 
446
        value   = super(FileField, self).to_python(x,label).strip()
 
447
        if not value and self.allow_empty:
 
448
            return ''
 
449
        ext     = os.path.splitext(value)[-1][1:]
 
450
        if self.extensions and not (ext in self.extensions):
 
451
            if ext:
 
452
                raise ValidationError(self.description,
 
453
                '%s: %s "%s" %s\n\n%s:\n%s.'%(_(label),
 
454
                    _('the file extension'),ext,
 
455
                    _('is invalid.'),
 
456
                    _('You can only use files with the following extensions'),
 
457
                    ', '.join(self.extensions)))
 
458
            else:
 
459
                raise ValidationError(self.description,
 
460
                '%s: %s\n%s:\n%s.'%(
 
461
                    _(label),
 
462
                    _('a filename with a valid extension was expected.'),
 
463
                    _('You can only use files with the following extensions'),
 
464
                    textwrap.fill(', '.join(self.extensions),70)))
 
465
        return value
 
466
    
 
467
class ReadFileField(TestFieldMixin,FileField):
 
468
    """This is a test field to ensure that the file exists.
 
469
    It could also have been called the MustExistFileField."""
 
470
    
 
471
    def to_python(self,x,label,test=False):
 
472
        value = super(ReadFileField, self).to_python(x,label)
 
473
        if not value.strip() and self.allow_empty:
 
474
            return ''
 
475
        if (x==value or not test) and (not is_file(value)):
 
476
            raise ValidationError(self.description,
 
477
            '%s: %s "%s" %s.'%(_(label),_('the filename'),value,
 
478
                _('does not exist.')))
 
479
        return value
 
480
    
 
481
class ImageReadFileField(ReadFileField):
 
482
    extensions  = IMAGE_READ_EXTENSIONS
 
483
    
 
484
class FontFileField(ReadFileField):
 
485
    extensions  = FONT_EXTENSIONS
 
486
    allow_empty = True
 
487
    
 
488
class FileNameField(CharField):
 
489
    """Without extension"""
 
490
    pass
 
491
    
 
492
class FilePathField(CharField):
 
493
    pass
 
494
    
 
495
class ImageTypeField(ChoiceField):
 
496
    def __init__(self,value,**keyw):
 
497
        super(ImageTypeField,self).__init__(value,IMAGE_EXTENSIONS,**keyw)
 
498
        
 
499
    def set_as_string(self,x):
 
500
        #ignore translation
 
501
        if x and x[0]=='.':
 
502
            x = x[1:]
 
503
        super(ImageTypeField,self).set_as_string(x)
 
504
        
 
505
##class ImageTypeField(ChoiceField):
 
506
##    def set_as_string(self,x):
 
507
##        if x and x[0]=='.':
 
508
##            x = x[1:]
 
509
##        super(ImageTypeField,self).set_as_string(x)
 
510
##        
 
511
class ImageReadTypeField(ChoiceField):
 
512
    def __init__(self,value,**keyw):
 
513
        super(ImageReadTypeField,self).__init__(\
 
514
            value,IMAGE_READ_EXTENSIONS,**keyw)
 
515
        
 
516
class ImageWriteTypeField(ChoiceField):
 
517
    def __init__(self,value,**keyw):
 
518
        super(ImageWriteTypeField,self).__init__(\
 
519
            value,IMAGE_MODELS_WRITE_EXTENSIONS,**keyw)
 
520
        
 
521
class ImageModeField(ChoiceField):
 
522
    def __init__(self,value,**keyw):
 
523
        super(ImageModeField,self).__init__(value,IMAGE_MODES,**keyw)
 
524
        
 
525
    def to_python(self,x,label):
 
526
        return x.split(' ')[0].replace('Grayscale','L').replace('Monochrome','1')
 
527
        
 
528
class ImageEffectField(PilConstantMixin,ChoiceField):
 
529
    def __init__(self,value,**keyw):
 
530
        super(ImageEffectField,self).__init__(\
 
531
            value,IMAGE_EFFECTS,**keyw)
 
532
            
 
533
class ImageFilterField(PilConstantMixin,ChoiceField):
 
534
    def __init__(self,value,**keyw):
 
535
        super(ImageFilterField,self).__init__(\
 
536
            value,IMAGE_FILTERS,**keyw)
 
537
 
 
538
class ImageResampleField(PilConstantMixin,ChoiceField):
 
539
    def __init__(self,value,**keyw):
 
540
        super(ImageResampleField,self).__init__(\
 
541
            value,IMAGE_RESAMPLE_FILTERS,**keyw)
 
542
            
 
543
class ImageTransposeField(PilConstantMixin,ChoiceField):
 
544
    def __init__(self,value,**keyw):
 
545
        super(ImageTransposeField,self).__init__(\
 
546
            value,IMAGE_TRANSPOSE,**keyw)
 
547
            
 
548
class TextOrientationField(PilConstantMixin,ChoiceField):
 
549
    def __init__(self,value,**keyw):
 
550
        super(TextOrientationField,self).__init__(\
 
551
            value,TEXT_ORIENTATION,**keyw)
 
552
            
 
553
    def to_python(self,x,label):
 
554
        if x == _t('Normal'):
 
555
            return None
 
556
        return super(TextOrientationField,self).to_python(x,label)
 
557
    
 
558
class AlignHorizontalField(ChoiceField):
 
559
    def __init__(self,value,**keyw):
 
560
        super(AlignHorizontalField,self).__init__(\
 
561
            value,ALIGN_HORIZONTAL,**keyw)
 
562
            
 
563
class AlignVerticalField(ChoiceField):
 
564
    def __init__(self,value,**keyw):
 
565
        super(AlignVerticalField,self).__init__(\
 
566
            value,ALIGN_VERTICAL,**keyw)
 
567
            
 
568
class RankSizeField(IntegerField,ChoiceField):
 
569
    def __init__(self,value,**keyw):
 
570
        super(RankSizeField,self).__init__(\
 
571
            value,RANK_SIZES,**keyw)
 
572
            
 
573
class PixelField(IntegerField):
 
574
    """Can be pixels, cm, inch, %."""
 
575
    def get_size(self,info,base,dpi,label,value_as_string=None):
 
576
        if value_as_string is None:
 
577
            value_as_string = self.value_as_string
 
578
        for unit, value in self._units(base,dpi).items():
 
579
            value_as_string = value_as_string.replace(unit,value)
 
580
        return super(PixelField,self).get(info,label,value_as_string)
 
581
        
 
582
    def _units(self,base,dpi):
 
583
        return {
 
584
            'cm'    : '*%f'%(dpi/2.54),
 
585
            'mm'    : '*%f'%(dpi/25.4),
 
586
            'inch'  : '*%f'%dpi,
 
587
            '%'     : '*%f'%(base/100.0),
 
588
            'px'    : '',
 
589
        }
 
590
 
 
591
class FileSizeField(IntegerField):
 
592
    """Can be pixels, cm, inch, %."""
 
593
    def get_size(self,info,base,label,value_as_string=None):
 
594
        if value_as_string is None:
 
595
            value_as_string = self.value_as_string
 
596
        for unit, value in self._units(base).items():
 
597
            value_as_string = value_as_string.replace(unit,value)
 
598
        return super(FileSizeField,self).get(info,label,value_as_string)
 
599
        
 
600
    def _units(self,base):
 
601
        return {
 
602
            'kb'    : '*1024',
 
603
            '%'     : '*%f'%(base/100.0),
 
604
            'gb'    : '*1073741824',
 
605
            'mb'    : '*1048576',
 
606
            'bt'    : '',
 
607
        }
 
608
 
 
609
class SliderField(IntegerField):
 
610
    """A value with boundaries set by a slider."""
 
611
    def __init__(self,value,minValue,maxValue,**keyw):
 
612
        super(SliderField,self).__init__(value,**keyw)
 
613
        self.min    = minValue
 
614
        self.max    = maxValue
 
615
            
 
616
class ColourField(CharField):
 
617
    pass
 
618
##    def to_python(self,x,label):
 
619
##        return eval(x.replace('#','0x'))
 
620
    
 
621
#todo
 
622
##class CommaSeparatedIntegerField(CharField):
 
623
##    """Not implemented yet."""
 
624
##    pass
 
625
##    
 
626
##class DateField(Field):
 
627
##    """Not implemented yet."""
 
628
##    pass
 
629
##    
 
630
##class DateTimeField(DateField):
 
631
##    """Not implemented yet."""
 
632
##    pass
 
633
##    
 
634
##class EmailField(CharField):
 
635
##    """Not implemented yet."""
 
636
##    pass
 
637
##    
 
638
##class UrlField(CharField):
 
639
##    """Not implemented yet."""
 
640
##    pass
 
641
 
 
642
#Give Form all the tools
 
643
FIELDS = [(name,cls) for name, cls in locals().items() 
 
644
            if name[0] != '_' and \
 
645
            ((type(cls) == types.TypeType and issubclass(cls,Field)) or\
 
646
            type(cls) in [types.StringType,types.UnicodeType,types.ListType,
 
647
            types.TupleType])
 
648
        ]
 
649
            
 
650
for name,Field in FIELDS:
 
651
    setattr(Form,name,Field)