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

#--(on_trait_change Decorator)--------------------------------------------------
"""
on_trait_change Decorator
=========================

Until Traits 3.0, the only way to define a static trait notification handler
as part of a class definition was to define the method following a certain 
naming convention::
    
    def _name_changed ( self, ... ):
        ...
        
    or
    
    def _name_fired ( self, ... ):
        ...
        
where *name* is the name of the trait to which the notification handler method
applies.

Starting with Traits 3.0, there is now an alternate method for declaring
notification handlers using the **on_trait_change** decorator. The syntax
for the decorator is::
    
    @on_trait_change( 'extended_trait_name' )
    def any_method_name ( self, ... ):
        ...
        
where *extended_trait_name* is the name of the trait for which the following 
method is the notification handler, and *any_method_name* is an arbitrary
method name, which does not need to follow any particular naming convention.

Although the new syntax is more verbose than the original syntax, it has
several advantages:
    
- It does not require using special method names.
- It allows using the extended trait naming support added to the 
  **on_trait_change** method in Traits 3.0.
  
The last item is especially important since it allows you to statically 
declare notification handlers for many more cases than were previously possible.

For example::
    
    @on_trait_change( 'foo,bar,baz' )
    def _update ( self ):
        ...perform update logic...
        
defines an *_update* method that is called whenever the class's *foo*, *bar*
or *baz* traits change. Previously, this would have required writing three
separate notification handlers (one each for the *foo*, *bar* and *baz* traits),
or adding *event* metadata to each of the trait declarations for *foo*, *bar*
and *baz*.

Similarly, the previous technique of writing methods such as::
    
    def _bar_changed_for_foo ( self, ... ):
        ...
        
which statically defines a notification handler for changes to the *bar* trait
of the object's *foo* trait can now be written as::
    
    @on_trait_change( 'foo.bar' )
    def any_method_name ( self, ... ):
        ...
        
Perhaps even more importantly, this technique can be applied in even more
complex situations, such as::
    
    @on_trait_change( 'foo.bar.baz' )
    def any_method_name ( self, ... ):
        ...
        
    or
    
    @on_trait_change( 'foo.+dirty,foo2.[bar,baz,foogle]' )
    def any_method_name ( self, ... ):
        ...

The first case is a simple extension of the previous example, while the
second is a somewhat far-fetched example which can be interpreted as 
defining a method that handles:
    
- Changes to any trait on the object's *foo* trait which has *dirty* metadata 
  defined, or
- Changes to the *bar*, *baz* or *foogle* trait of the object's *foo2* trait.

Note that there is one important semantic difference between writing::
    
    def _name_changed ( self, ... ):
        ...
        
and::
    
    @on_trait_change( 'name' )
    def any_method_name ( self, ... ):
        ...
        
While both are recognized as being notification handlers for the object's *name*
trait, the interpretation of the argument signature for the first case follows 
the static trait change handler pattern, while the second case follows the
dynamic **on_trait_change** method pattern. While this might seem obvious, given
the fact that the decorator is called *on_trait_change*, it is an important
enough difference to note explicitly.

A Complete Example
------------------

Refer to the code tabs of this lesson for a complete example using the 
*on_trait_change* decorator. In particular, look at the definition of the
*sick_again* method in the **Corporation Class** tab.
"""

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

from enthought.traits.api import *

#--[Employee Class]-------------------------------------------------------------

class Employee ( HasTraits ):
    
    # The name of the employee:
    name = Str
    
    # The number of sick days they have taken this year:
    sick_days = Int
    
#--[Department Class]-----------------------------------------------------------

class Department ( HasTraits ):
    
    # The name of the department:
    name = Str
    
    # The employees in the department:
    employees = List( Employee )
    
#--[Corporation Class]----------------------------------------------------------

class Corporation ( HasTraits ):
    
    # The name of the corporation:
    name = Str
    
    # The departments within the corporation:
    departments = List( Department )
                    
    # Define a corporate 'whistle blower' method:
    @on_trait_change( 'departments:employees.sick_days' )
    def sick_again ( self, object, name, old, new ):
        print '%s just took sick day number %d for this year!' % ( 
              object.name, new )
    
#--[Example*]-------------------------------------------------------------------

# Create some sample employees:
millie   = Employee( name = 'Millie',   sick_days = 2 )
ralph    = Employee( name = 'Ralph',    sick_days = 3 )
tom      = Employee( name = 'Tom',      sick_days = 1 )
slick    = Employee( name = 'Slick',    sick_days = 16 )
marcelle = Employee( name = 'Marcelle', sick_days = 7 )
reggie   = Employee( name = 'Reggie',   sick_days = 11 )
dave     = Employee( name = 'Dave',     sick_days = 0 )
bob      = Employee( name = 'Bob',      sick_days = 1 )
alphonse = Employee( name = 'Alphonse', sick_days = 5 )

# Create some sample departments:
accounting = Department( name      = 'accounting', 
                         employees = [ millie, ralph, tom ] )
    
sales = Department( name      = 'Sales',
                    employees = [ slick, marcelle, reggie ] )
    
development = Department( name      = 'Development', 
                          employees = [ dave, bob, alphonse ] )
    
# Create a sample corporation:    
acme = Corporation( name        = 'Acme, Inc.', 
                    departments = [ accounting, sales, development ] )

# Now let's try out our 'reporting' system:
slick.sick_days  += 1
reggie.sick_days += 1

