1#-------------------------------------------------------------------------------
2#
3#  Defines the NamingLog and NamingIndex traits
4#
5#  Written by: David C. Morrill
6#
7#  Date: 08/15/2005
8#
9#  (c) Copyright 2005 by Enthought, Inc.
10#
11#-------------------------------------------------------------------------------
12
13#-------------------------------------------------------------------------------
14#  Imports:
15#-------------------------------------------------------------------------------
16
17import sys
18
19import six
20
21from traits.api \
22    import Trait, TraitHandler, TraitFactory
23
24from traits.trait_base \
25    import class_of, get_module_name
26
27from traitsui.api \
28    import DropEditor
29
30from apptools.naming.api \
31    import Binding
32
33
34#-------------------------------------------------------------------------------
35#  'NamingInstance' trait factory:
36#-------------------------------------------------------------------------------
37
38def NamingInstance ( klass = None, value = '', allow_none = False, **metadata ):
39    metadata.setdefault( 'copy', 'deep' )
40    return Trait( value, NamingTraitHandler( klass, or_none = allow_none,
41                  module = get_module_name() ), **metadata )
42
43NamingInstance = TraitFactory( NamingInstance )
44
45#-------------------------------------------------------------------------------
46#  'NamingTraitHandler' class:
47#-------------------------------------------------------------------------------
48
49class NamingTraitHandler ( TraitHandler ):
50
51    #---------------------------------------------------------------------------
52    #  Initializes the object:
53    #---------------------------------------------------------------------------
54
55    def __init__ ( self, aClass, or_none, module ):
56        """ Initializes the object.
57        """
58        self.or_none = (or_none != False)
59        self.module  = module
60        self.aClass  = aClass
61        if (aClass is not None) \
62            and (not isinstance( aClass, ( six.string_types, type ) )):
63            self.aClass = aClass.__class__
64
65    def validate ( self, object, name, value ):
66        if isinstance( value, six.string_types ):
67            if value == '':
68                if self.or_none:
69                    return ''
70                else:
71                    self.validate_failed( object, name, value )
72            try:
73                value = self._get_binding_for( value )
74            except:
75                self.validate_failed( object, name, value )
76
77        if isinstance(self.aClass, six.string_types):
78            self.resolve_class( object, name, value )
79
80        if (isinstance( value, Binding ) and
81            ((self.aClass is None) or isinstance( value.obj, self.aClass ))):
82            return value.namespace_name
83        self.validate_failed( object, name, value )
84
85    def info ( self ):
86        aClass = self.aClass
87        if aClass is None:
88            result = 'path'
89        else:
90            if type( aClass ) is not str:
91                aClass = aClass.__name__
92            result = 'path to an instance of ' + class_of( aClass )
93        if self.or_none is None:
94            return result + ' or an empty string'
95        return result
96
97    def validate_failed ( self, object, name, value ):
98        if not isinstance( value, type ):
99            msg = 'class %s' % value.__class__.__name__
100        else:
101            msg = '%s (i.e. %s)' % ( str( type( value ) )[1:-1], repr( value ) )
102        self.error( object, name, msg )
103
104    def get_editor ( self, trait ):
105        if self.editor is None:
106            from traitsui.api import DropEditor
107
108            self.editor = DropEditor( klass    = self.aClass,
109                                      binding  = True,
110                                      readonly = False )
111        return self.editor
112
113    def post_setattr ( self, object, name, value ):
114        other = None
115        if value != '':
116            other = self._get_binding_for( value ).obj
117        object.__dict__[ name + '_' ] = other
118
119    def _get_binding_for ( self, value ):
120
121        result = None
122
123        # FIXME: The following code makes this whole component have a dependency
124        # on envisage, and worse, assumes the use of a particular project
125        # plugin!  This is horrible and should be refactored out, possibly to
126        # a custom sub-class of whoever needs this behavior.
127        try:
128            from envisage import get_application
129            workspace = get_application().service_registry.get_service(
130                            'envisage.project.IWorkspace' )
131            result = workspace.lookup_binding( value )
132        except ImportError:
133            pass
134
135        return result
136
137
138    def resolve_class ( self, object, name, value ):
139        aClass = self.find_class()
140        if aClass is None:
141            self.validate_failed( object, name, value )
142        self.aClass = aClass
143
144        # fixme: The following is quite ugly, because it wants to try and fix
145        # the trait referencing this handler to use the 'fast path' now that the
146        # actual class has been resolved. The problem is finding the trait,
147        # especially in the case of List(Instance('foo')), where the
148        # object.base_trait(...) value is the List trait, not the Instance
149        # trait, so we need to check for this and pull out the List
150        # 'item_trait'. Obviously this does not extend well to other traits
151        # containing nested trait references (Dict?)...
152        trait   = object.base_trait( name )
153        handler = trait.handler
154        if (handler is not self) and hasattr( handler, 'item_trait' ):
155            trait = handler.item_trait
156        trait.validate( self.fast_validate )
157
158    def find_class ( self ):
159        module = self.module
160        aClass = self.aClass
161        col    = aClass.rfind( '.' )
162        if col >= 0:
163            module = aClass[ : col ]
164            aClass = aClass[ col + 1: ]
165        theClass = getattr( sys.modules.get( module ), aClass, None )
166        if (theClass is None) and (col >= 0):
167            try:
168                theClass = getattr( __import__( module ), aClass, None )
169            except:
170                pass
171        return theClass
172
173