diff --git a/IPython/html/static/notebook/js/widget.js b/IPython/html/static/notebook/js/widget.js index e7c667d..94e2b72 100644 --- a/IPython/html/static/notebook/js/widget.js +++ b/IPython/html/static/notebook/js/widget.js @@ -31,6 +31,7 @@ var IPython = (function (IPython) { }; WidgetManager.prototype.init_kernel = function (kernel) { + // connect the kernel, and register message handlers this.kernel = kernel; var msg_types = ['widget_create', 'widget_destroy', 'widget_update']; for (var i = 0; i < msg_types.length; i++) { @@ -44,6 +45,20 @@ var IPython = (function (IPython) { this.widget_types[widget_type] = constructor; }; + WidgetManager.prototype.register_widget = function (widget) { + // Register a widget in the mapping + this.widgets[widget.widget_id] = widget; + widget.kernel = this.kernel; + return widget.widget_id; + }; + + WidgetManager.prototype.unregister_widget = function (widget_id) { + // Remove a widget from the mapping + delete this.widgets[widget_id]; + }; + + // widget message handlers + WidgetManager.prototype.widget_create = function (msg) { var content = msg.content; var constructor = this.widget_types[content.widget_type]; @@ -52,7 +67,10 @@ var IPython = (function (IPython) { console.log("Available widget types are: ", this.widget_types); return; } - var widget = new constructor(this.kernel, content); + var widget = new constructor(content.widget_id); + this.register_widget(widget); + widget.handle_create(content.data); + this.widgets[content.widget_id] = widget; }; @@ -79,39 +97,48 @@ var IPython = (function (IPython) { // Widget base class //----------------------------------------------------------------------- - var Widget = function (kernel, content) { - this.kernel = kernel; - if (!content) return; - this.widget_id = content.widget_id; - this.handle_create(content.data); - }; - - Widget.prototype.handle_create = function (data) { + var Widget = function (widget_id) { + this.widget_id = widget_id; + this.widget_type = 'widget'; }; - Widget.prototype.handle_update = function (data) { - }; - - Widget.prototype.handle_destroy = function (data) { + // methods for sending messages + Widget.prototype.create = function (data) { + var content = { + widget_id : this.widget_id, + widget_type : this.widget_type, + data : data || {}, + }; + this.kernel.send_shell_message("widget_create", content); }; Widget.prototype.update = function (data) { var content = { widget_id : this.widget_id, - data : data, + data : data || {}, }; this.kernel.send_shell_message("widget_update", content); }; - Widget.prototype.destroy = function (data) { var content = { widget_id : this.widget_id, - data : data, + data : data || {}, }; this.kernel.send_shell_message("widget_destroy", content); }; + // methods for handling incoming messages + + Widget.prototype.handle_create = function (data) { + }; + + Widget.prototype.handle_update = function (data) { + }; + + Widget.prototype.handle_destroy = function (data) { + }; + IPython.WidgetManager = WidgetManager; IPython.Widget = Widget; diff --git a/IPython/kernel/widgets/manager.py b/IPython/kernel/widgets/manager.py index 0a2ca46..c0fc995 100644 --- a/IPython/kernel/widgets/manager.py +++ b/IPython/kernel/widgets/manager.py @@ -11,11 +11,11 @@ # Imports #----------------------------------------------------------------------------- -from weakref import ref - from IPython.config import LoggingConfigurable from IPython.core.prompts import LazyEvaluate from IPython.core.getipython import get_ipython + +from IPython.utils.importstring import import_item from IPython.utils.traitlets import Instance, Unicode, Dict, Any #----------------------------------------------------------------------------- @@ -30,6 +30,7 @@ def lazy_keys(dikt): """ return LazyEvaluate(lambda d: list(d.keys())) + class WidgetManager(LoggingConfigurable): """Manager for Widgets in the Kernel""" @@ -46,25 +47,32 @@ class WidgetManager(LoggingConfigurable): return self.shell.parent.session widgets = Dict() + widget_types = Dict() # Public APIs + def register_widget_type(self, widget_type, constructor): + """Register a constructor for a given widget_type + + constructor can be a Widget class or an importstring for a Widget class. + """ + if isinstance(constructor, basestring): + constructor = import_item(constructor) + + self.widget_types[widget_type] = constructor + def register_widget(self, widget): """Register a new widget""" - self.widgets[widget.widget_id] = ref(widget) + widget_id = widget.widget_id widget.shell = self.shell widget.iopub_socket = self.iopub_socket - widget.create() - return widget.widget_id + self.widgets[widget_id] = widget + return widget_id def unregister_widget(self, widget_id): """Unregister a widget, and destroy its counterpart""" # unlike get_widget, this should raise a KeyError - widget_ref = self.widgets.pop(widget_id) - widget = widget_ref() - if widget is None: - # already destroyed, nothing to do - return + widget = self.widgets.pop(widget_id) widget.destroy() def get_widget(self, widget_id): @@ -80,16 +88,27 @@ class WidgetManager(LoggingConfigurable): self.log.debug("Current widgets: %s", lazy_keys(self.widgets)) return # call, because we store weakrefs - widget = self.widgets[widget_id]() - if widget is None: - self.log.error("Widget %s has been removed", widget_id) - del self.widgets[widget_id] - self.log.debug("Current widgets: %s", lazy_keys(self.widgets)) - return + widget = self.widgets[widget_id] return widget # Message handlers + def widget_create(self, stream, ident, msg): + """Handler for widget_update messages""" + content = msg['content'] + widget_id = content['widget_id'] + widget_type = content['widget_type'] + constructor = self.widget_types.get(widget_type, None) + if constructor is None: + self.log.error("No such widget_type registered: %s", widget_type) + return + widget = constructor(widget_id=widget_id, + shell=self.shell, + iopub_socket=self.iopub_socket, + _create_data=content['data'], + ) + self.register_widget(widget) + def widget_update(self, stream, ident, msg): """Handler for widget_update messages""" content = msg['content'] @@ -110,6 +129,6 @@ class WidgetManager(LoggingConfigurable): return widget.handle_destroy(content['data']) del self.widgets[widget_id] - + __all__ = ['WidgetManager'] diff --git a/IPython/kernel/widgets/widget.py b/IPython/kernel/widgets/widget.py index f258b69..8532419 100644 --- a/IPython/kernel/widgets/widget.py +++ b/IPython/kernel/widgets/widget.py @@ -13,6 +13,7 @@ import uuid +from IPython.core.getipython import get_ipython from IPython.config import LoggingConfigurable from IPython.utils.traitlets import Instance, Unicode, Bytes, Bool, Dict, Any @@ -47,6 +48,18 @@ class Widget(LoggingConfigurable): def _widget_id_default(self): return uuid.uuid4().hex + primary = Bool(False, help="Am I the primary or secondary Widget?") + + def __init__(self, **kwargs): + super(Widget, self).__init__(**kwargs) + get_ipython().widget_manager.register_widget(self) + if self.primary: + # I am primary, create my peer + self.create() + else: + # I am secondary, handle creation + self.handle_create(self._create_data) + def _publish_msg(self, msg_type, data=None, **keys): """Helper for sending a widget message on IOPub""" data = {} if data is None else data @@ -61,16 +74,20 @@ class Widget(LoggingConfigurable): # publishing messages - def create(self): + def create(self, data=None): """Create the frontend-side version of this widget""" - self._publish_msg('widget_create', self._create_data, widget_type = self.widget_type) + if data is None: + data = self._create_data + self._publish_msg('widget_create', data, widget_type=self.widget_type) - def destroy(self): + def destroy(self, data=None): """Destroy the frontend-side version of this widget""" if self._destroyed: # only destroy once return - self._publish_msg('widget_destroy', self._destroy_data) + if data is None: + data = self._destroy_data + self._publish_msg('widget_destroy', data) self._destroyed = True def update(self, data=None): @@ -79,6 +96,10 @@ class Widget(LoggingConfigurable): # handling of incoming messages + def handle_create(self, data): + """Handle a widget_create message""" + self.log.debug("handle_create %s", data) + def handle_destroy(self, data): """Handle a widget_destroy message""" self.log.debug("handle_destroy %s", data) diff --git a/IPython/kernel/zmq/ipkernel.py b/IPython/kernel/zmq/ipkernel.py index 97d121f..3f53dfe 100755 --- a/IPython/kernel/zmq/ipkernel.py +++ b/IPython/kernel/zmq/ipkernel.py @@ -168,7 +168,7 @@ class Kernel(Configurable): for msg_type in msg_types: self.shell_handlers[msg_type] = getattr(self, msg_type) - widget_msg_types = [ 'widget_update', 'widget_destroy' ] + widget_msg_types = [ 'widget_create', 'widget_update', 'widget_destroy' ] widget_manager = self.shell.widget_manager for msg_type in widget_msg_types: self.shell_handlers[msg_type] = getattr(widget_manager, msg_type)