Skip to content
This repository has been archived by the owner on May 24, 2021. It is now read-only.

Commit

Permalink
Add support for adding windows to a running session.
Browse files Browse the repository at this point in the history
This commit also adds support for creating top-level windows which are
children of another widget. It also adds a more formal api for batching
actions on a session.
  • Loading branch information
sccolbert committed Jan 23, 2013
1 parent 8e4c3b1 commit c090c0f
Show file tree
Hide file tree
Showing 7 changed files with 265 additions and 90 deletions.
30 changes: 5 additions & 25 deletions enaml/core/include.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from traits.api import List, Instance, Bool

from .declarative import Declarative
from .object import Object, ChildrenEventContext
from .object import Object


class Include(Declarative):
Expand Down Expand Up @@ -47,8 +47,8 @@ def parent_event(self, event):
if self.is_active:
old = event.old
new = event.new
with ChildrenEventContext(new):
with ChildrenEventContext(old):
with new.children_event_context():
with old.children_event_context():
if new is None:
for obj in self.objects:
obj.set_parent(None)
Expand All @@ -67,7 +67,7 @@ def _objects_changed(self, old, new):
if self.is_active:
parent = self.parent
if parent is not None:
with ChildrenEventContext(parent):
with parent.children_event_context():
new_set = set(new)
if self.destroy_old:
for obj in old:
Expand All @@ -79,7 +79,6 @@ def _objects_changed(self, old, new):
obj.set_parent(None)
if new_set:
parent.insert_children(self, self.objects)
self._activate_objects(new_set)

def _objects_items_changed(self, event):
""" Handle the `objects` list changing in-place.
Expand All @@ -93,7 +92,7 @@ def _objects_items_changed(self, event):
if self.is_active:
parent = self.parent
if parent is not None:
with ChildrenEventContext(parent):
with parent.children_event_context():
add_set = set(event.added)
if self.destroy_old:
for obj in event.removed:
Expand All @@ -105,23 +104,4 @@ def _objects_items_changed(self, event):
obj.set_parent(None)
if add_set:
parent.insert_children(self, self.objects)
self._activate_objects(add_set)

def _activate_objects(self, objects):
""" Initialize and activate the given objects.
Parameters
----------
objects : iterable
An iterable of objects which should be initialized and
activated. Objects which are already active are ignored.
"""
for obj in objects:
if obj.is_inactive:
obj.initialize()
session = self.session
for obj in objects:
if obj.is_initialized:
obj.activate(session)

83 changes: 65 additions & 18 deletions enaml/core/messenger.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,62 @@ def equals(self, other):
PublishAttributeNotifier = PublishAttributeNotifier()


class ChildrenChangedTask(object):
""" A task for posting a children changed event to a client.
Instances of this class can be posted to the `batch_action_task`
method of Object to send a 'children_changed' action to a client
object. This task will ensure that new children are initialized
and activated using session object of the provided parent.
"""
def __init__(self, parent, event):
""" Initialize a ChildrenChangedTask.
Parameters
----------
parent : Object
The object to which the children event was posted.
event : ChildrenEvent
The children event posted to the parent.
"""
self._parent = parent
self._event = event

def __call__(self):
""" Create the content dictionary for the task.
This method will also initialize and activate any new objects
which were added to the parent.
"""
event = self._event
content = {}
new_set = set(event.new)
old_set = set(event.old)
added = new_set - old_set
removed = old_set - new_set
for obj in added:
if obj.is_inactive:
obj.initialize()
content['order'] = [
c.object_id for c in event.new if isinstance(c, Messenger)
]
content['removed'] = [
c.object_id for c in removed if isinstance(c, Messenger)
]
content['added'] = [
c.snapshot() for c in added if isinstance(c, Messenger)
]
session = self._parent.session
for obj in added:
if obj.is_initialized:
obj.activate(session)
return content


class Messenger(Declarative):
""" A base class for creating messaging-enabled Enaml objects.
Expand Down Expand Up @@ -202,23 +258,14 @@ def children_event(self, event):
the trait change notification.
"""
# Children events are fired all the time. Only pull for a new
# snapshot if the widget has been fully activated.
if self.is_active:
content = {}
new_set = set(event.new)
old_set = set(event.old)
added = new_set - old_set
removed = old_set - new_set
content['order'] = [
c.object_id for c in event.new if isinstance(c, Messenger)
]
content['removed'] = [
c.object_id for c in removed if isinstance(c, Messenger)
]
content['added'] = [
c.snapshot() for c in added if isinstance(c, Messenger)
]
self.send_action('children_changed', content)
super(Messenger, self).children_event(event)
# Children events are fired all the time during initialization,
# so only batch the children task if the widget is activated.
# The children may not be fully instantiated when this event is
# fired, and they may still be executing their constructor. The
# batched task allows the children to finish initializing before
# their snapshot is taken.
if self.is_active:
task = ChildrenChangedTask(self, event)
self.batch_action_task('children_changed', task)

65 changes: 60 additions & 5 deletions enaml/core/object.py
Original file line number Diff line number Diff line change
Expand Up @@ -313,7 +313,7 @@ def destroy(self):
# the automatic destruction of children is assumed.
parent = self._parent
if parent is None or not parent.is_destroying:
self.send_action('destroy', {})
self.batch_action('destroy', {})
self.state = 'destroying'
self.pre_destroy()
if self._children:
Expand Down Expand Up @@ -387,10 +387,10 @@ def set_parent(self, parent):
if old_parent is not None:
old_kids = old_parent._children
idx = old_kids.index(self)
with ChildrenEventContext(old_parent):
with old_parent.children_event_context():
old_parent._children = old_kids[:idx] + old_kids[idx + 1:]
if parent is not None:
with ChildrenEventContext(parent):
with parent.children_event_context():
parent._children += (self,)

def insert_children(self, before, insert):
Expand Down Expand Up @@ -447,10 +447,10 @@ def insert_children(self, before, insert):
old_kids = old_parent._children
idx = old_kids.index(child)
old_kids = old_kids[:idx] + old_kids[idx + 1:]
with ChildrenEventContext(old_parent):
with old_parent.children_event_context():
old_parent._children = old_kids

with ChildrenEventContext(self):
with self.children_event_context():
self._children = tuple(new)

def parent_event(self, event):
Expand Down Expand Up @@ -492,6 +492,22 @@ def children_event(self, event):
"""
self.trait_property_changed('children', event.old, event.new)

def children_event_context(self):
""" Get a context manager for sending children events.
This method should be called and entered whenever the children
of an object are changed. The returned context manager will
collapse all nested changes into a single aggregate event.
Returns
-------
result : ChildrenEventContext
The context manager which should be entered before changing
the children of the object.
"""
return ChildrenEventContext(self)

#--------------------------------------------------------------------------
# Messaging API
#--------------------------------------------------------------------------
Expand All @@ -514,6 +530,45 @@ def send_action(self, action, content):
if self.is_active:
self._session.send(self.object_id, action, content)

def batch_action(self, action, content):
""" Batch an action to be sent to the client at a later time.
The action will only be batched if the current state of the
object is `active`. Subclasses may reimplement this method
if more control is needed.
Parameters
----------
action : str
The name of the action which the client should perform.
content : dict
The content data for the action.
"""
if self.is_active:
self._session.batch(self.object_id, action, content)

def batch_action_task(self, action, task):
""" Similar to `batch_action` but takes a callable task.
The task will only be batched if the current state of the
object is `active`. Subclasses may reimplement this method
if more control is needed.
Parameters
----------
action : str
The name of the action which the client should perform.
task : callable
A callable which will be invoked at a later time. It must
return the content dictionary for the action.
"""
if self.is_active:
self._session.batch_task(self.object_id, action, task)

def receive_action(self, action, content):
""" Receive an action from the client of this object.
Expand Down
12 changes: 10 additions & 2 deletions enaml/qt/qt_session.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,6 @@ class URLRequest(object):
""" A simple object for making url requests.
"""
__slots__ = ('_session',)

def __init__(self, session):
""" Initialize a URLRequest.
Expand Down Expand Up @@ -285,6 +283,16 @@ def on_message(self, object_id, action, content):
#--------------------------------------------------------------------------
# Action Handlers
#--------------------------------------------------------------------------
def on_action_add_window(self, content):
""" Handle the 'add_window' action from the Enaml session.
"""
window = self.build(content['window'], None)
if window is not None:
self._windows.append(window)
window.initialize()
window.activate()

def on_action_url_reply(self, content):
""" Handle the 'url_reply' action from the Enaml session.
Expand Down
Loading

0 comments on commit c090c0f

Please sign in to comment.