1#-------------------------------------------------------------------------------
2#
3#  An abstract base class implementation of the ITemplateDataNameItem interface
4#  that looks for all specified values in its input context or optionally any of
5#  its sub-contexts and outputs a context containing all such values found.
6#
7#  Written by: David C. Morrill
8#
9#  Date: 07/29/2007
10#
11#  (c) Copyright 2007 by Enthought, Inc.
12#
13#-------------------------------------------------------------------------------
14
15""" An abstract base class implementation of the ITemplateDataNameItem interface
16    that looks for all specified values in its input context or optionally any
17    of its sub-contexts and outputs a context containing all such values found.
18"""
19
20#-------------------------------------------------------------------------------
21#  Imports:
22#-------------------------------------------------------------------------------
23
24from traits.api \
25    import HasPrivateTraits, Instance, Property, provides
26
27from apptools.template.template_traits \
28    import TBool
29
30from apptools.template.itemplate_data_context \
31    import ITemplateDataContext
32
33from apptools.template.itemplate_data_name_item \
34    import ITemplateDataNameItem
35
36from apptools.template.template_impl \
37    import Template
38
39from .template_data_context \
40    import TemplateDataContext
41
42from .helper \
43    import path_for
44
45#-------------------------------------------------------------------------------
46#  'AnyDataNameItem' class:
47#-------------------------------------------------------------------------------
48
49class AnyDataNameItem ( Template ):
50    """ An abstract base class implementation of the ITemplateDataNameItem
51        interface that looks for all specified values in its input context or
52        optionally any of its sub-contexts and outputs a context containing all
53        such values found.
54    """
55
56    implements ( ITemplateDataNameItem )
57
58    #-- 'ITemplateDataNameItem' Interface Implementation -----------------------
59
60    # The data context which this data name item should match against:
61    input_data_context = Instance( ITemplateDataContext )
62
63    # The data context containing the data values and/or contexts this data
64    # name item matches:
65    output_data_context = Instance( ITemplateDataContext )
66
67    # The ITemplateChoice instance representing the current settings of the
68    # data name item. This value must be read/write, and must be overridden by
69    # sublasses.
70    data_name_item_choice = Property
71
72    # The alternative choices the user has for the data name item settings for
73    # the current input data context. The list may be empty, in which case the
74    # user cannot change the settings of the data name item. This value can be
75    # read only, and must be overridden by subclasses.
76    data_name_item_choices = Property
77
78    #-- Public Traits ----------------------------------------------------------
79
80    # Should all sub-contexts be included in the search:
81    recursive = TBool( False )
82
83    # Should included sub-contexts be flattened into a single context?
84    flatten = TBool( False )
85
86    #-- Private Traits ---------------------------------------------------------
87
88    # The current recursive setting:
89    current_recursive = TBool( False )
90
91    # The current input data context:
92    current_input_data_context = Property
93
94    #-- Abstract Methods (Must be overridden in subclasses) --------------------
95
96    def filter ( self, name, value ):
97        """ Returns **True** if the specified context data *name* and *value*
98            should be included in the output context; and **False** otherwise.
99        """
100        raise NotImplementedError
101
102    #-- Property Implementations -----------------------------------------------
103
104    def _get_data_name_item_choice ( self ):
105        raise NotImplementedError
106
107    def _set_data_name_item_choice ( self, value ):
108        raise NotImplementedError
109
110    def _get_data_name_item_choices ( self ):
111        raise NotImplementedError
112
113    def _get_current_input_data_context ( self ):
114        return self.input_data_context
115
116    #-- Trait Event Handlers ---------------------------------------------------
117
118    def _recursive_changed ( self, value ):
119        """ Handles the primary recursive setting being changed.
120        """
121        self.current_recursive = value
122
123    def _input_data_context_changed ( self ):
124        """ Handles the 'input_data_context' trait being changed.
125        """
126        self.inputs_changed()
127
128    #-- Private Methods --------------------------------------------------------
129
130    def inputs_changed ( self ):
131        """ Handles any input value being changed. This method should be called
132            by subclasses when any of their input values change.
133        """
134        output_context = None
135        input_context  = self.current_input_data_context
136        if input_context is not None:
137            values = {}
138
139            if self.current_recursive:
140                if self.flatten:
141                    self._add_context( input_context, values )
142                else:
143                    self._copy_context( input_context, values )
144            else:
145                self._add_values(  input_context, values, '' )
146
147            if len( values ) > 0:
148                output_context = TemplateDataContext(
149                    data_context_path = input_context.data_context_path,
150                    data_context_name = input_context.data_context_name,
151                    values            = values )
152
153        self.output_data_context = output_context
154
155    def _add_values ( self, input_context, values, path = '' ):
156        """ Adds all of the matching values in the specified *input_context* to
157            the specified *values* dictionary.
158        """
159        # Filter each name/value in the current input context to see if it
160        # should be added to the output values:
161        filter = self.filter
162        gdcv   = input_context.get_data_context_value
163        for name in input_context.data_context_values:
164            value = gdcv( name )
165            if self.filter( name, value ):
166                values[ path_for( path, name ) ] = value
167
168    def _add_context ( self, input_context, values, path = '' ):
169        """ Adds all of the matching values in the specified *input_context* to
170            the specified *output_context*, and then applies itself recursively
171            to all contexts contained in the specified *input_context*.
172        """
173        # Add all of the filtered values in the specified input context:
174        self._add_values( input_context, values, path )
175
176        # Now process all of the input context's sub-contexts:
177        gdc = input_context.get_data_context
178        for name in input_context.data_contexts:
179            self._add_context( gdc( name ), values, path_for( path,
180                                            input_context.data_context_name ) )
181
182    def _copy_context ( self, input_context ):
183        """ Clone the input context so that the result only contains values and
184            contexts which contain valid values and are not empty.
185        """
186        values   = {}
187        contexts = {}
188
189        # Add all of the filtered values in the specified input context:
190        self._add_values( input_context, values )
191
192        # Now process all of the input context's sub-contexts:
193        gdc = input_context.get_data_context
194        for name in input_context.data_contexts:
195            context = self._copy_context( gdc( name ) )
196            if context is not None:
197                contexts[ name ] = context
198
199        if (len( values ) == 0) and (len( contexts ) == 0):
200            return None
201
202        return TemplateDataContext(
203                    data_context_path = input_context.data_context_path,
204                    data_context_name = input_context.data_context_name,
205                    values            = values,
206                    contexts          = contexts )
207
208