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