#  Copyright (c) 2007, Enthought, Inc.
#  License: BSD Style.

#--(Creating New Trait Types)---------------------------------------------------
"""
Creating New Trait Types
========================

You create a *new style* trait type by subclassing the **TraitType** class or 
one of its subclasses, such as **Float** or **Str**. **TraitType** provides the 
infrastructure for creating a trait type and allows subclasses to define 
specific methods and class constants used to create either a new trait *type* or 
*property*.

In the next section, we'll cover the methods and class constants used to define
a new trait *type*, and in the section following that we'll show how to define
a new trait *property*.

Defining a New Trait Type
-------------------------

The thing that distinguishes a trait *type* from a *property* is the existence
of a *validate* method in the subclass. The *validate* method should have the 
following signature: 
           
validate ( self, object, name, value )
    This method validates, coerces, or adapts the specified *value* as the value 
    of the *name* trait of the *object* object. This method is called when a 
    value is assigned to an object trait that is based on this subclass of 
    *TraitType* and the class does not  contain a definition for either the 
    *get()* or *set()* methods. 
    
    The method must return the original *value* or any suitably coerced or 
    adapted value that is a legal value for the trait. If *value* is  not a 
    legal value for the trait, and cannot be coerced or adapted to a legal 
    value, the method should either raise a **TraitError** or  call the 
    *error()* method to raise a **TraitError** on its behalf.
      
In addtion to *validate*, the subclass can also define the *post_setattr*
method, which should have the following signature:
    
post_setattr ( self, object, name, value )
    This method allows the trait to do additional processing after *value* has
    been successfully assigned to the *name* trait of the *object* object. For 
    most traits there is no additional processing that needs to be done, and 
    this method need not be defined. It is normally used for creating *shadow* 
    (i.e., *mapped* traits), but other uses may arise as well. This method does 
    not need to return a value, and should normally not raise any exceptions.
    
The subclass can also define a constant default value by setting the class-level
*default_value* attribute to the desired constant value. For example::
    
    class OddInt ( Int ):
        
        default_value = 1
        
        ...
        
If a non-constant default value is desired, you should override the 
**TraitType** class's *get_default_value* method. Refer to the documentation
for the **TraitType** class for more information on what this method does.

If you have a constant string that can be used as the type's *info* value, you
can provide it by simple setting the string as the value of the class-level
*info_text* attribute::

    class OddInt ( Int ):
        
        info_text = 'an odd integer'
        
        ...
        
If you have a type info string which depends upon the internal state of the
trait, then you should override the **TraitType's** *info* method. This method
has no arguments, and should return a string describing the values accepted by 
the trait type (e.g. 'an integer in the range from 1 to 5').

If you also have some type specific initialization that needs to be performed
when the trait type is created, you can also override the **TraitType** class's
*init* method. This method has no arguments and is automatically called from
the **TraitType** class constructor.

If you would like to specify a default Traits UI editor for your new trait type,
you can also override the **TraitType** class's *create_editor* method, which 
has no arguments and should return the default **TraitEditor** for any 
instances of the type to use. 

This provides a basic overview of the basic methods and class constants needed
to define a new trait type. Refer to the complete documentation for the
**TraitType** and **BaseTraitHandler** classes for more information on other
methods that can be overridden if necessary.

Defining a New Trait Property
-----------------------------

You can also define new trait *properties* by subclassing from **TraitType** or
one of its subclasses. A *property* is distinguished from a *type* by the
existence of a *get* and/or *set* method in the **TraitType** subclass. The
signature for these two methods is as follows:
    
get ( self, object, name )
    This is the *getter* method of a trait that behaves like a property. It has 
    the following arguments:
    
    object
        The object that the property applies to. 
    name
        The name of the *object* property.
    
    If this method is not defined, but the *set* method is defined, the trait  
    behaves like a *write-only* property. This method should return the value of 
    the *name* property for the *object* object.
            
set ( self, object, name, value )
    This is the *setter* method of a trait that behaves like a property. It has
    the following arguments:
      
    object 
        The object the property applies to. 
    name
        The name of the property on *object*.
    value
        The value being assigned as the value of the property. 
          
    If this method is not defined, but the *get* method is defined, the trait 
    behaves like a *read-only* property. This method does not need to return a 
    value, but it should raise a **TraitError** exception if the specified 
    *value* is not valid and cannot be  coerced or adapted to a valid value.

Because the value of a *property* is determined by the *get* method, the
*default_value* class constant and *get_default_value* method are not used.

However, all other values and methods, such as the *info_text* class attribute 
and *info* method apply to a *property* as well as a normal type. Please refer 
to the preceding section on defining a trait type for additional infomation
that applies to properties as well.
"""

#--<Imports>--------------------------------------------------------------------

from enthought.traits.api import *

#--[DiceRoll Type]--------------------------------------------------------------

# Define a type whose value represents the roll of a pair of dice:
class DiceRoll ( TraitType ):

    # Set default value to 'snake-eyes':
    default_value = ( 1, 1 )
    
    # Describe the type:
    info_text = ('a tuple of the form (n,m), where both n and m are integers '
                 'in the range from 1 to 6 representing a roll of a pair of '
                 'dice')
    
    # Validate any value assigned to the trait to make sure it is a valid
    # dice roll:
    def validate ( self, object, name, value ):
        if (isinstance( value, tuple ) and (len( value ) == 2) and
            (1 <= value[0] <= 6) and (1 <= value[1] <= 6)):
            return value
            
        self.error( object, name, value )

#--[RandInt Property]-----------------------------------------------------------

from random import randint

# Define a read-only property whose value is a random integer in a specified
# range:
class RandInt ( TraitType ):
    
    # Define the type's constructor:
    def __init__( self, low = 1, high = 10, **metadata ):
        super( RandInt, self ).__init__( **metadata )
        self.low  = int( low )
        self.high = int( high )
        
    # Define the property's getter:
    def get ( self ):
        return randint( self.low, self.high )
        
    # Define the type's type information:
    def info ( self ):
        return ('a random integer in the range from %d to %d' % 
                ( self.low, self.high))
        
#--[Craps Class]----------------------------------------------------------------

# Define a test class containing both new trait types/properties:
class Craps ( HasTraits ):
    
    rolls = List( DiceRoll )
    die   = RandInt( 1, 6 )

#--[Example*]--------------------------------------------------------------------

# Create a test object:
craps = Craps()

# Add a number of test dice rolls:
for i in range( 10 ):
    craps.rolls.append( ( craps.die, craps.die ) )
    
# Display the results:
print craps.rolls

# Try to assign an invalid dice roll:
try:
    craps.rolls.append( ( 0, 0 ) )
except TraitError:
    print 'Assigning an invalid dice roll failed.'
