##// END OF EJS Templates
Rebase fixes.
Jonathan Frederic -
Show More
@@ -1,268 +1,268
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 define([
4 define([
5 "underscore",
5 "underscore",
6 "backbone",
6 "backbone",
7 "jquery",
7 "jquery",
8 "base/js/namespace"
8 "base/js/namespace"
9 ], function (_, Backbone, $, IPython) {
9 ], function (_, Backbone, $, IPython) {
10 "use strict";
10 "use strict";
11 //--------------------------------------------------------------------
11 //--------------------------------------------------------------------
12 // WidgetManager class
12 // WidgetManager class
13 //--------------------------------------------------------------------
13 //--------------------------------------------------------------------
14 var WidgetManager = function (comm_manager, notebook) {
14 var WidgetManager = function (comm_manager, notebook) {
15 // Public constructor
15 // Public constructor
16 WidgetManager._managers.push(this);
16 WidgetManager._managers.push(this);
17
17
18 // Attach a comm manager to the
18 // Attach a comm manager to the
19 this.keyboard_manager = notebook.keyboard_manager;
19 this.keyboard_manager = notebook.keyboard_manager;
20 this.notebook = notebook;
20 this.notebook = notebook;
21 this.comm_manager = comm_manager;
21 this.comm_manager = comm_manager;
22 this._models = {}; /* Dictionary of model ids and model instances */
22 this._models = {}; /* Dictionary of model ids and model instances */
23
23
24 // Register with the comm manager.
24 // Register with the comm manager.
25 this.comm_manager.register_target('ipython.widget', $.proxy(this._handle_comm_open, this));
25 this.comm_manager.register_target('ipython.widget', $.proxy(this._handle_comm_open, this));
26 };
26 };
27
27
28 //--------------------------------------------------------------------
28 //--------------------------------------------------------------------
29 // Class level
29 // Class level
30 //--------------------------------------------------------------------
30 //--------------------------------------------------------------------
31 WidgetManager._model_types = {}; /* Dictionary of model type names (target_name) and model types. */
31 WidgetManager._model_types = {}; /* Dictionary of model type names (target_name) and model types. */
32 WidgetManager._view_types = {}; /* Dictionary of view names and view types. */
32 WidgetManager._view_types = {}; /* Dictionary of view names and view types. */
33 WidgetManager._managers = []; /* List of widget managers */
33 WidgetManager._managers = []; /* List of widget managers */
34
34
35 WidgetManager.register_widget_model = function (model_name, model_type) {
35 WidgetManager.register_widget_model = function (model_name, model_type) {
36 // Registers a widget model by name.
36 // Registers a widget model by name.
37 WidgetManager._model_types[model_name] = model_type;
37 WidgetManager._model_types[model_name] = model_type;
38 };
38 };
39
39
40 WidgetManager.register_widget_view = function (view_name, view_type) {
40 WidgetManager.register_widget_view = function (view_name, view_type) {
41 // Registers a widget view by name.
41 // Registers a widget view by name.
42 WidgetManager._view_types[view_name] = view_type;
42 WidgetManager._view_types[view_name] = view_type;
43 };
43 };
44
44
45 //--------------------------------------------------------------------
45 //--------------------------------------------------------------------
46 // Instance level
46 // Instance level
47 //--------------------------------------------------------------------
47 //--------------------------------------------------------------------
48 WidgetManager.prototype.display_view = function(msg, model) {
48 WidgetManager.prototype.display_view = function(msg, model) {
49 // Displays a view for a particular model.
49 // Displays a view for a particular model.
50 var cell = this.get_msg_cell(msg.parent_header.msg_id);
50 var cell = this.get_msg_cell(msg.parent_header.msg_id);
51 if (cell === null) {
51 if (cell === null) {
52 console.log("Could not determine where the display" +
52 console.log("Could not determine where the display" +
53 " message was from. Widget will not be displayed");
53 " message was from. Widget will not be displayed");
54 } else {
54 } else {
55 var that = this;
55 var that = this;
56 this.create_view(model, {cell: cell, callback: function(view) {
56 this.create_view(model, {cell: cell, callback: function(view) {
57 that._handle_display_view(view);
57 that._handle_display_view(view);
58 if (cell.widget_subarea) {
58 if (cell.widget_subarea) {
59 cell.widget_subarea.append(view.$el);
59 cell.widget_subarea.append(view.$el);
60 }
60 }
61 view.trigger('displayed');
61 view.trigger('displayed');
62 }});
62 }});
63 }
63 }
64 };
64 };
65
65
66 WidgetManager.prototype._handle_display_view = function (view) {
66 WidgetManager.prototype._handle_display_view = function (view) {
67 // Have the IPython keyboard manager disable its event
67 // Have the IPython keyboard manager disable its event
68 // handling so the widget can capture keyboard input.
68 // handling so the widget can capture keyboard input.
69 // Note, this is only done on the outer most widgets.
69 // Note, this is only done on the outer most widgets.
70 if (this.keyboard_manager) {
70 if (this.keyboard_manager) {
71 this.keyboard_manager.register_events(view.$el);
71 this.keyboard_manager.register_events(view.$el);
72
72
73 if (view.additional_elements) {
73 if (view.additional_elements) {
74 for (var i = 0; i < view.additional_elements.length; i++) {
74 for (var i = 0; i < view.additional_elements.length; i++) {
75 this.keyboard_manager.register_events(view.additional_elements[i]);
75 this.keyboard_manager.register_events(view.additional_elements[i]);
76 }
76 }
77 }
77 }
78 }
78 }
79 };
79 };
80
80
81
81
82 WidgetManager.prototype.create_view = function(model, options) {
82 WidgetManager.prototype.create_view = function(model, options) {
83 // Creates a view for a particular model.
83 // Creates a view for a particular model.
84
84
85 var view_name = model.get('_view_name');
85 var view_name = model.get('_view_name');
86 var view_mod = model.get('_view_module');
86 var view_mod = model.get('_view_module');
87 var errback = options.errback || function(err) {console.log(err);};
87 var errback = options.errback || function(err) {console.log(err);};
88
88
89 var instantiate_view = function(ViewType) {
89 var instantiate_view = function(ViewType) {
90 if (ViewType) {
90 if (ViewType) {
91 // If a view is passed into the method, use that view's cell as
91 // If a view is passed into the method, use that view's cell as
92 // the cell for the view that is created.
92 // the cell for the view that is created.
93 options = options || {};
93 options = options || {};
94 if (options.parent !== undefined) {
94 if (options.parent !== undefined) {
95 options.cell = options.parent.options.cell;
95 options.cell = options.parent.options.cell;
96 }
96 }
97
97
98 // Create and render the view...
98 // Create and render the view...
99 var parameters = {model: model, options: options};
99 var parameters = {model: model, options: options};
100 var view = new ViewType(parameters);
100 var view = new ViewType(parameters);
101 view.render();
101 view.render();
102 model.on('destroy', view.remove, view);
102 model.on('destroy', view.remove, view);
103 options.callback(view);
103 options.callback(view);
104 } else {
104 } else {
105 errback({unknown_view: true, view_name: view_name,
105 errback({unknown_view: true, view_name: view_name,
106 view_module: view_mod});
106 view_module: view_mod});
107 }
107 }
108 };
108 };
109
109
110 if (view_mod) {
110 if (view_mod) {
111 require([view_mod], function(module) {
111 require([view_mod], function(module) {
112 instantiate_view(module[view_name]);
112 instantiate_view(module[view_name]);
113 }, errback);
113 }, errback);
114 } else {
114 } else {
115 instantiate_view(WidgetManager._view_types[view_name]);
115 instantiate_view(WidgetManager._view_types[view_name]);
116 }
116 }
117 };
117 };
118
118
119 WidgetManager.prototype.get_msg_cell = function (msg_id) {
119 WidgetManager.prototype.get_msg_cell = function (msg_id) {
120 var cell = null;
120 var cell = null;
121 // First, check to see if the msg was triggered by cell execution.
121 // First, check to see if the msg was triggered by cell execution.
122 if (this.notebook) {
122 if (this.notebook) {
123 cell = this.notebook.get_msg_cell(msg_id);
123 cell = this.notebook.get_msg_cell(msg_id);
124 }
124 }
125 if (cell !== null) {
125 if (cell !== null) {
126 return cell;
126 return cell;
127 }
127 }
128 // Second, check to see if a get_cell callback was defined
128 // Second, check to see if a get_cell callback was defined
129 // for the message. get_cell callbacks are registered for
129 // for the message. get_cell callbacks are registered for
130 // widget messages, so this block is actually checking to see if the
130 // widget messages, so this block is actually checking to see if the
131 // message was triggered by a widget.
131 // message was triggered by a widget.
132 var kernel = this.comm_manager.kernel;
132 var kernel = this.comm_manager.kernel;
133 if (kernel) {
133 if (kernel) {
134 var callbacks = kernel.get_callbacks_for_msg(msg_id);
134 var callbacks = kernel.get_callbacks_for_msg(msg_id);
135 if (callbacks && callbacks.iopub &&
135 if (callbacks && callbacks.iopub &&
136 callbacks.iopub.get_cell !== undefined) {
136 callbacks.iopub.get_cell !== undefined) {
137 return callbacks.iopub.get_cell();
137 return callbacks.iopub.get_cell();
138 }
138 }
139 }
139 }
140
140
141 // Not triggered by a cell or widget (no get_cell callback
141 // Not triggered by a cell or widget (no get_cell callback
142 // exists).
142 // exists).
143 return null;
143 return null;
144 };
144 };
145
145
146 WidgetManager.prototype.callbacks = function (view) {
146 WidgetManager.prototype.callbacks = function (view) {
147 // callback handlers specific a view
147 // callback handlers specific a view
148 var callbacks = {};
148 var callbacks = {};
149 if (view && view.options.cell) {
149 if (view && view.options.cell) {
150
150
151 // Try to get output handlers
151 // Try to get output handlers
152 var cell = view.options.cell;
152 var cell = view.options.cell;
153 var handle_output = null;
153 var handle_output = null;
154 var handle_clear_output = null;
154 var handle_clear_output = null;
155 if (cell.output_area) {
155 if (cell.output_area) {
156 handle_output = $.proxy(cell.output_area.handle_output, cell.output_area);
156 handle_output = $.proxy(cell.output_area.handle_output, cell.output_area);
157 handle_clear_output = $.proxy(cell.output_area.handle_clear_output, cell.output_area);
157 handle_clear_output = $.proxy(cell.output_area.handle_clear_output, cell.output_area);
158 }
158 }
159
159
160 // Create callback dict using what is known
160 // Create callback dict using what is known
161 var that = this;
161 var that = this;
162 callbacks = {
162 callbacks = {
163 iopub : {
163 iopub : {
164 output : handle_output,
164 output : handle_output,
165 clear_output : handle_clear_output,
165 clear_output : handle_clear_output,
166
166
167 // Special function only registered by widget messages.
167 // Special function only registered by widget messages.
168 // Allows us to get the cell for a message so we know
168 // Allows us to get the cell for a message so we know
169 // where to add widgets if the code requires it.
169 // where to add widgets if the code requires it.
170 get_cell : function () {
170 get_cell : function () {
171 return cell;
171 return cell;
172 },
172 },
173 },
173 },
174 };
174 };
175 }
175 }
176 return callbacks;
176 return callbacks;
177 };
177 };
178
178
179 WidgetManager.prototype.get_model = function (model_id) {
179 WidgetManager.prototype.get_model = function (model_id) {
180 // Look-up a model instance by its id.
180 // Look-up a model instance by its id.
181 var model = this._models[model_id];
181 var model = this._models[model_id];
182 if (model !== undefined && model.id == model_id) {
182 if (model !== undefined && model.id == model_id) {
183 return model;
183 return model;
184 }
184 }
185 return null;
185 return null;
186 };
186 };
187
187
188 WidgetManager.prototype._handle_comm_open = function (comm, msg) {
188 WidgetManager.prototype._handle_comm_open = function (comm, msg) {
189 // Handle when a comm is opened.
189 // Handle when a comm is opened.
190 return this._create_model({model_name: msg.content.data.target_name, comm: comm});
190 return this._create_model({model_name: msg.content.data.model_name, comm: comm});
191 };
191 };
192
192
193 WidgetManager.prototype.create_model = function (model_name, target_name, init_state_callback) {
193 WidgetManager.prototype.create_model = function (model_name, target_name, init_state_callback) {
194 // Create and return a new widget model.
194 // Create and return a new widget model.
195 //
195 //
196 // Parameters
196 // Parameters
197 // ----------
197 // ----------
198 // model_name: string
198 // model_name: string
199 // Target name of the widget model to create.
199 // Target name of the widget model to create.
200 // target_name: string
200 // target_name: string
201 // Target name of the widget in the back-end.
201 // Target name of the widget in the back-end.
202 // init_state_callback: (optional) callback
202 // init_state_callback: (optional) callback
203 // Called when the first state push from the back-end is
203 // Called when the first state push from the back-end is
204 // recieved.
204 // recieved.
205 return this._create_model({
205 return this._create_model({
206 model_name: model_name,
206 model_name: model_name,
207 target_name: target_name,
207 target_name: target_name,
208 init_state_callback: init_state_callback});
208 init_state_callback: init_state_callback});
209 };
209 };
210
210
211 WidgetManager.prototype._create_model = function (options) {
211 WidgetManager.prototype._create_model = function (options) {
212 // Create and return a new widget model.
212 // Create and return a new widget model.
213 //
213 //
214 // Parameters
214 // Parameters
215 // ----------
215 // ----------
216 // options: dictionary
216 // options: dictionary
217 // Dictionary of options with the following contents:
217 // Dictionary of options with the following contents:
218 // model_name: string
218 // model_name: string
219 // Target name of the widget model to create.
219 // Target name of the widget model to create.
220 // target_name: (optional) string
220 // target_name: (optional) string
221 // Target name of the widget in the back-end.
221 // Target name of the widget in the back-end.
222 // comm: (optional) Comm
222 // comm: (optional) Comm
223 // init_state_callback: (optional) callback
223 // init_state_callback: (optional) callback
224 // Called when the first state push from the back-end is
224 // Called when the first state push from the back-end is
225 // recieved.
225 // recieved.
226
226
227 // Create a comm if it wasn't provided.
227 // Create a comm if it wasn't provided.
228 var comm = options.comm;
228 var comm = options.comm;
229 if (!comm) {
229 if (!comm) {
230 comm = this.comm_manager.new_comm('ipython.widget', {'target_name': options.target_name});
230 comm = this.comm_manager.new_comm('ipython.widget', {'target_name': options.target_name});
231 }
231 }
232
232
233 // Create and return a new model that is connected to the comm.
233 // Create and return a new model that is connected to the comm.
234 var that = this;
234 var that = this;
235
235
236 var instantiate_model = function(ModelType) {
236 var instantiate_model = function(ModelType) {
237 var model_id = comm.comm_id;
237 var model_id = comm.comm_id;
238 var widget_model = new ModelType(that, model_id, comm, options.init_state_callback);
238 var widget_model = new ModelType(that, model_id, comm, options.init_state_callback);
239 widget_model.on('comm:close', function () {
239 widget_model.on('comm:close', function () {
240 delete that._models[model_id];
240 delete that._models[model_id];
241 });
241 });
242 that._models[model_id] = widget_model;
242 that._models[model_id] = widget_model;
243 };
243 };
244
244
245 var widget_type_name = msg.content.data.model_name;
245 var widget_type_name = msg.content.data.model_name;
246 var widget_module = msg.content.data.model_module;
246 var widget_module = msg.content.data.model_module;
247
247
248 if (widget_module) {
248 if (widget_module) {
249 // Load the module containing the widget model
249 // Load the module containing the widget model
250 require([widget_module], function(mod) {
250 require([widget_module], function(mod) {
251 if (mod[widget_type_name]) {
251 if (mod[widget_type_name]) {
252 instantiate_model(mod[widget_type_name]);
252 instantiate_model(mod[widget_type_name]);
253 } else {
253 } else {
254 console.log("Error creating widget model: " + widget_type_name
254 console.log("Error creating widget model: " + widget_type_name
255 + " not found in " + widget_module);
255 + " not found in " + widget_module);
256 }
256 }
257 }, function(err) { console.log(err); });
257 }, function(err) { console.log(err); });
258 } else {
258 } else {
259 // No module specified, load from the global models registry
259 // No module specified, load from the global models registry
260 instantiate_model(WidgetManager._model_types[widget_type_name]);
260 instantiate_model(WidgetManager._model_types[widget_type_name]);
261 }
261 }
262 };
262 };
263
263
264 // Backwards compatability.
264 // Backwards compatability.
265 IPython.WidgetManager = WidgetManager;
265 IPython.WidgetManager = WidgetManager;
266
266
267 return {'WidgetManager': WidgetManager};
267 return {'WidgetManager': WidgetManager};
268 });
268 });
@@ -1,470 +1,470
1 """Base Widget class. Allows user to create widgets in the back-end that render
1 """Base Widget class. Allows user to create widgets in the back-end that render
2 in the IPython notebook front-end.
2 in the IPython notebook front-end.
3 """
3 """
4 #-----------------------------------------------------------------------------
4 #-----------------------------------------------------------------------------
5 # Copyright (c) 2013, the IPython Development Team.
5 # Copyright (c) 2013, the IPython Development Team.
6 #
6 #
7 # Distributed under the terms of the Modified BSD License.
7 # Distributed under the terms of the Modified BSD License.
8 #
8 #
9 # The full license is in the file COPYING.txt, distributed with this software.
9 # The full license is in the file COPYING.txt, distributed with this software.
10 #-----------------------------------------------------------------------------
10 #-----------------------------------------------------------------------------
11
11
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13 # Imports
13 # Imports
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15 from contextlib import contextmanager
15 from contextlib import contextmanager
16 import collections
16 import collections
17
17
18 from IPython.core.getipython import get_ipython
18 from IPython.core.getipython import get_ipython
19 from IPython.kernel.comm import Comm
19 from IPython.kernel.comm import Comm
20 from IPython.config import LoggingConfigurable
20 from IPython.config import LoggingConfigurable
21 from IPython.utils.importstring import import_item
21 from IPython.utils.importstring import import_item
22 from IPython.utils.traitlets import Unicode, Dict, Instance, Bool, List, \
22 from IPython.utils.traitlets import Unicode, Dict, Instance, Bool, List, \
23 CaselessStrEnum, Tuple, CUnicode, Int, Set
23 CaselessStrEnum, Tuple, CUnicode, Int, Set
24 from IPython.utils.py3compat import string_types
24 from IPython.utils.py3compat import string_types
25
25
26 #-----------------------------------------------------------------------------
26 #-----------------------------------------------------------------------------
27 # Classes
27 # Classes
28 #-----------------------------------------------------------------------------
28 #-----------------------------------------------------------------------------
29 class CallbackDispatcher(LoggingConfigurable):
29 class CallbackDispatcher(LoggingConfigurable):
30 """A structure for registering and running callbacks"""
30 """A structure for registering and running callbacks"""
31 callbacks = List()
31 callbacks = List()
32
32
33 def __call__(self, *args, **kwargs):
33 def __call__(self, *args, **kwargs):
34 """Call all of the registered callbacks."""
34 """Call all of the registered callbacks."""
35 value = None
35 value = None
36 for callback in self.callbacks:
36 for callback in self.callbacks:
37 try:
37 try:
38 local_value = callback(*args, **kwargs)
38 local_value = callback(*args, **kwargs)
39 except Exception as e:
39 except Exception as e:
40 ip = get_ipython()
40 ip = get_ipython()
41 if ip is None:
41 if ip is None:
42 self.log.warn("Exception in callback %s: %s", callback, e, exc_info=True)
42 self.log.warn("Exception in callback %s: %s", callback, e, exc_info=True)
43 else:
43 else:
44 ip.showtraceback()
44 ip.showtraceback()
45 else:
45 else:
46 value = local_value if local_value is not None else value
46 value = local_value if local_value is not None else value
47 return value
47 return value
48
48
49 def register_callback(self, callback, remove=False):
49 def register_callback(self, callback, remove=False):
50 """(Un)Register a callback
50 """(Un)Register a callback
51
51
52 Parameters
52 Parameters
53 ----------
53 ----------
54 callback: method handle
54 callback: method handle
55 Method to be registered or unregistered.
55 Method to be registered or unregistered.
56 remove=False: bool
56 remove=False: bool
57 Whether to unregister the callback."""
57 Whether to unregister the callback."""
58
58
59 # (Un)Register the callback.
59 # (Un)Register the callback.
60 if remove and callback in self.callbacks:
60 if remove and callback in self.callbacks:
61 self.callbacks.remove(callback)
61 self.callbacks.remove(callback)
62 elif not remove and callback not in self.callbacks:
62 elif not remove and callback not in self.callbacks:
63 self.callbacks.append(callback)
63 self.callbacks.append(callback)
64
64
65 def _show_traceback(method):
65 def _show_traceback(method):
66 """decorator for showing tracebacks in IPython"""
66 """decorator for showing tracebacks in IPython"""
67 def m(self, *args, **kwargs):
67 def m(self, *args, **kwargs):
68 try:
68 try:
69 return(method(self, *args, **kwargs))
69 return(method(self, *args, **kwargs))
70 except Exception as e:
70 except Exception as e:
71 ip = get_ipython()
71 ip = get_ipython()
72 if ip is None:
72 if ip is None:
73 self.log.warn("Exception in widget method %s: %s", method, e, exc_info=True)
73 self.log.warn("Exception in widget method %s: %s", method, e, exc_info=True)
74 else:
74 else:
75 ip.showtraceback()
75 ip.showtraceback()
76 return m
76 return m
77
77
78 class Widget(LoggingConfigurable):
78 class Widget(LoggingConfigurable):
79 #-------------------------------------------------------------------------
79 #-------------------------------------------------------------------------
80 # Class attributes
80 # Class attributes
81 #-------------------------------------------------------------------------
81 #-------------------------------------------------------------------------
82 _widget_construction_callback = None
82 _widget_construction_callback = None
83 widgets = {}
83 widgets = {}
84
84
85 @staticmethod
85 @staticmethod
86 def on_widget_constructed(callback):
86 def on_widget_constructed(callback):
87 """Registers a callback to be called when a widget is constructed.
87 """Registers a callback to be called when a widget is constructed.
88
88
89 The callback must have the following signature:
89 The callback must have the following signature:
90 callback(widget)"""
90 callback(widget)"""
91 Widget._widget_construction_callback = callback
91 Widget._widget_construction_callback = callback
92
92
93 @staticmethod
93 @staticmethod
94 def _call_widget_constructed(widget):
94 def _call_widget_constructed(widget):
95 """Static method, called when a widget is constructed."""
95 """Static method, called when a widget is constructed."""
96 if Widget._widget_construction_callback is not None and callable(Widget._widget_construction_callback):
96 if Widget._widget_construction_callback is not None and callable(Widget._widget_construction_callback):
97 Widget._widget_construction_callback(widget)
97 Widget._widget_construction_callback(widget)
98
98
99 @staticmethod
99 @staticmethod
100 def handle_comm_opened(comm, msg):
100 def handle_comm_opened(comm, msg):
101 """Static method, called when a widget is constructed."""
101 """Static method, called when a widget is constructed."""
102 target_name = msg['content']['data']['target_name']
102 target_name = msg['content']['data']['target_name']
103 widget_class = import_item(target_name)
103 widget_class = import_item(target_name)
104 widget = widget_class(open_comm=False)
104 widget = widget_class(open_comm=False)
105 widget.set_comm(comm)
105 widget.set_comm(comm)
106
106
107
107
108 #-------------------------------------------------------------------------
108 #-------------------------------------------------------------------------
109 # Traits
109 # Traits
110 #-------------------------------------------------------------------------
110 #-------------------------------------------------------------------------
111 _model_module = Unicode(None, allow_none=True, help="""A requirejs module name
111 _model_module = Unicode(None, allow_none=True, help="""A requirejs module name
112 in which to find _model_name. If empty, look in the global registry.""")
112 in which to find _model_name. If empty, look in the global registry.""")
113 _model_name = Unicode('WidgetModel', help="""Name of the backbone model
113 _model_name = Unicode('WidgetModel', help="""Name of the backbone model
114 registered in the front-end to create and sync this widget with.""")
114 registered in the front-end to create and sync this widget with.""")
115 _view_module = Unicode(help="""A requirejs module in which to find _view_name.
115 _view_module = Unicode(help="""A requirejs module in which to find _view_name.
116 If empty, look in the global registry.""", sync=True)
116 If empty, look in the global registry.""", sync=True)
117 _view_name = Unicode(None, allow_none=True, help="""Default view registered in the front-end
117 _view_name = Unicode(None, allow_none=True, help="""Default view registered in the front-end
118 to use to represent the widget.""", sync=True)
118 to use to represent the widget.""", sync=True)
119 comm = Instance('IPython.kernel.comm.Comm')
119 comm = Instance('IPython.kernel.comm.Comm')
120
120
121 msg_throttle = Int(3, sync=True, help="""Maximum number of msgs the
121 msg_throttle = Int(3, sync=True, help="""Maximum number of msgs the
122 front-end can send before receiving an idle msg from the back-end.""")
122 front-end can send before receiving an idle msg from the back-end.""")
123
123
124 version = Int(0, sync=True, help="""Widget's version""")
124 version = Int(0, sync=True, help="""Widget's version""")
125 keys = List()
125 keys = List()
126 def _keys_default(self):
126 def _keys_default(self):
127 return [name for name in self.traits(sync=True)]
127 return [name for name in self.traits(sync=True)]
128
128
129 _property_lock = Tuple((None, None))
129 _property_lock = Tuple((None, None))
130 _send_state_lock = Int(0)
130 _send_state_lock = Int(0)
131 _states_to_send = Set(allow_none=False)
131 _states_to_send = Set(allow_none=False)
132 _display_callbacks = Instance(CallbackDispatcher, ())
132 _display_callbacks = Instance(CallbackDispatcher, ())
133 _msg_callbacks = Instance(CallbackDispatcher, ())
133 _msg_callbacks = Instance(CallbackDispatcher, ())
134
134
135 #-------------------------------------------------------------------------
135 #-------------------------------------------------------------------------
136 # (Con/de)structor
136 # (Con/de)structor
137 #-------------------------------------------------------------------------
137 #-------------------------------------------------------------------------
138 def __init__(self, open_comm=True, **kwargs):
138 def __init__(self, open_comm=True, **kwargs):
139 """Public constructor"""
139 """Public constructor"""
140 self._model_id = kwargs.pop('model_id', None)
140 self._model_id = kwargs.pop('model_id', None)
141 super(Widget, self).__init__(**kwargs)
141 super(Widget, self).__init__(**kwargs)
142
142
143 Widget._call_widget_constructed(self)
143 Widget._call_widget_constructed(self)
144 if open_comm:
144 if open_comm:
145 self.open()
145 self.open()
146
146
147 def __del__(self):
147 def __del__(self):
148 """Object disposal"""
148 """Object disposal"""
149 self.close()
149 self.close()
150
150
151 #-------------------------------------------------------------------------
151 #-------------------------------------------------------------------------
152 # Properties
152 # Properties
153 #-------------------------------------------------------------------------
153 #-------------------------------------------------------------------------
154
154
155 def open(self):
155 def open(self):
156 """Open a comm to the frontend if one isn't already open."""
156 """Open a comm to the frontend if one isn't already open."""
157 if self.comm is None:
157 if self.comm is None:
158 args = dict(target_name='ipython.widget',
158 args = dict(target_name='ipython.widget',
159 data={'model_name': self._model_name,
159 data={'model_name': self._model_name,
160 'model_module': self._model_module})
160 'model_module': self._model_module})
161 if self._model_id is not None:
161 if self._model_id is not None:
162 args['comm_id'] = self._model_id
162 args['comm_id'] = self._model_id
163 self.set_comm(Comm(**args))
163 self.set_comm(Comm(**args))
164
164
165 def set_comm(self, comm):
165 def set_comm(self, comm):
166 """Set's the comm of the widget."""
166 """Set's the comm of the widget."""
167 self.comm = comm
167 self.comm = comm
168 self._model_id = self.model_id
168 self._model_id = self.model_id
169
169
170 self.comm.on_msg(self._handle_msg)
170 self.comm.on_msg(self._handle_msg)
171 Widget.widgets[self.model_id] = self
171 Widget.widgets[self.model_id] = self
172
172
173 # first update
173 # first update
174 self.send_state()
174 self.send_state()
175
175
176
176
177 @property
177 @property
178 def model_id(self):
178 def model_id(self):
179 """Gets the model id of this widget.
179 """Gets the model id of this widget.
180
180
181 If a Comm doesn't exist yet, a Comm will be created automagically."""
181 If a Comm doesn't exist yet, a Comm will be created automagically."""
182 return self.comm.comm_id
182 return self.comm.comm_id
183
183
184 #-------------------------------------------------------------------------
184 #-------------------------------------------------------------------------
185 # Methods
185 # Methods
186 #-------------------------------------------------------------------------
186 #-------------------------------------------------------------------------
187
187
188 def close(self):
188 def close(self):
189 """Close method.
189 """Close method.
190
190
191 Closes the underlying comm.
191 Closes the underlying comm.
192 When the comm is closed, all of the widget views are automatically
192 When the comm is closed, all of the widget views are automatically
193 removed from the front-end."""
193 removed from the front-end."""
194 if self.comm is not None:
194 if self.comm is not None:
195 Widget.widgets.pop(self.model_id, None)
195 Widget.widgets.pop(self.model_id, None)
196 self.comm.close()
196 self.comm.close()
197 self.comm = None
197 self.comm = None
198
198
199 def send_state(self, key=None):
199 def send_state(self, key=None):
200 """Sends the widget state, or a piece of it, to the front-end.
200 """Sends the widget state, or a piece of it, to the front-end.
201
201
202 Parameters
202 Parameters
203 ----------
203 ----------
204 key : unicode, or iterable (optional)
204 key : unicode, or iterable (optional)
205 A single property's name or iterable of property names to sync with the front-end.
205 A single property's name or iterable of property names to sync with the front-end.
206 """
206 """
207 self._send({
207 self._send({
208 "method" : "update",
208 "method" : "update",
209 "state" : self.get_state(key=key)
209 "state" : self.get_state(key=key)
210 })
210 })
211
211
212 def get_state(self, key=None):
212 def get_state(self, key=None):
213 """Gets the widget state, or a piece of it.
213 """Gets the widget state, or a piece of it.
214
214
215 Parameters
215 Parameters
216 ----------
216 ----------
217 key : unicode or iterable (optional)
217 key : unicode or iterable (optional)
218 A single property's name or iterable of property names to get.
218 A single property's name or iterable of property names to get.
219 """
219 """
220 if key is None:
220 if key is None:
221 keys = self.keys
221 keys = self.keys
222 elif isinstance(key, string_types):
222 elif isinstance(key, string_types):
223 keys = [key]
223 keys = [key]
224 elif isinstance(key, collections.Iterable):
224 elif isinstance(key, collections.Iterable):
225 keys = key
225 keys = key
226 else:
226 else:
227 raise ValueError("key must be a string, an iterable of keys, or None")
227 raise ValueError("key must be a string, an iterable of keys, or None")
228 state = {}
228 state = {}
229 for k in keys:
229 for k in keys:
230 f = self.trait_metadata(k, 'to_json', self._trait_to_json)
230 f = self.trait_metadata(k, 'to_json', self._trait_to_json)
231 value = getattr(self, k)
231 value = getattr(self, k)
232 state[k] = f(value)
232 state[k] = f(value)
233 return state
233 return state
234
234
235 def set_state(self, sync_data):
235 def set_state(self, sync_data):
236 """Called when a state is received from the front-end."""
236 """Called when a state is received from the front-end."""
237 for name in self.keys:
237 for name in self.keys:
238 if name in sync_data:
238 if name in sync_data:
239 json_value = sync_data[name]
239 json_value = sync_data[name]
240 from_json = self.trait_metadata(name, 'from_json', self._trait_from_json)
240 from_json = self.trait_metadata(name, 'from_json', self._trait_from_json)
241 with self._lock_property(name, json_value):
241 with self._lock_property(name, json_value):
242 setattr(self, name, from_json(json_value))
242 setattr(self, name, from_json(json_value))
243
243
244 def send(self, content):
244 def send(self, content):
245 """Sends a custom msg to the widget model in the front-end.
245 """Sends a custom msg to the widget model in the front-end.
246
246
247 Parameters
247 Parameters
248 ----------
248 ----------
249 content : dict
249 content : dict
250 Content of the message to send.
250 Content of the message to send.
251 """
251 """
252 self._send({"method": "custom", "content": content})
252 self._send({"method": "custom", "content": content})
253
253
254 def on_msg(self, callback, remove=False):
254 def on_msg(self, callback, remove=False):
255 """(Un)Register a custom msg receive callback.
255 """(Un)Register a custom msg receive callback.
256
256
257 Parameters
257 Parameters
258 ----------
258 ----------
259 callback: callable
259 callback: callable
260 callback will be passed two arguments when a message arrives::
260 callback will be passed two arguments when a message arrives::
261
261
262 callback(widget, content)
262 callback(widget, content)
263
263
264 remove: bool
264 remove: bool
265 True if the callback should be unregistered."""
265 True if the callback should be unregistered."""
266 self._msg_callbacks.register_callback(callback, remove=remove)
266 self._msg_callbacks.register_callback(callback, remove=remove)
267
267
268 def on_displayed(self, callback, remove=False):
268 def on_displayed(self, callback, remove=False):
269 """(Un)Register a widget displayed callback.
269 """(Un)Register a widget displayed callback.
270
270
271 Parameters
271 Parameters
272 ----------
272 ----------
273 callback: method handler
273 callback: method handler
274 Must have a signature of::
274 Must have a signature of::
275
275
276 callback(widget, **kwargs)
276 callback(widget, **kwargs)
277
277
278 kwargs from display are passed through without modification.
278 kwargs from display are passed through without modification.
279 remove: bool
279 remove: bool
280 True if the callback should be unregistered."""
280 True if the callback should be unregistered."""
281 self._display_callbacks.register_callback(callback, remove=remove)
281 self._display_callbacks.register_callback(callback, remove=remove)
282
282
283 #-------------------------------------------------------------------------
283 #-------------------------------------------------------------------------
284 # Support methods
284 # Support methods
285 #-------------------------------------------------------------------------
285 #-------------------------------------------------------------------------
286 @contextmanager
286 @contextmanager
287 def _lock_property(self, key, value):
287 def _lock_property(self, key, value):
288 """Lock a property-value pair.
288 """Lock a property-value pair.
289
289
290 The value should be the JSON state of the property.
290 The value should be the JSON state of the property.
291
291
292 NOTE: This, in addition to the single lock for all state changes, is
292 NOTE: This, in addition to the single lock for all state changes, is
293 flawed. In the future we may want to look into buffering state changes
293 flawed. In the future we may want to look into buffering state changes
294 back to the front-end."""
294 back to the front-end."""
295 self._property_lock = (key, value)
295 self._property_lock = (key, value)
296 try:
296 try:
297 yield
297 yield
298 finally:
298 finally:
299 self._property_lock = (None, None)
299 self._property_lock = (None, None)
300
300
301 @contextmanager
301 @contextmanager
302 def hold_sync(self):
302 def hold_sync(self):
303 """Hold syncing any state until the context manager is released"""
303 """Hold syncing any state until the context manager is released"""
304 # We increment a value so that this can be nested. Syncing will happen when
304 # We increment a value so that this can be nested. Syncing will happen when
305 # all levels have been released.
305 # all levels have been released.
306 self._send_state_lock += 1
306 self._send_state_lock += 1
307 try:
307 try:
308 yield
308 yield
309 finally:
309 finally:
310 self._send_state_lock -=1
310 self._send_state_lock -=1
311 if self._send_state_lock == 0:
311 if self._send_state_lock == 0:
312 self.send_state(self._states_to_send)
312 self.send_state(self._states_to_send)
313 self._states_to_send.clear()
313 self._states_to_send.clear()
314
314
315 def _should_send_property(self, key, value):
315 def _should_send_property(self, key, value):
316 """Check the property lock (property_lock)"""
316 """Check the property lock (property_lock)"""
317 to_json = self.trait_metadata(key, 'to_json', self._trait_to_json)
317 to_json = self.trait_metadata(key, 'to_json', self._trait_to_json)
318 if (key == self._property_lock[0]
318 if (key == self._property_lock[0]
319 and to_json(value) == self._property_lock[1]):
319 and to_json(value) == self._property_lock[1]):
320 return False
320 return False
321 elif self._send_state_lock > 0:
321 elif self._send_state_lock > 0:
322 self._states_to_send.add(key)
322 self._states_to_send.add(key)
323 return False
323 return False
324 else:
324 else:
325 return True
325 return True
326
326
327 # Event handlers
327 # Event handlers
328 @_show_traceback
328 @_show_traceback
329 def _handle_msg(self, msg):
329 def _handle_msg(self, msg):
330 """Called when a msg is received from the front-end"""
330 """Called when a msg is received from the front-end"""
331 data = msg['content']['data']
331 data = msg['content']['data']
332 method = data['method']
332 method = data['method']
333 if not method in ['backbone', 'custom']:
333 if not method in ['backbone', 'custom']:
334 self.log.error('Unknown front-end to back-end widget msg with method "%s"' % method)
334 self.log.error('Unknown front-end to back-end widget msg with method "%s"' % method)
335
335
336 # Handle backbone sync methods CREATE, PATCH, and UPDATE all in one.
336 # Handle backbone sync methods CREATE, PATCH, and UPDATE all in one.
337 if method == 'backbone' and 'sync_data' in data:
337 if method == 'backbone' and 'sync_data' in data:
338 sync_data = data['sync_data']
338 sync_data = data['sync_data']
339 self.set_state(sync_data) # handles all methods
339 self.set_state(sync_data) # handles all methods
340
340
341 # Handle a custom msg from the front-end
341 # Handle a custom msg from the front-end
342 elif method == 'custom':
342 elif method == 'custom':
343 if 'content' in data:
343 if 'content' in data:
344 self._handle_custom_msg(data['content'])
344 self._handle_custom_msg(data['content'])
345
345
346 def _handle_custom_msg(self, content):
346 def _handle_custom_msg(self, content):
347 """Called when a custom msg is received."""
347 """Called when a custom msg is received."""
348 self._msg_callbacks(self, content)
348 self._msg_callbacks(self, content)
349
349
350 def _notify_trait(self, name, old_value, new_value):
350 def _notify_trait(self, name, old_value, new_value):
351 """Called when a property has been changed."""
351 """Called when a property has been changed."""
352 # Trigger default traitlet callback machinery. This allows any user
352 # Trigger default traitlet callback machinery. This allows any user
353 # registered validation to be processed prior to allowing the widget
353 # registered validation to be processed prior to allowing the widget
354 # machinery to handle the state.
354 # machinery to handle the state.
355 LoggingConfigurable._notify_trait(self, name, old_value, new_value)
355 LoggingConfigurable._notify_trait(self, name, old_value, new_value)
356
356
357 # Send the state after the user registered callbacks for trait changes
357 # Send the state after the user registered callbacks for trait changes
358 # have all fired (allows for user to validate values).
358 # have all fired (allows for user to validate values).
359 if self.comm is not None and name in self.keys:
359 if self.comm is not None and name in self.keys:
360 # Make sure this isn't information that the front-end just sent us.
360 # Make sure this isn't information that the front-end just sent us.
361 if self._should_send_property(name, new_value):
361 if self._should_send_property(name, new_value):
362 # Send new state to front-end
362 # Send new state to front-end
363 self.send_state(key=name)
363 self.send_state(key=name)
364
364
365 def _handle_displayed(self, **kwargs):
365 def _handle_displayed(self, **kwargs):
366 """Called when a view has been displayed for this widget instance"""
366 """Called when a view has been displayed for this widget instance"""
367 self._display_callbacks(self, **kwargs)
367 self._display_callbacks(self, **kwargs)
368
368
369 def _trait_to_json(self, x):
369 def _trait_to_json(self, x):
370 """Convert a trait value to json
370 """Convert a trait value to json
371
371
372 Traverse lists/tuples and dicts and serialize their values as well.
372 Traverse lists/tuples and dicts and serialize their values as well.
373 Replace any widgets with their model_id
373 Replace any widgets with their model_id
374 """
374 """
375 if isinstance(x, dict):
375 if isinstance(x, dict):
376 return {k: self._trait_to_json(v) for k, v in x.items()}
376 return {k: self._trait_to_json(v) for k, v in x.items()}
377 elif isinstance(x, (list, tuple)):
377 elif isinstance(x, (list, tuple)):
378 return [self._trait_to_json(v) for v in x]
378 return [self._trait_to_json(v) for v in x]
379 elif isinstance(x, Widget):
379 elif isinstance(x, Widget):
380 return "IPY_MODEL_" + x.model_id
380 return "IPY_MODEL_" + x.model_id
381 else:
381 else:
382 return x # Value must be JSON-able
382 return x # Value must be JSON-able
383
383
384 def _trait_from_json(self, x):
384 def _trait_from_json(self, x):
385 """Convert json values to objects
385 """Convert json values to objects
386
386
387 Replace any strings representing valid model id values to Widget references.
387 Replace any strings representing valid model id values to Widget references.
388 """
388 """
389 if isinstance(x, dict):
389 if isinstance(x, dict):
390 return {k: self._trait_from_json(v) for k, v in x.items()}
390 return {k: self._trait_from_json(v) for k, v in x.items()}
391 elif isinstance(x, (list, tuple)):
391 elif isinstance(x, (list, tuple)):
392 return [self._trait_from_json(v) for v in x]
392 return [self._trait_from_json(v) for v in x]
393 elif isinstance(x, string_types) and x.startswith('IPY_MODEL_') and x[10:] in Widget.widgets:
393 elif isinstance(x, string_types) and x.startswith('IPY_MODEL_') and x[10:] in Widget.widgets:
394 # we want to support having child widgets at any level in a hierarchy
394 # we want to support having child widgets at any level in a hierarchy
395 # trusting that a widget UUID will not appear out in the wild
395 # trusting that a widget UUID will not appear out in the wild
396 return Widget.widgets[x[10:]]
396 return Widget.widgets[x[10:]]
397 else:
397 else:
398 return x
398 return x
399
399
400 def _ipython_display_(self, **kwargs):
400 def _ipython_display_(self, **kwargs):
401 """Called when `IPython.display.display` is called on the widget."""
401 """Called when `IPython.display.display` is called on the widget."""
402 # Show view.
402 # Show view.
403 if self._view_name is not None:
403 if self._view_name is not None:
404 self._send({"method": "display"})
404 self._send({"method": "display"})
405 self._handle_displayed(**kwargs)
405 self._handle_displayed(**kwargs)
406
406
407 def _send(self, msg):
407 def _send(self, msg):
408 """Sends a message to the model in the front-end."""
408 """Sends a message to the model in the front-end."""
409 self.comm.send(msg)
409 self.comm.send(msg)
410
410
411
411
412 class DOMWidget(Widget):
412 class DOMWidget(Widget):
413 visible = Bool(True, help="Whether the widget is visible.", sync=True)
413 visible = Bool(True, help="Whether the widget is visible.", sync=True)
414 _css = Tuple(sync=True, help="CSS property list: (selector, key, value)")
414 _css = Tuple(sync=True, help="CSS property list: (selector, key, value)")
415 _dom_classes = Tuple(sync=True, help="DOM classes applied to widget.$el.")
415 _dom_classes = Tuple(sync=True, help="DOM classes applied to widget.$el.")
416
416
417 width = CUnicode(sync=True)
417 width = CUnicode(sync=True)
418 height = CUnicode(sync=True)
418 height = CUnicode(sync=True)
419 padding = CUnicode(sync=True)
419 padding = CUnicode(sync=True)
420 margin = CUnicode(sync=True)
420 margin = CUnicode(sync=True)
421
421
422 color = Unicode(sync=True)
422 color = Unicode(sync=True)
423 background_color = Unicode(sync=True)
423 background_color = Unicode(sync=True)
424 border_color = Unicode(sync=True)
424 border_color = Unicode(sync=True)
425
425
426 border_width = CUnicode(sync=True)
426 border_width = CUnicode(sync=True)
427 border_radius = CUnicode(sync=True)
427 border_radius = CUnicode(sync=True)
428 border_style = CaselessStrEnum(values=[ # http://www.w3schools.com/cssref/pr_border-style.asp
428 border_style = CaselessStrEnum(values=[ # http://www.w3schools.com/cssref/pr_border-style.asp
429 'none',
429 'none',
430 'hidden',
430 'hidden',
431 'dotted',
431 'dotted',
432 'dashed',
432 'dashed',
433 'solid',
433 'solid',
434 'double',
434 'double',
435 'groove',
435 'groove',
436 'ridge',
436 'ridge',
437 'inset',
437 'inset',
438 'outset',
438 'outset',
439 'initial',
439 'initial',
440 'inherit', ''],
440 'inherit', ''],
441 default_value='', sync=True)
441 default_value='', sync=True)
442
442
443 font_style = CaselessStrEnum(values=[ # http://www.w3schools.com/cssref/pr_font_font-style.asp
443 font_style = CaselessStrEnum(values=[ # http://www.w3schools.com/cssref/pr_font_font-style.asp
444 'normal',
444 'normal',
445 'italic',
445 'italic',
446 'oblique',
446 'oblique',
447 'initial',
447 'initial',
448 'inherit', ''],
448 'inherit', ''],
449 default_value='', sync=True)
449 default_value='', sync=True)
450 font_weight = CaselessStrEnum(values=[ # http://www.w3schools.com/cssref/pr_font_weight.asp
450 font_weight = CaselessStrEnum(values=[ # http://www.w3schools.com/cssref/pr_font_weight.asp
451 'normal',
451 'normal',
452 'bold',
452 'bold',
453 'bolder',
453 'bolder',
454 'lighter',
454 'lighter',
455 'initial',
455 'initial',
456 'inherit', ''] + [str(100 * (i+1)) for i in range(9)],
456 'inherit', ''] + [str(100 * (i+1)) for i in range(9)],
457 default_value='', sync=True)
457 default_value='', sync=True)
458 font_size = CUnicode(sync=True)
458 font_size = CUnicode(sync=True)
459 font_family = Unicode(sync=True)
459 font_family = Unicode(sync=True)
460
460
461 def __init__(self, *pargs, **kwargs):
461 def __init__(self, *pargs, **kwargs):
462 super(DOMWidget, self).__init__(*pargs, **kwargs)
462 super(DOMWidget, self).__init__(*pargs, **kwargs)
463
463
464 def _validate_border(name, old, new):
464 def _validate_border(name, old, new):
465 if new is not None and new != '':
465 if new is not None and new != '':
466 if name != 'border_width' and not self.border_width:
466 if name != 'border_width' and not self.border_width:
467 self.border_width = 1
467 self.border_width = 1
468 if name != 'border_style' and self.border_style == '':
468 if name != 'border_style' and self.border_style == '':
469 self.border_style = 'solid'
469 self.border_style = 'solid'
470 self.on_trait_change(_validate_border, ['border_width', 'border_style', 'border_color'])
470 self.on_trait_change(_validate_border, ['border_width', 'border_style', 'border_color'])
General Comments 0
You need to be logged in to leave comments. Login now