1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165
|
#------------------------------------------------------------------------------
# Copyright (c) 2013-2025, Nucleic Development Team.
#
# Distributed under the terms of the Modified BSD License.
#
# The full license is in the file LICENSE, distributed with this software.
#------------------------------------------------------------------------------
from atom.api import Dict, List, Str, Tuple, Typed
from enaml.application import ScheduledTask, schedule
from enaml.objectdict import ObjectDict
from .declarative import Declarative, d_, observe
from .template import Template
class Tagged(ObjectDict):
""" An empty ObjectDict subclass.
This subclass helps provide more informative error messages by
having a class name which reflects it's used.
"""
__slots__ = ()
def make_tagged(items, tags, startag):
""" Create a Tagged object for the given items.
Parameters
----------
items : list
The list of objects which should be tagged.
tags : tuple
A tuple of string tag names. This should be an empty tuple if
no named tags are available.
startag : str
The star tag name. This should be an empty string if there is
no star tag available.
Returns
-------
result : Tagged
The tagged object for the given items.
"""
if tags and len(tags) > len(items):
msg = 'need more than %d values to unpack'
raise ValueError(msg % len(items))
if tags and not startag and len(items) > len(tags):
raise ValueError('too many values to unpack')
tagged = Tagged()
if tags:
for name, item in zip(tags, items):
tagged[name] = item
if startag:
tagged[startag] = tuple(items[len(tags):])
return tagged
class DynamicTemplate(Declarative):
""" An object which dynamically instantiates a template.
A DynamicTemplate allows a template to be instantiated using the
runtime scope available to RHS expressions.
Creating a DynamicTemplate without a parent is a programming error.
"""
#: The template object to instantiate.
base = d_(Typed(Template))
#: The arguments to pass to the template.
args = d_(Tuple())
#: The tags to apply to the return values of the template. The tags
#: are used as the key names for the 'tagged' ObjectDict.
tags = d_(Tuple(Str()))
#: The tag to apply to overflow return items from the template.
startag = d_(Str())
#: The data keywords to apply to the instantiated items.
data = d_(Dict())
#: The object dictionary which maps tag name to tagged object. This
#: is updated automatically when the template is instantiated.
tagged = Typed(ObjectDict, ())
#: The internal task used to collapse template updates.
_update_task = Typed(ScheduledTask)
#: The internal list of items generated by the template.
_items = List(Declarative)
def initialize(self):
""" A reimplemented initializer.
This method will instantiate the template and initialize the
items for the first time.
"""
self._refresh()
for item in self._items:
item.initialize()
super(DynamicTemplate, self).initialize()
def destroy(self):
""" A reimplemented destructor.
This method will ensure that the instantiated tempalte items are
destroyed and that any potential reference cycles are released.
"""
parent = self.parent
destroy_items = parent is None or not parent.is_destroyed
super(DynamicTemplate, self).destroy()
if destroy_items:
for item in self._items:
if not item.is_destroyed:
item.destroy()
del self.data
del self.tagged
if self._update_task is not None:
self._update_task.unschedule()
del self._update_task
del self._items
#--------------------------------------------------------------------------
# Private API
#--------------------------------------------------------------------------
@observe('base', 'args', 'tags', 'startag', 'data')
def _schedule_refresh(self, change):
""" Schedule an item refresh when the item dependencies change.
"""
if change['type'] == 'update':
if self._update_task is None:
self._update_task = schedule(self._refresh)
def _refresh(self):
""" Refresh the template instantiation.
This method will destroy the old items, build the new items,
and then update the parent object and tagged object.
"""
self._update_task = None
if self.base is not None:
items = self.base(*self.args)(**self.data)
else:
items = []
for old in self._items:
if not old.is_destroyed:
old.destroy()
if len(items) > 0:
self.parent.insert_children(self, items)
self._items = items
self.tagged = make_tagged(items, self.tags, self.startag)
|