##// END OF EJS Templates
Intermediate changes to javascript side of backbone widgets
Jason Grout -
Show More
@@ -1,129 +1,214 b''
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2013 The IPython Development Team
2 // Copyright (C) 2013 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 //============================================================================
8 //============================================================================
9 // WidgetModel, WidgetView, and WidgetManager
9 // WidgetModel, WidgetView, and WidgetManager
10 //============================================================================
10 //============================================================================
11 /**
11 /**
12 * Base Widget classes
12 * Base Widget classes
13 * @module IPython
13 * @module IPython
14 * @namespace IPython
14 * @namespace IPython
15 * @submodule widget
15 * @submodule widget
16 */
16 */
17
17
18 (function () {
18 (function () {
19 "use strict";
19 "use strict";
20
20
21 // Use require.js 'define' method so that require.js is intelligent enough to
21 // Use require.js 'define' method so that require.js is intelligent enough to
22 // syncronously load everything within this file when it is being 'required'
22 // syncronously load everything within this file when it is being 'required'
23 // elsewhere.
23 // elsewhere.
24 define(["underscore",
24 define(["underscore",
25 "backbone",
25 "backbone",
26 ], function (underscore, backbone) {
26 ], function (underscore, backbone) {
27
27
28 //--------------------------------------------------------------------
28 //--------------------------------------------------------------------
29 // WidgetManager class
29 // WidgetManager class
30 //--------------------------------------------------------------------
30 //--------------------------------------------------------------------
31 var WidgetManager = function () {
31 var WidgetManager = function () {
32 this.comm_manager = null;
32 this.comm_manager = null;
33 this.widget_model_types = {};
33 this.widget_model_types = {};
34 this.widget_view_types = {};
34 this.widget_view_types = {};
35 this._model_instances = {};
35 this._model_instances = {};
36
36
37 Backbone.sync = function (method, model, options, error) {
37 Backbone.sync = function (method, model, options, error) {
38 var result = model._handle_sync(method, options);
38 var result = model._handle_sync(method, options);
39 if (options.success) {
39 if (options.success) {
40 options.success(result);
40 options.success(result);
41 }
41 }
42 };
42 };
43 };
43 };
44
44
45
45
46 WidgetManager.prototype.attach_comm_manager = function (comm_manager) {
46 WidgetManager.prototype.attach_comm_manager = function (comm_manager) {
47 this.comm_manager = comm_manager;
47 this.comm_manager = comm_manager;
48
48
49 // Register already register widget model types with the comm manager.
49 // Register already-registered widget model types with the comm manager.
50 for (var widget_model_name in this.widget_model_types) {
50 for (var widget_model_name in this.widget_model_types) {
51 this.comm_manager.register_target(widget_model_name, $.proxy(this._handle_com_open, this));
51 this.comm_manager.register_target(widget_model_name, $.proxy(this._handle_comm_open, this));
52 }
52 }
53 };
53 };
54
54
55
55
56 WidgetManager.prototype.register_widget_model = function (widget_model_name, widget_model_type) {
56 WidgetManager.prototype.register_widget_model = function (widget_model_name, widget_model_type) {
57 // Register the widget with the comm manager. Make sure to pass this object's context
57 // Register the widget with the comm manager. Make sure to pass this object's context
58 // in so `this` works in the call back.
58 // in so `this` works in the call back.
59 if (this.comm_manager !== null) {
59 if (this.comm_manager !== null) {
60 this.comm_manager.register_target(widget_model_name, $.proxy(this._handle_com_open, this));
60 this.comm_manager.register_target(widget_model_name, $.proxy(this._handle_comm_open, this));
61 }
61 }
62 this.widget_model_types[widget_model_name] = widget_model_type;
62 this.widget_model_types[widget_model_name] = widget_model_type;
63 };
63 };
64
64
65
65
66 WidgetManager.prototype.register_widget_view = function (widget_view_name, widget_view_type) {
66 WidgetManager.prototype.register_widget_view = function (widget_view_name, widget_view_type) {
67 this.widget_view_types[widget_view_name] = widget_view_type;
67 this.widget_view_types[widget_view_name] = widget_view_type;
68 };
68 };
69
69 WidgetManager.prototype.handle_msg = function(msg, model) {
70 var method = msg.content.data.method;
71 switch (method) {
72 case 'display':
73 var cell = this.get_msg_cell(msg.parent_header.msg_id);
74 if (cell === null) {
75 console.log("Could not determine where the display" +
76 " message was from. Widget will not be displayed");
77 } else {
78 var view = this.create_view(model,
79 msg.content.data.view_name, cell);
80 if (view !== undefined
81 && cell.widget_subarea !== undefined
82 && cell.widget_subarea !== null) {
83 cell.widget_area.show();
84 cell.widget_subarea.append(view.$el);
85 }
86 }
87 break;
88 case 'set_snapshot':
89 var cell = this.get_msg_cell(msg.parent_header.msg_id);
90 cell.metadata.snapshot = msg.content.data.snapshot;
91 break;
92 }
93 }
94
95 WidgetManager.prototype.create_view = function(model, view_name, cell) {
96 view_name = view_name || model.get('default_view_name');
97 var ViewType = this.widget_view_types[view_name];
98 if (ViewType !== undefined && ViewType !== null) {
99 var view = new ViewType({model: model, widget_manager: this, cell: cell});
100 view.render();
101 //this.views.push(view);
102
103 /*
104 // jng: Handle when the view element is remove from the page.
105 // observe the view destruction event and do this. We may need
106 // to override the view's remove method to trigger this event.
107 var that = this;
108 view.$el.on("remove", function () {
109 var index = that.views.indexOf(view);
110 if (index > -1) {
111 that.views.splice(index, 1);
112 }
113 view.remove(); // Clean-up view
114
115 // Close the comm if there are no views left.
116 if (that.views.length() === 0) {
117 //jng: trigger comm close event
118 }
119
120
121 if (that.comm !== undefined) {
122 that.comm.close();
123 delete that.comm.model; // Delete ref so GC will collect widget model.
124 delete that.comm;
125 }
126 delete that.widget_id; // Delete id from model so widget manager cleans up.
127 });
128 */
129 return view;
130 }
131 },
70
132
71 WidgetManager.prototype.get_msg_cell = function (msg_id) {
133 WidgetManager.prototype.get_msg_cell = function (msg_id) {
134 var cell = null;
135 // First, check to see if the msg was triggered by cell execution.
72 if (IPython.notebook !== undefined && IPython.notebook !== null) {
136 if (IPython.notebook !== undefined && IPython.notebook !== null) {
73 return IPython.notebook.get_msg_cell(msg_id);
137 cell = IPython.notebook.get_msg_cell(msg_id);
138 }
139 if (cell !== null) {
140 return cell
141 }
142 // Second, check to see if a get_cell callback was defined
143 // for the message. get_cell callbacks are registered for
144 // widget messages, so this block is actually checking to see if the
145 // message was triggered by a widget.
146 var kernel = this.get_kernel();
147 if (kernel !== undefined && kernel !== null) {
148 var callbacks = kernel.get_callbacks_for_msg(msg_id);
149 if (callbacks !== undefined &&
150 callbacks.iopub !== undefined &&
151 callbacks.iopub.get_cell !== undefined) {
152
153 return callbacks.iopub.get_cell();
154 }
74 }
155 }
156
157 // Not triggered by a cell or widget (no get_cell callback
158 // exists).
159 return null;
75 };
160 };
76
161
77
162
78 WidgetManager.prototype.get_model = function (widget_id) {
163 WidgetManager.prototype.get_model = function (widget_id) {
79 var model = this._model_instances[widget_id];
164 var model = this._model_instances[widget_id];
80 if (model !== undefined && model.id == widget_id) {
165 if (model !== undefined && model.id == widget_id) {
81 return model;
166 return model;
82 }
167 }
83 return null;
168 return null;
84 };
169 };
85
170
86
171
87 WidgetManager.prototype.get_kernel = function () {
172 WidgetManager.prototype.get_kernel = function () {
88 if (this.comm_manager === null) {
173 if (this.comm_manager === null) {
89 return null;
174 return null;
90 } else {
175 } else {
91 return this.comm_manager.kernel;
176 return this.comm_manager.kernel;
92 }
177 }
93 };
178 };
94
179
95
180
96 WidgetManager.prototype.on_create_widget = function (callback) {
181 WidgetManager.prototype.on_create_widget = function (callback) {
97 this._create_widget_callback = callback;
182 this._create_widget_callback = callback;
98 };
183 };
99
184
100
185
101 WidgetManager.prototype._handle_create_widget = function (widget_model) {
186 WidgetManager.prototype._handle_create_widget = function (widget_model) {
102 if (this._create_widget_callback) {
187 if (this._create_widget_callback) {
103 try {
188 try {
104 this._create_widget_callback(widget_model);
189 this._create_widget_callback(widget_model);
105 } catch (e) {
190 } catch (e) {
106 console.log("Exception in WidgetManager callback", e, widget_model);
191 console.log("Exception in WidgetManager callback", e, widget_model);
107 }
192 }
108 }
193 }
109 };
194 };
110
195
111
196
112 WidgetManager.prototype._handle_com_open = function (comm, msg) {
197 WidgetManager.prototype._handle_comm_open = function (comm, msg) {
113 var widget_type_name = msg.content.target_name;
198 var widget_type_name = msg.content.target_name;
114 var widget_model = new this.widget_model_types[widget_type_name](this, comm.comm_id, comm);
199 var widget_model = new this.widget_model_types[widget_type_name](this, comm.comm_id, comm);
115 this._model_instances[comm.comm_id] = widget_model;
200 this._model_instances[comm.comm_id] = widget_model;
116 this._handle_create_widget(widget_model);
201 this._handle_create_widget(widget_model);
117 };
202 };
118
203
119 //--------------------------------------------------------------------
204 //--------------------------------------------------------------------
120 // Init code
205 // Init code
121 //--------------------------------------------------------------------
206 //--------------------------------------------------------------------
122 IPython.WidgetManager = WidgetManager;
207 IPython.WidgetManager = WidgetManager;
123 if (IPython.widget_manager === undefined || IPython.widget_manager === null) {
208 if (IPython.widget_manager === undefined || IPython.widget_manager === null) {
124 IPython.widget_manager = new WidgetManager();
209 IPython.widget_manager = new WidgetManager();
125 }
210 }
126
211
127 return IPython.widget_manager;
212 return IPython.widget_manager;
128 });
213 });
129 }()); No newline at end of file
214 }());
@@ -1,555 +1,362 b''
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2013 The IPython Development Team
2 // Copyright (C) 2013 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 //============================================================================
8 //============================================================================
9 // Base Widget Model and View classes
9 // Base Widget Model and View classes
10 //============================================================================
10 //============================================================================
11
11
12 /**
12 /**
13 * @module IPython
13 * @module IPython
14 * @namespace IPython
14 * @namespace IPython
15 **/
15 **/
16
16
17 define(["notebook/js/widgetmanager",
17 define(["notebook/js/widgetmanager",
18 "underscore",
18 "underscore",
19 "backbone"],
19 "backbone"],
20 function(widget_manager, underscore, backbone){
20 function(widget_manager, underscore, backbone){
21
21
22 //--------------------------------------------------------------------
22 //--------------------------------------------------------------------
23 // WidgetModel class
23 // WidgetModel class
24 //--------------------------------------------------------------------
24 //--------------------------------------------------------------------
25 var WidgetModel = Backbone.Model.extend({
25 var WidgetModel = Backbone.Model.extend({
26 constructor: function (widget_manager, widget_id, comm) {
26 constructor: function (widget_manager, widget_id, comm) {
27 this.widget_manager = widget_manager;
27 this.widget_manager = widget_manager;
28 this.pending_msgs = 0;
28 this.pending_msgs = 0;
29 this.msg_throttle = 3;
29 this.msg_throttle = 3;
30 this.msg_buffer = null;
30 this.msg_buffer = null;
31 this.views = [];
32 this.id = widget_id;
31 this.id = widget_id;
33 this._custom_msg_callbacks = [];
34
32
35 if (comm !== undefined) {
33 if (comm !== undefined) {
36
37 // Remember comm associated with the model.
34 // Remember comm associated with the model.
38 this.comm = comm;
35 this.comm = comm;
39 comm.model = this;
36 comm.model = this;
40
37
41 // Hook comm messages up to model.
38 // Hook comm messages up to model.
39 var that = this;
42 comm.on_close($.proxy(this._handle_comm_closed, this));
40 comm.on_close($.proxy(this._handle_comm_closed, this));
43 comm.on_msg($.proxy(this._handle_comm_msg, this));
41 comm.on_msg($.proxy(this._handle_comm_msg, this));
44 }
42 }
45
43
46 return Backbone.Model.apply(this);
44 return Backbone.Model.apply(this);
47 },
45 },
48
46
49
47
50 send: function (content, cell) {
48 send: function (content, callbacks) {
51 if (this._has_comm()) {
49 console.log('send',content, callbacks);
52 // Used the last modified view as the sender of the message. This
50 if (this.comm !== undefined) {
53 // will insure that any python code triggered by the sent message
54 // can create and display widgets and output.
55 if (cell === undefined) {
56 if (this.last_modified_view !== undefined &&
57 this.last_modified_view.cell !== undefined) {
58 cell = this.last_modified_view.cell;
59 }
60 }
61 var callbacks = this._make_callbacks(cell);
62 var data = {method: 'custom', custom_content: content};
51 var data = {method: 'custom', custom_content: content};
63
64 this.comm.send(data, callbacks);
52 this.comm.send(data, callbacks);
65 }
53 }
66 },
54 },
67
55
68
69 on_view_created: function (callback) {
70 this._view_created_callback = callback;
71 },
72
73
74 on_close: function (callback) {
75 this._close_callback = callback;
76 },
77
78
79 on_msg: function (callback, remove) {
80 if (remove) {
81 var found_index = -1;
82 for (var index in this._custom_msg_callbacks) {
83 if (callback === this._custom_msg_callbacks[index]) {
84 found_index = index;
85 break;
86 }
87 }
88
89 if (found_index >= 0) {
90 this._custom_msg_callbacks.splice(found_index, 1);
91 }
92 } else {
93 this._custom_msg_callbacks.push(callback);
94 }
95 },
96
97
98 _handle_custom_msg: function (content) {
99 for (var index in this._custom_msg_callbacks) {
100 try {
101 this._custom_msg_callbacks[index](content);
102 } catch (e) {
103 console.log("Exception in widget model msg callback", e, content);
104 }
105 }
106 },
107
108
109 // Handle when a widget is closed.
56 // Handle when a widget is closed.
110 _handle_comm_closed: function (msg) {
57 _handle_comm_closed: function (msg) {
111 this._execute_views_method('remove');
58 // jng: widget manager should observe the comm_close event and delete views when triggered
59 this.trigger('comm:close');
112 if (this._has_comm()) {
60 if (this._has_comm()) {
113 delete this.comm.model; // Delete ref so GC will collect widget model.
61 delete this.comm.model; // Delete ref so GC will collect widget model.
114 delete this.comm;
62 delete this.comm;
115 }
63 }
116 delete this.widget_id; // Delete id from model so widget manager cleans up.
64 delete this.widget_id; // Delete id from model so widget manager cleans up.
117 },
65 },
118
66
119
67
120 // Handle incomming comm msg.
68 // Handle incoming comm msg.
121 _handle_comm_msg: function (msg) {
69 _handle_comm_msg: function (msg) {
122 var method = msg.content.data.method;
70 var method = msg.content.data.method;
123 switch (method) {
71 switch (method) {
124 case 'display':
125
126 // Try to get the cell.
127 var cell = this._get_msg_cell(msg.parent_header.msg_id);
128 if (cell === null) {
129 console.log("Could not determine where the display" +
130 " message was from. Widget will not be displayed");
131 } else {
132 this.create_views(msg.content.data.view_name,
133 msg.content.data.parent,
134 cell);
135 }
136 break;
137 case 'update':
72 case 'update':
138 this.apply_update(msg.content.data.state);
73 this.apply_update(msg.content.data.state);
139 break;
74 break;
140 case 'add_class':
141 case 'remove_class':
142 var selector = msg.content.data.selector;
143 if (selector === undefined) {
144 selector = '';
145 }
146
147 var class_list = msg.content.data.class_list;
148 this._execute_views_method(method, selector, class_list);
149 break;
150 case 'set_snapshot':
151 var cell = this._get_msg_cell(msg.parent_header.msg_id);
152 cell.metadata.snapshot = msg.content.data.snapshot;
153 break;
154 case 'custom':
75 case 'custom':
155 this._handle_custom_msg(msg.content.data.custom_content);
76 this.trigger('msg:custom', msg.content.data.custom_content);
156 break;
77 break;
78 default:
79 // pass on to widget manager
80 this.widget_manager.handle_msg(msg, this);
157 }
81 }
158 },
82 },
159
83
160
84
161 // Handle when a widget is updated via the python side.
85 // Handle when a widget is updated via the python side.
162 apply_update: function (state) {
86 apply_update: function (state) {
163 this.updating = true;
87 this.updating = true;
164 try {
88 try {
165 for (var key in state) {
89 for (var key in state) {
166 if (state.hasOwnProperty(key)) {
90 if (state.hasOwnProperty(key)) {
167 if (key == "_css") {
91 this.set(key, state[key]);
168
169 // Set the css value of the model as an attribute
170 // instead of a backbone trait because we are only
171 // interested in backend css -> frontend css. In
172 // other words, if the css dict changes in the
173 // frontend, we don't need to push the changes to
174 // the backend.
175 this.css = state[key];
176 } else {
177 this.set(key, state[key]);
178 }
179 }
92 }
180 }
93 }
181 this.save();
94 this.save();
182 } finally {
95 } finally {
183 this.updating = false;
96 this.updating = false;
184 }
97 }
185 },
98 },
186
99
187
100
188 _handle_status: function (cell, msg) {
101 _handle_status: function (msg, callbacks) {
189 //execution_state : ('busy', 'idle', 'starting')
102 //execution_state : ('busy', 'idle', 'starting')
190 if (this._has_comm()) {
103 if (this.comm !== undefined) {
191 if (msg.content.execution_state=='idle') {
104 if (msg.content.execution_state ==='idle') {
192
105
193 // Send buffer if this message caused another message to be
106 // Send buffer if this message caused another message to be
194 // throttled.
107 // throttled.
195 if (this.msg_buffer !== null &&
108 if (this.msg_buffer !== null &&
196 this.msg_throttle == this.pending_msgs) {
109 this.msg_throttle === this.pending_msgs) {
197
198 var callbacks = this._make_callbacks(cell);
199 var data = {method: 'backbone', sync_method: 'update', sync_data: this.msg_buffer};
110 var data = {method: 'backbone', sync_method: 'update', sync_data: this.msg_buffer};
200 this.comm.send(data, callbacks);
111 this.comm.send(data, callbacks);
201 this.msg_buffer = null;
112 this.msg_buffer = null;
202 } else {
113 } else {
203
114
204 // Only decrease the pending message count if the buffer
115 // Only decrease the pending message count if the buffer
205 // doesn't get flushed (sent).
116 // doesn't get flushed (sent).
206 --this.pending_msgs;
117 --this.pending_msgs;
207 }
118 }
208 }
119 }
209 }
120 }
210 },
121 },
211
122
212
123
213 // Custom syncronization logic.
124 // Custom syncronization logic.
214 _handle_sync: function (method, options) {
125 _handle_sync: function (method, options) {
215 var model_json = this.toJSON();
126 var model_json = this.toJSON();
216 var attr;
127 var attr;
217
128
218 // Only send updated state if the state hasn't been changed
129 // Only send updated state if the state hasn't been changed
219 // during an update.
130 // during an update.
220 if (this._has_comm()) {
131 if (this.comm !== undefined) {
221 if (!this.updating) {
132 if (!this.updating) {
222 if (this.pending_msgs >= this.msg_throttle) {
133 if (this.pending_msgs >= this.msg_throttle) {
223 // The throttle has been exceeded, buffer the current msg so
134 // The throttle has been exceeded, buffer the current msg so
224 // it can be sent once the kernel has finished processing
135 // it can be sent once the kernel has finished processing
225 // some of the existing messages.
136 // some of the existing messages.
226 if (method=='patch') {
137 if (method=='patch') {
227 if (this.msg_buffer === null) {
138 if (this.msg_buffer === null) {
228 this.msg_buffer = $.extend({}, model_json); // Copy
139 this.msg_buffer = $.extend({}, model_json); // Copy
229 }
140 }
230 for (attr in options.attrs) {
141 for (attr in options.attrs) {
231 this.msg_buffer[attr] = options.attrs[attr];
142 this.msg_buffer[attr] = options.attrs[attr];
232 }
143 }
233 } else {
144 } else {
234 this.msg_buffer = $.extend({}, model_json); // Copy
145 this.msg_buffer = $.extend({}, model_json); // Copy
235 }
146 }
236
147
237 } else {
148 } else {
238 // We haven't exceeded the throttle, send the message like
149 // We haven't exceeded the throttle, send the message like
239 // normal. If this is a patch operation, just send the
150 // normal. If this is a patch operation, just send the
240 // changes.
151 // changes.
241 var send_json = model_json;
152 var send_json = model_json;
242 if (method =='patch') {
153 if (method =='patch') {
243 send_json = {};
154 send_json = {};
244 for (attr in options.attrs) {
155 for (attr in options.attrs) {
245 send_json[attr] = options.attrs[attr];
156 send_json[attr] = options.attrs[attr];
246 }
157 }
247 }
158 }
248
159
249 var data = {method: 'backbone', sync_method: method, sync_data: send_json};
160 var data = {method: 'backbone', sync_method: method, sync_data: send_json};
250
161 this.comm.send(data, this.cell_callbacks());
251 var cell = null;
252 if (this.last_modified_view !== undefined && this.last_modified_view !== null) {
253 cell = this.last_modified_view.cell;
254 }
255
256 var callbacks = this._make_callbacks(cell);
257 this.comm.send(data, callbacks);
258 this.pending_msgs++;
162 this.pending_msgs++;
259 }
163 }
260 }
164 }
261 }
165 }
262
166
263 // Since the comm is a one-way communication, assume the message
167 // Since the comm is a one-way communication, assume the message
264 // arrived.
168 // arrived.
265 return model_json;
169 return model_json;
266 },
170 },
267
171
268
269 _handle_view_created: function (view) {
270 if (this._view_created_callback) {
271 try {
272 this._view_created_callback(view);
273 } catch (e) {
274 console.log("Exception in widget model view displayed callback", e, view, this);
275 }
276 }
277 },
278
279
280 _execute_views_method: function (/* method_name, [argument0], [argument1], [...] */) {
281 var method_name = arguments[0];
282 var args = null;
283 if (arguments.length > 1) {
284 args = [].splice.call(arguments,1);
285 }
286
287 for (var view_index in this.views) {
288 var view = this.views[view_index];
289 var method = view[method_name];
290 if (args === null) {
291 method.apply(view);
292 } else {
293 method.apply(view, args);
294 }
295 }
296 },
297
298
299 // Create view that represents the model.
300 create_views: function (view_name, parent_id, cell) {
301 var new_views = [];
302 var view;
303
304 // Try creating and adding the view to it's parent.
305 var displayed = false;
306 if (parent_id !== undefined) {
307 var parent_model = this.widget_manager.get_model(parent_id);
308 if (parent_model !== null) {
309 var parent_views = parent_model.views;
310 for (var parent_view_index in parent_views) {
311 var parent_view = parent_views[parent_view_index];
312 if (parent_view.cell === cell) {
313 if (parent_view.display_child !== undefined) {
314 view = this._create_view(view_name, cell);
315 if (view !== null) {
316 new_views.push(view);
317 parent_view.display_child(view);
318 displayed = true;
319 this._handle_view_created(view);
320 }
321 }
322 }
323 }
324 }
325 }
326
327 // If no parent view is defined or exists. Add the view's
328 // element to cell's widget div.
329 if (!displayed) {
330 view = this._create_view(view_name, cell);
331 if (view !== null) {
332 new_views.push(view);
333
334 if (cell.widget_subarea !== undefined && cell.widget_subarea !== null) {
335 cell.widget_area.show();
336 cell.widget_subarea.append(view.$el);
337 this._handle_view_created(view);
338 }
339 }
340 }
341
342 // Force the new view(s) to update their selves
343 for (var view_index in new_views) {
344 view = new_views[view_index];
345 view.update();
346 }
347 },
348
349
350 // Create a view
351 _create_view: function (view_name, cell) {
352 var ViewType = this.widget_manager.widget_view_types[view_name];
353 if (ViewType !== undefined && ViewType !== null) {
354 var view = new ViewType({model: this});
355 view.render();
356 this.views.push(view);
357 view.cell = cell;
358
359 // Handle when the view element is remove from the page.
360 var that = this;
361 view.$el.on("remove", function () {
362 var index = that.views.indexOf(view);
363 if (index > -1) {
364 that.views.splice(index, 1);
365 }
366 view.remove(); // Clean-up view
367
368 // Close the comm if there are no views left.
369 if (that.views.length() === 0) {
370 if (that._close_callback) {
371 try {
372 that._close_callback(that);
373 } catch (e) {
374 console.log("Exception in widget model close callback", e, that);
375 }
376 }
377
378 if (that._has_comm()) {
379 that.comm.close();
380 delete that.comm.model; // Delete ref so GC will collect widget model.
381 delete that.comm;
382 }
383 delete that.widget_id; // Delete id from model so widget manager cleans up.
384 }
385 });
386 return view;
387 }
388 return null;
389 },
390
391
392 // Build a callback dict.
172 // Build a callback dict.
393 _make_callbacks: function (cell) {
173 cell_callbacks: function (cell) {
394 var callbacks = {};
174 var callbacks = {};
175 console.log('cell_callbacks A', cell);
176 if (cell === undefined) {
177 // Used the last modified view as the sender of the message. This
178 // will insure that any python code triggered by the sent message
179 // can create and display widgets and output.
180 if (this.last_modified_view !== undefined &&
181 this.last_modified_view.cell !== undefined) {
182 cell = this.last_modified_view.cell;
183 } else {
184 cell = null;
185 }
186 }
187 console.log('cell_callbacks B', cell);
395 if (cell !== null) {
188 if (cell !== null) {
396
189
397 // Try to get output handlers
190 // Try to get output handlers
398 var handle_output = null;
191 var handle_output = null;
399 var handle_clear_output = null;
192 var handle_clear_output = null;
400 if (cell.output_area !== undefined && cell.output_area !== null) {
193 if (cell.output_area !== undefined && cell.output_area !== null) {
401 handle_output = $.proxy(cell.output_area.handle_output, cell.output_area);
194 handle_output = $.proxy(cell.output_area.handle_output, cell.output_area);
402 handle_clear_output = $.proxy(cell.output_area.handle_clear_output, cell.output_area);
195 handle_clear_output = $.proxy(cell.output_area.handle_clear_output, cell.output_area);
403 }
196 }
404
197
405 // Create callback dict usign what is known
198 // Create callback dict using what is known
406 var that = this;
199 var that = this;
407 callbacks = {
200 callbacks = {
408 iopub : {
201 iopub : {
409 output : handle_output,
202 output : handle_output,
410 clear_output : handle_clear_output,
203 clear_output : handle_clear_output,
411
204
412 status : function (msg) {
205 status : function (msg) {
413 that._handle_status(cell, msg);
206 that._handle_status(msg, that.cell_callbacks(cell));
414 },
207 },
415
208
416 // Special function only registered by widget messages.
209 // Special function only registered by widget messages.
417 // Allows us to get the cell for a message so we know
210 // Allows us to get the cell for a message so we know
418 // where to add widgets if the code requires it.
211 // where to add widgets if the code requires it.
419 get_cell : function () {
212 get_cell : function () {
420 return cell;
213 return cell;
421 },
214 },
422 },
215 },
423 };
216 };
424 }
217 }
218 console.log('constructed callbacks for',cell);
425 return callbacks;
219 return callbacks;
426 },
220 },
221 });
427
222
428
223
429 // Get the output area corresponding to the msg_id.
224 //--------------------------------------------------------------------
430 // cell is an instance of IPython.Cell
225 // WidgetView class
431 _get_msg_cell: function (msg_id) {
226 //--------------------------------------------------------------------
432
227 var BaseWidgetView = Backbone.View.extend({
433 // First, check to see if the msg was triggered by cell execution.
228 initialize: function(options) {
434 var cell = this.widget_manager.get_msg_cell(msg_id);
229 this.model.on('change',this.update,this);
435 if (cell !== null) {
230 this.widget_manager = options.widget_manager;
436 return cell;
231 this.comm_manager = options.widget_manager.comm_manager;
437 }
232 this.cell = options.cell
233 this.render();
234 // jng: maybe the following shouldn't be automatic---maybe the render method should take
235 // care of knowing what needs to be added as a child?
236 var children_attr = this.model.get('_children_attr');
237 for (var i in children_attr) {
238 var child_attr = children_attr[i];
239 var child_model = this.comm_manager.comms[this.model.get(child_attr)].model;
240 var child_view_name = this.child_view_name(child_attr, child_model);
241 var child_view = this.widget_manager.create_view(child_model, child_view_name, this.cell);
242 this.add_child_view(child_attr, child_view);
243 }
244 var children_lists_attr = this.model.get('_children_lists_attr')
245 for (var i in children_lists_attr) {
246 var child_attr = children_lists_attr[i];
247 var child_list = this.model.get(child_attr);
248 for (var j in child_list) {
249 var child_model = this.comm_manager.comms[child_list[j]].model;
250 var child_view_name = this.child_view_name(child_attr, child_model);
251 var child_view = this.widget_manager.create_view(child_model, child_view_name, this.cell);
252 this.add_child_view(child_attr, child_view);
253 }
254 }
255 },
256 update: function(){
257 // update thyself to be consistent with this.model
258 },
259
260 child_view: function(attr, viewname) {
261 var child_model = this.comm_manager.comms[this.model.get(attr)].model;
262 var child_view = this.widget_manager.create_view(child_model, view_name, this.cell);
263 return child_view;
264 },
265
266 render: function(){
267 // render thyself
268 },
269 child_view_name: function(attr, model) {
270 // attr is the name of the attribute we are constructing a view for
271 // model is the model stored in that attribute
272 // return a valid view_name to construct a view of that type
273 // or null for the default view for the model
274 return null;
275 },
276 add_child_view: function(attr, view) {
277 //attr is the name of the attribute containing a reference to this child
278 //view is the child view that has been constructed
279 //typically this will just add the child view's view.el attribute to some dom element
280 },
281
282 send: function (content) {
283 this.model.send(content, this.model.cell_callbacks(this.cell));
284 },
438
285
439 // Second, check to see if a get_cell callback was defined
286 touch: function () {
440 // for the message. get_cell callbacks are registered for
287 this.model.last_modified_view = this;
441 // widget messages, so this block is actually checking to see if the
288 this.model.save(this.model.changedAttributes(), {patch: true});
442 // message was triggered by a widget.
443 var kernel = this.widget_manager.get_kernel();
444 if (kernel !== undefined && kernel !== null) {
445 var callbacks = kernel.get_callbacks_for_msg(msg_id);
446 if (callbacks !== undefined &&
447 callbacks.iopub !== undefined &&
448 callbacks.iopub.get_cell !== undefined) {
449
450 return callbacks.iopub.get_cell();
451 }
452 }
453
454 // Not triggered by a cell or widget (no get_cell callback
455 // exists).
456 return null;
457 },
289 },
458
290
459
291
460 // Function that checks if a comm has been attached to this widget
461 // model. Returns True if a valid comm is attached.
462 _has_comm: function() {
463 return this.comm !== undefined && this.comm !== null;
464 },
465 });
292 });
466
293
467
294 var WidgetView = BaseWidgetView.extend({
468 //--------------------------------------------------------------------
295 initialize: function (options) {
469 // WidgetView class
470 //--------------------------------------------------------------------
471 var WidgetView = Backbone.View.extend({
472
473 initialize: function () {
474 this.visible = true;
296 this.visible = true;
475 this.model.on('sync',this.update,this);
297 BaseWidgetView.prototype.initialize.apply(this, arguments);
476 },
298 },
477
299
478 add_class: function (selector, class_list) {
300 add_class: function (selector, class_list) {
479 var elements = this._get_selector_element(selector);
301 var elements = this._get_selector_element(selector);
480 if (elements.length > 0) {
302 if (elements.length > 0) {
481 elements.addClass(class_list);
303 elements.addClass(class_list);
482 }
304 }
483 },
305 },
484
306
485 remove_class: function (selector, class_list) {
307 remove_class: function (selector, class_list) {
486 var elements = this._get_selector_element(selector);
308 var elements = this._get_selector_element(selector);
487 if (elements.length > 0) {
309 if (elements.length > 0) {
488 elements.removeClass(class_list);
310 elements.removeClass(class_list);
489 }
311 }
490 },
312 },
491
313
492
493 send: function (content) {
494 this.model.send(content, this.cell);
495 },
496
497
498 touch: function () {
499 this.model.last_modified_view = this;
500 this.model.save(this.model.changedAttributes(), {patch: true});
501 },
502
503 update: function () {
314 update: function () {
504 if (this.model.get('visible') !== undefined) {
315 // jng: hook into change:visible trigger
505 if (this.visible != this.model.get('visible')) {
316 var visible = this.model.get('visible');
506 this.visible = this.model.get('visible');
317 if (visible !== undefined && this.visible !== visible) {
507 if (this.visible) {
318 this.visible = visible;
508 this.$el.show();
319 this.$el.toggle(visible)
509 } else {
510 this.$el.hide();
511 }
512 }
513 }
320 }
514
321
515 if (this.model.css !== undefined) {
322 if (this.model.css !== undefined) {
516 for (var selector in this.model.css) {
323 for (var selector in this.model.css) {
517 if (this.model.css.hasOwnProperty(selector)) {
324 if (this.model.css.hasOwnProperty(selector)) {
518
325
519 // Apply the css traits to all elements that match the selector.
326 // Apply the css traits to all elements that match the selector.
520 var elements = this._get_selector_element(selector);
327 var elements = this._get_selector_element(selector);
521 if (elements.length > 0) {
328 if (elements.length > 0) {
522 var css_traits = this.model.css[selector];
329 var css_traits = this.model.css[selector];
523 for (var css_key in css_traits) {
330 for (var css_key in css_traits) {
524 if (css_traits.hasOwnProperty(css_key)) {
331 if (css_traits.hasOwnProperty(css_key)) {
525 elements.css(css_key, css_traits[css_key]);
332 elements.css(css_key, css_traits[css_key]);
526 }
333 }
527 }
334 }
528 }
335 }
529 }
336 }
530 }
337 }
531 }
338 }
532 },
339 },
533
340
534 _get_selector_element: function (selector) {
341 _get_selector_element: function (selector) {
535 // Get the elements via the css selector. If the selector is
342 // Get the elements via the css selector. If the selector is
536 // blank, apply the style to the $el_to_style element. If
343 // blank, apply the style to the $el_to_style element. If
537 // the $el_to_style element is not defined, use apply the
344 // the $el_to_style element is not defined, use apply the
538 // style to the view's element.
345 // style to the view's element.
539 var elements = this.$el.find(selector);
346 var elements = this.$el.find(selector);
540 if (selector === undefined || selector === null || selector === '') {
347 if (selector === undefined || selector === null || selector === '') {
541 if (this.$el_to_style === undefined) {
348 if (this.$el_to_style === undefined) {
542 elements = this.$el;
349 elements = this.$el;
543 } else {
350 } else {
544 elements = this.$el_to_style;
351 elements = this.$el_to_style;
545 }
352 }
546 }
353 }
547 return elements;
354 return elements;
548 },
355 },
549 });
356 });
550
357
551 IPython.WidgetModel = WidgetModel;
358 IPython.WidgetModel = WidgetModel;
552 IPython.WidgetView = WidgetView;
359 IPython.WidgetView = WidgetView;
553
360
554 return widget_manager;
361 return widget_manager;
555 }); No newline at end of file
362 });
@@ -1,259 +1,266 b''
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2013 The IPython Development Team
2 // Copyright (C) 2013 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 //============================================================================
8 //============================================================================
9 // ContainerWidget
9 // ContainerWidget
10 //============================================================================
10 //============================================================================
11
11
12 /**
12 /**
13 * @module IPython
13 * @module IPython
14 * @namespace IPython
14 * @namespace IPython
15 **/
15 **/
16
16
17 define(["notebook/js/widgets/base"], function(widget_manager) {
17 define(["notebook/js/widgets/base"], function(widget_manager) {
18
18
19 var set_flex_property = function(element, property_name, enabled) {
19 var set_flex_property = function(element, property_name, enabled) {
20 if (enabled) {
20 if (enabled) {
21 element.addClass(property_name);
21 element.addClass(property_name);
22 } else {
22 } else {
23 element.removeClass(property_name);
23 element.removeClass(property_name);
24 }
24 }
25 };
25 };
26
26
27 var set_flex_properties = function(context, element) {
27 var set_flex_properties = function(context, element) {
28
28
29 // Apply flexible box model properties by adding and removing
29 // Apply flexible box model properties by adding and removing
30 // corrosponding CSS classes.
30 // corrosponding CSS classes.
31 // Defined in IPython/html/static/base/less/flexbox.less
31 // Defined in IPython/html/static/base/less/flexbox.less
32 set_flex_property(element, 'vbox', context.model.get('_vbox'));
32 set_flex_property(element, 'vbox', context.model.get('_vbox'));
33 set_flex_property(element, 'hbox', context.model.get('_hbox'));
33 set_flex_property(element, 'hbox', context.model.get('_hbox'));
34 set_flex_property(element, 'start', context.model.get('_pack_start'));
34 set_flex_property(element, 'start', context.model.get('_pack_start'));
35 set_flex_property(element, 'center', context.model.get('_pack_center'));
35 set_flex_property(element, 'center', context.model.get('_pack_center'));
36 set_flex_property(element, 'end', context.model.get('_pack_end'));
36 set_flex_property(element, 'end', context.model.get('_pack_end'));
37 set_flex_property(element, 'align-start', context.model.get('_align_start'));
37 set_flex_property(element, 'align-start', context.model.get('_align_start'));
38 set_flex_property(element, 'align-center', context.model.get('_align_center'));
38 set_flex_property(element, 'align-center', context.model.get('_align_center'));
39 set_flex_property(element, 'align-end', context.model.get('_align_end'));
39 set_flex_property(element, 'align-end', context.model.get('_align_end'));
40 set_flex_property(element, 'box-flex0', context.model.get('_flex0'));
40 set_flex_property(element, 'box-flex0', context.model.get('_flex0'));
41 set_flex_property(element, 'box-flex1', context.model.get('_flex1'));
41 set_flex_property(element, 'box-flex1', context.model.get('_flex1'));
42 set_flex_property(element, 'box-flex2', context.model.get('_flex2'));
42 set_flex_property(element, 'box-flex2', context.model.get('_flex2'));
43 };
43 };
44
44
45
45
46
46
47 var ContainerModel = IPython.WidgetModel.extend({});
47 var ContainerModel = IPython.WidgetModel.extend({});
48 widget_manager.register_widget_model('ContainerWidgetModel', ContainerModel);
48 widget_manager.register_widget_model('ContainerWidgetModel', ContainerModel);
49
49
50 var ContainerView = IPython.WidgetView.extend({
50 var ContainerView = IPython.WidgetView.extend({
51
51
52 render: function(){
52 render: function(){
53 this.$el
53 this.$el
54 .addClass('widget-container');
54 .addClass('widget-container');
55 var children = this.model.get('children')
56 for(var i in this.model.get('children')) {
57
58 this.update()
55 },
59 },
56
60
57 update: function(){
61 update: function(){
58 set_flex_properties(this, this.$el);
62 set_flex_properties(this, this.$el);
59 return IPython.WidgetView.prototype.update.call(this);
63 return IPython.WidgetView.prototype.update.call(this);
60 },
64 },
61
65 add_child_view: function(attr, view) {
62 display_child: function(view) {
66 if (attr==='children') {
63 this.$el.append(view.$el);
67 this.$el.append(view.$el);
64 },
68 }
69 }
65 });
70 });
66
71
67 widget_manager.register_widget_view('ContainerView', ContainerView);
72 widget_manager.register_widget_view('ContainerView', ContainerView);
68
73
69
74
70 var ModalView = IPython.WidgetView.extend({
75 var ModalView = IPython.WidgetView.extend({
71
76
72 render: function(){
77 render: function(){
73 var that = this;
78 var that = this;
74 this.$el
79 this.$el
75 .html('')
80 .html('')
76 .on("remove", function(){
81 .on("remove", function(){
77 that.$window.remove();
82 that.$window.remove();
78 });
83 });
79 this.$window = $('<div />')
84 this.$window = $('<div />')
80 .addClass('modal widget-modal')
85 .addClass('modal widget-modal')
81 .appendTo($('#notebook-container'))
86 .appendTo($('#notebook-container'))
82 .mousedown(function(){
87 .mousedown(function(){
83 that.bring_to_front();
88 that.bring_to_front();
84 });
89 });
85 this.$title_bar = $('<div />')
90 this.$title_bar = $('<div />')
86 .addClass('popover-title')
91 .addClass('popover-title')
87 .appendTo(this.$window)
92 .appendTo(this.$window)
88 .mousedown(function(){
93 .mousedown(function(){
89 that.bring_to_front();
94 that.bring_to_front();
90 });
95 });
91 this.$close = $('<button />')
96 this.$close = $('<button />')
92 .addClass('close icon-remove')
97 .addClass('close icon-remove')
93 .css('margin-left', '5px')
98 .css('margin-left', '5px')
94 .appendTo(this.$title_bar)
99 .appendTo(this.$title_bar)
95 .click(function(){
100 .click(function(){
96 that.hide();
101 that.hide();
97 event.stopPropagation();
102 event.stopPropagation();
98 });
103 });
99 this.$minimize = $('<button />')
104 this.$minimize = $('<button />')
100 .addClass('close icon-arrow-down')
105 .addClass('close icon-arrow-down')
101 .appendTo(this.$title_bar)
106 .appendTo(this.$title_bar)
102 .click(function(){
107 .click(function(){
103 that.popped_out = !that.popped_out;
108 that.popped_out = !that.popped_out;
104 if (!that.popped_out) {
109 if (!that.popped_out) {
105 that.$minimize
110 that.$minimize
106 .removeClass('icon-arrow-down')
111 .removeClass('icon-arrow-down')
107 .addClass('icon-arrow-up');
112 .addClass('icon-arrow-up');
108
113
109 that.$window
114 that.$window
110 .draggable('destroy')
115 .draggable('destroy')
111 .resizable('destroy')
116 .resizable('destroy')
112 .removeClass('widget-modal modal')
117 .removeClass('widget-modal modal')
113 .addClass('docked-widget-modal')
118 .addClass('docked-widget-modal')
114 .detach()
119 .detach()
115 .insertBefore(that.$show_button);
120 .insertBefore(that.$show_button);
116 that.$show_button.hide();
121 that.$show_button.hide();
117 that.$close.hide();
122 that.$close.hide();
118 } else {
123 } else {
119 that.$minimize
124 that.$minimize
120 .addClass('icon-arrow-down')
125 .addClass('icon-arrow-down')
121 .removeClass('icon-arrow-up');
126 .removeClass('icon-arrow-up');
122
127
123 that.$window
128 that.$window
124 .removeClass('docked-widget-modal')
129 .removeClass('docked-widget-modal')
125 .addClass('widget-modal modal')
130 .addClass('widget-modal modal')
126 .detach()
131 .detach()
127 .appendTo($('#notebook-container'))
132 .appendTo($('#notebook-container'))
128 .draggable({handle: '.popover-title', snap: '#notebook, .modal', snapMode: 'both'})
133 .draggable({handle: '.popover-title', snap: '#notebook, .modal', snapMode: 'both'})
129 .resizable()
134 .resizable()
130 .children('.ui-resizable-handle').show();
135 .children('.ui-resizable-handle').show();
131 that.show();
136 that.show();
132 that.$show_button.show();
137 that.$show_button.show();
133 that.$close.show();
138 that.$close.show();
134 }
139 }
135 event.stopPropagation();
140 event.stopPropagation();
136 });
141 });
137 this.$title = $('<div />')
142 this.$title = $('<div />')
138 .addClass('widget-modal-title')
143 .addClass('widget-modal-title')
139 .html('&nbsp;')
144 .html('&nbsp;')
140 .appendTo(this.$title_bar);
145 .appendTo(this.$title_bar);
141 this.$body = $('<div />')
146 this.$body = $('<div />')
142 .addClass('modal-body')
147 .addClass('modal-body')
143 .addClass('widget-modal-body')
148 .addClass('widget-modal-body')
144 .addClass('widget-container')
149 .addClass('widget-container')
145 .appendTo(this.$window);
150 .appendTo(this.$window);
146
151
147 this.$show_button = $('<button />')
152 this.$show_button = $('<button />')
148 .html('&nbsp;')
153 .html('&nbsp;')
149 .addClass('btn btn-info widget-modal-show')
154 .addClass('btn btn-info widget-modal-show')
150 .appendTo(this.$el)
155 .appendTo(this.$el)
151 .click(function(){
156 .click(function(){
152 that.show();
157 that.show();
153 });
158 });
154
159
155 this.$window.draggable({handle: '.popover-title', snap: '#notebook, .modal', snapMode: 'both'});
160 this.$window.draggable({handle: '.popover-title', snap: '#notebook, .modal', snapMode: 'both'});
156 this.$window.resizable();
161 this.$window.resizable();
157 this.$window.on('resize', function(){
162 this.$window.on('resize', function(){
158 that.$body.outerHeight(that.$window.innerHeight() - that.$title_bar.outerHeight());
163 that.$body.outerHeight(that.$window.innerHeight() - that.$title_bar.outerHeight());
159 });
164 });
160
165
161 this.$el_to_style = this.$body;
166 this.$el_to_style = this.$body;
162 this._shown_once = false;
167 this._shown_once = false;
163 this.popped_out = true;
168 this.popped_out = true;
164 },
169 },
165
170
166 hide: function() {
171 hide: function() {
167 this.$window.hide();
172 this.$window.hide();
168 this.$show_button.removeClass('btn-info');
173 this.$show_button.removeClass('btn-info');
169 },
174 },
170
175
171 show: function() {
176 show: function() {
172 this.$show_button.addClass('btn-info');
177 this.$show_button.addClass('btn-info');
173
178
174 this.$window.show();
179 this.$window.show();
175 if (this.popped_out) {
180 if (this.popped_out) {
176 this.$window.css("positon", "absolute");
181 this.$window.css("positon", "absolute");
177 this.$window.css("top", "0px");
182 this.$window.css("top", "0px");
178 this.$window.css("left", Math.max(0, (($('body').outerWidth() - this.$window.outerWidth()) / 2) +
183 this.$window.css("left", Math.max(0, (($('body').outerWidth() - this.$window.outerWidth()) / 2) +
179 $(window).scrollLeft()) + "px");
184 $(window).scrollLeft()) + "px");
180 this.bring_to_front();
185 this.bring_to_front();
181 }
186 }
182 },
187 },
183
188
184 bring_to_front: function() {
189 bring_to_front: function() {
185 var $widget_modals = $(".widget-modal");
190 var $widget_modals = $(".widget-modal");
186 var max_zindex = 0;
191 var max_zindex = 0;
187 $widget_modals.each(function (index, el){
192 $widget_modals.each(function (index, el){
188 max_zindex = Math.max(max_zindex, parseInt($(el).css('z-index')));
193 max_zindex = Math.max(max_zindex, parseInt($(el).css('z-index')));
189 });
194 });
190
195
191 // Start z-index of widget modals at 2000
196 // Start z-index of widget modals at 2000
192 max_zindex = Math.max(max_zindex, 2000);
197 max_zindex = Math.max(max_zindex, 2000);
193
198
194 $widget_modals.each(function (index, el){
199 $widget_modals.each(function (index, el){
195 $el = $(el);
200 $el = $(el);
196 if (max_zindex == parseInt($el.css('z-index'))) {
201 if (max_zindex == parseInt($el.css('z-index'))) {
197 $el.css('z-index', max_zindex - 1);
202 $el.css('z-index', max_zindex - 1);
198 }
203 }
199 });
204 });
200 this.$window.css('z-index', max_zindex);
205 this.$window.css('z-index', max_zindex);
201 },
206 },
202
207
203 update: function(){
208 update: function(){
204 set_flex_properties(this, this.$body);
209 set_flex_properties(this, this.$body);
205
210
206 var description = this.model.get('description');
211 var description = this.model.get('description');
207 description = description.replace(/ /g, '&nbsp;', 'm');
212 description = description.replace(/ /g, '&nbsp;', 'm');
208 description = description.replace(/\n/g, '<br>\n', 'm');
213 description = description.replace(/\n/g, '<br>\n', 'm');
209 if (description.length === 0) {
214 if (description.length === 0) {
210 this.$title.html('&nbsp;'); // Preserve title height
215 this.$title.html('&nbsp;'); // Preserve title height
211 } else {
216 } else {
212 this.$title.html(description);
217 this.$title.html(description);
213 }
218 }
214
219
215 var button_text = this.model.get('button_text');
220 var button_text = this.model.get('button_text');
216 button_text = button_text.replace(/ /g, '&nbsp;', 'm');
221 button_text = button_text.replace(/ /g, '&nbsp;', 'm');
217 button_text = button_text.replace(/\n/g, '<br>\n', 'm');
222 button_text = button_text.replace(/\n/g, '<br>\n', 'm');
218 if (button_text.length === 0) {
223 if (button_text.length === 0) {
219 this.$show_button.html('&nbsp;'); // Preserve button height
224 this.$show_button.html('&nbsp;'); // Preserve button height
220 } else {
225 } else {
221 this.$show_button.html(button_text);
226 this.$show_button.html(button_text);
222 }
227 }
223
228
224 if (!this._shown_once) {
229 if (!this._shown_once) {
225 this._shown_once = true;
230 this._shown_once = true;
226 this.show();
231 this.show();
227 }
232 }
228
233
229 return IPython.WidgetView.prototype.update.call(this);
234 return IPython.WidgetView.prototype.update.call(this);
230 },
235 },
231
236
232 display_child: function(view) {
237 add_child_view: function(attr, view) {
233 this.$body.append(view.$el);
238 if (attr==='children') {
239 this.$body.append(view.$el);
240 }
234 },
241 },
235
242
236 _get_selector_element: function(selector) {
243 _get_selector_element: function(selector) {
237
244
238 // Since the modal actually isn't within the $el in the DOM, we need to extend
245 // Since the modal actually isn't within the $el in the DOM, we need to extend
239 // the selector logic to allow the user to set css on the modal if need be.
246 // the selector logic to allow the user to set css on the modal if need be.
240 // The convention used is:
247 // The convention used is:
241 // "modal" - select the modal div
248 // "modal" - select the modal div
242 // "modal [selector]" - select element(s) within the modal div.
249 // "modal [selector]" - select element(s) within the modal div.
243 // "[selector]" - select elements within $el
250 // "[selector]" - select elements within $el
244 // "" - select the $el_to_style
251 // "" - select the $el_to_style
245 if (selector.substring(0, 5) == 'modal') {
252 if (selector.substring(0, 5) == 'modal') {
246 if (selector == 'modal') {
253 if (selector == 'modal') {
247 return this.$window;
254 return this.$window;
248 } else {
255 } else {
249 return this.$window.find(selector.substring(6));
256 return this.$window.find(selector.substring(6));
250 }
257 }
251 } else {
258 } else {
252 return IPython.WidgetView.prototype._get_selector_element.call(this, selector);
259 return IPython.WidgetView.prototype._get_selector_element.call(this, selector);
253 }
260 }
254 },
261 },
255
262
256 });
263 });
257
264
258 widget_manager.register_widget_view('ModalView', ModalView);
265 widget_manager.register_widget_view('ModalView', ModalView);
259 });
266 });
@@ -1,255 +1,257 b''
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2013 The IPython Development Team
2 // Copyright (C) 2013 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 //============================================================================
8 //============================================================================
9 // FloatRangeWidget
9 // FloatRangeWidget
10 //============================================================================
10 //============================================================================
11
11
12 /**
12 /**
13 * @module IPython
13 * @module IPython
14 * @namespace IPython
14 * @namespace IPython
15 **/
15 **/
16
16
17 define(["notebook/js/widgets/base"], function(widget_manager){
17 define(["notebook/js/widgets/base"], function(widget_manager){
18 var FloatRangeWidgetModel = IPython.WidgetModel.extend({});
18 var FloatRangeWidgetModel = IPython.WidgetModel.extend({});
19 widget_manager.register_widget_model('FloatRangeWidgetModel', FloatRangeWidgetModel);
19 widget_manager.register_widget_model('FloatRangeWidgetModel', FloatRangeWidgetModel);
20
20
21 var FloatSliderView = IPython.WidgetView.extend({
21 var FloatSliderView = IPython.WidgetView.extend({
22
22
23 // Called when view is rendered.
23 // Called when view is rendered.
24 render : function(){
24 render : function(){
25 this.$el
25 this.$el
26 .addClass('widget-hbox-single')
26 .addClass('widget-hbox-single')
27 .html('');
27 .html('');
28 this.$label = $('<div />')
28 this.$label = $('<div />')
29 .appendTo(this.$el)
29 .appendTo(this.$el)
30 .addClass('widget-hlabel')
30 .addClass('widget-hlabel')
31 .hide();
31 .hide();
32 this.$slider = $('<div />')
32 this.$slider = $('<div />')
33 .slider({})
33 .slider({})
34 .addClass('slider');
34 .addClass('slider');
35
35
36 // Put the slider in a container
36 // Put the slider in a container
37 this.$slider_container = $('<div />')
37 this.$slider_container = $('<div />')
38 .addClass('widget-hslider')
38 .addClass('widget-hslider')
39 .append(this.$slider);
39 .append(this.$slider);
40 this.$el_to_style = this.$slider_container; // Set default element to style
40 this.$el_to_style = this.$slider_container; // Set default element to style
41 this.$el.append(this.$slider_container);
41 this.$el.append(this.$slider_container);
42
42
43 // Set defaults.
43 // Set defaults.
44 this.update();
44 this.update();
45 },
45 },
46
46
47 // Handles: Backend -> Frontend Sync
47 // Handles: Backend -> Frontend Sync
48 // Frontent -> Frontend Sync
48 // Frontent -> Frontend Sync
49 update : function(){
49 update : function(){
50 // Slider related keys.
50 // Slider related keys.
51 var _keys = ['step', 'max', 'min', 'disabled'];
51 var _keys = ['step', 'max', 'min', 'disabled'];
52 for (var index in _keys) {
52 for (var index in _keys) {
53 var key = _keys[index];
53 var key = _keys[index];
54 if (this.model.get(key) !== undefined) {
54 if (this.model.get(key) !== undefined) {
55 this.$slider.slider("option", key, this.model.get(key));
55 this.$slider.slider("option", key, this.model.get(key));
56 }
56 }
57 }
57 }
58
58
59 // WORKAROUND FOR JQUERY SLIDER BUG.
59 // WORKAROUND FOR JQUERY SLIDER BUG.
60 // The horizontal position of the slider handle
60 // The horizontal position of the slider handle
61 // depends on the value of the slider at the time
61 // depends on the value of the slider at the time
62 // of orientation change. Before applying the new
62 // of orientation change. Before applying the new
63 // workaround, we set the value to the minimum to
63 // workaround, we set the value to the minimum to
64 // make sure that the horizontal placement of the
64 // make sure that the horizontal placement of the
65 // handle in the vertical slider is always
65 // handle in the vertical slider is always
66 // consistent.
66 // consistent.
67 var orientation = this.model.get('orientation');
67 var orientation = this.model.get('orientation');
68 var value = this.model.get('min');
68 var value = this.model.get('min');
69 this.$slider.slider('option', 'value', value);
69 this.$slider.slider('option', 'value', value);
70 this.$slider.slider('option', 'orientation', orientation);
70 this.$slider.slider('option', 'orientation', orientation);
71 value = this.model.get('value');
71 value = this.model.get('value');
72 this.$slider.slider('option', 'value', value);
72 this.$slider.slider('option', 'value', value);
73 console.log('updating value',value)
73
74
74 // Use the right CSS classes for vertical & horizontal sliders
75 // Use the right CSS classes for vertical & horizontal sliders
75 if (orientation=='vertical') {
76 if (orientation=='vertical') {
76 this.$slider_container
77 this.$slider_container
77 .removeClass('widget-hslider')
78 .removeClass('widget-hslider')
78 .addClass('widget-vslider');
79 .addClass('widget-vslider');
79 this.$el
80 this.$el
80 .removeClass('widget-hbox-single')
81 .removeClass('widget-hbox-single')
81 .addClass('widget-vbox-single');
82 .addClass('widget-vbox-single');
82 this.$label
83 this.$label
83 .removeClass('widget-hlabel')
84 .removeClass('widget-hlabel')
84 .addClass('widget-vlabel');
85 .addClass('widget-vlabel');
85
86
86 } else {
87 } else {
87 this.$slider_container
88 this.$slider_container
88 .removeClass('widget-vslider')
89 .removeClass('widget-vslider')
89 .addClass('widget-hslider');
90 .addClass('widget-hslider');
90 this.$el
91 this.$el
91 .removeClass('widget-vbox-single')
92 .removeClass('widget-vbox-single')
92 .addClass('widget-hbox-single');
93 .addClass('widget-hbox-single');
93 this.$label
94 this.$label
94 .removeClass('widget-vlabel')
95 .removeClass('widget-vlabel')
95 .addClass('widget-hlabel');
96 .addClass('widget-hlabel');
96 }
97 }
97
98
98 var description = this.model.get('description');
99 var description = this.model.get('description');
99 if (description.length === 0) {
100 if (description.length === 0) {
100 this.$label.hide();
101 this.$label.hide();
101 } else {
102 } else {
102 this.$label.html(description);
103 this.$label.html(description);
103 this.$label.show();
104 this.$label.show();
104 }
105 }
105 return IPython.WidgetView.prototype.update.call(this);
106 return IPython.WidgetView.prototype.update.call(this);
106 },
107 },
107
108
108 // Handles: User input
109 // Handles: User input
109 events: { "slide" : "handleSliderChange" },
110 events: { "slide" : "handleSliderChange" },
110 handleSliderChange: function(e, ui) {
111 handleSliderChange: function(e, ui) {
111 this.model.set('value', ui.value);
112 this.model.set('value', ui.value);
113 console.log('triggered value change', ui.value, this.model);
112 this.touch();
114 this.touch();
113 },
115 },
114 });
116 });
115
117
116 widget_manager.register_widget_view('FloatSliderView', FloatSliderView);
118 widget_manager.register_widget_view('FloatSliderView', FloatSliderView);
117
119
118
120
119 var FloatTextView = IPython.WidgetView.extend({
121 var FloatTextView = IPython.WidgetView.extend({
120
122
121 // Called when view is rendered.
123 // Called when view is rendered.
122 render : function(){
124 render : function(){
123 this.$el
125 this.$el
124 .addClass('widget-hbox-single')
126 .addClass('widget-hbox-single')
125 .html('');
127 .html('');
126 this.$label = $('<div />')
128 this.$label = $('<div />')
127 .appendTo(this.$el)
129 .appendTo(this.$el)
128 .addClass('widget-hlabel')
130 .addClass('widget-hlabel')
129 .hide();
131 .hide();
130 this.$textbox = $('<input type="text" />')
132 this.$textbox = $('<input type="text" />')
131 .addClass('input')
133 .addClass('input')
132 .addClass('widget-numeric-text')
134 .addClass('widget-numeric-text')
133 .appendTo(this.$el);
135 .appendTo(this.$el);
134 this.$el_to_style = this.$textbox; // Set default element to style
136 this.$el_to_style = this.$textbox; // Set default element to style
135 this.update(); // Set defaults.
137 this.update(); // Set defaults.
136 },
138 },
137
139
138 // Handles: Backend -> Frontend Sync
140 // Handles: Backend -> Frontend Sync
139 // Frontent -> Frontend Sync
141 // Frontent -> Frontend Sync
140 update : function(){
142 update : function(){
141 var value = this.model.get('value');
143 var value = this.model.get('value');
142 if (!this.changing && parseFloat(this.$textbox.val()) != value) {
144 if (!this.changing && parseFloat(this.$textbox.val()) != value) {
143 this.$textbox.val(value);
145 this.$textbox.val(value);
144 }
146 }
145
147
146 if (this.model.get('disabled')) {
148 if (this.model.get('disabled')) {
147 this.$textbox.attr('disabled','disabled');
149 this.$textbox.attr('disabled','disabled');
148 } else {
150 } else {
149 this.$textbox.removeAttr('disabled');
151 this.$textbox.removeAttr('disabled');
150 }
152 }
151
153
152 var description = this.model.get('description');
154 var description = this.model.get('description');
153 if (description.length === 0) {
155 if (description.length === 0) {
154 this.$label.hide();
156 this.$label.hide();
155 } else {
157 } else {
156 this.$label.html(description);
158 this.$label.html(description);
157 this.$label.show();
159 this.$label.show();
158 }
160 }
159 return IPython.WidgetView.prototype.update.call(this);
161 return IPython.WidgetView.prototype.update.call(this);
160 },
162 },
161
163
162
164
163 events: {"keyup input" : "handleChanging",
165 events: {"keyup input" : "handleChanging",
164 "paste input" : "handleChanging",
166 "paste input" : "handleChanging",
165 "cut input" : "handleChanging",
167 "cut input" : "handleChanging",
166 "change input" : "handleChanged"}, // Fires only when control is validated or looses focus.
168 "change input" : "handleChanged"}, // Fires only when control is validated or looses focus.
167
169
168 // Handles and validates user input.
170 // Handles and validates user input.
169 handleChanging: function(e) {
171 handleChanging: function(e) {
170
172
171 // Try to parse value as a float.
173 // Try to parse value as a float.
172 var numericalValue = 0.0;
174 var numericalValue = 0.0;
173 if (e.target.value !== '') {
175 if (e.target.value !== '') {
174 numericalValue = parseFloat(e.target.value);
176 numericalValue = parseFloat(e.target.value);
175 }
177 }
176
178
177 // If parse failed, reset value to value stored in model.
179 // If parse failed, reset value to value stored in model.
178 if (isNaN(numericalValue)) {
180 if (isNaN(numericalValue)) {
179 e.target.value = this.model.get('value');
181 e.target.value = this.model.get('value');
180 } else if (!isNaN(numericalValue)) {
182 } else if (!isNaN(numericalValue)) {
181 if (this.model.get('max') !== undefined) {
183 if (this.model.get('max') !== undefined) {
182 numericalValue = Math.min(this.model.get('max'), numericalValue);
184 numericalValue = Math.min(this.model.get('max'), numericalValue);
183 }
185 }
184 if (this.model.get('min') !== undefined) {
186 if (this.model.get('min') !== undefined) {
185 numericalValue = Math.max(this.model.get('min'), numericalValue);
187 numericalValue = Math.max(this.model.get('min'), numericalValue);
186 }
188 }
187
189
188 // Apply the value if it has changed.
190 // Apply the value if it has changed.
189 if (numericalValue != this.model.get('value')) {
191 if (numericalValue != this.model.get('value')) {
190 this.changing = true;
192 this.changing = true;
191 this.model.set('value', numericalValue);
193 this.model.set('value', numericalValue);
192 this.touch();
194 this.touch();
193 this.changing = false;
195 this.changing = false;
194 }
196 }
195 }
197 }
196 },
198 },
197
199
198 // Applies validated input.
200 // Applies validated input.
199 handleChanged: function(e) {
201 handleChanged: function(e) {
200 // Update the textbox
202 // Update the textbox
201 if (this.model.get('value') != e.target.value) {
203 if (this.model.get('value') != e.target.value) {
202 e.target.value = this.model.get('value');
204 e.target.value = this.model.get('value');
203 }
205 }
204 }
206 }
205 });
207 });
206
208
207 widget_manager.register_widget_view('FloatTextView', FloatTextView);
209 widget_manager.register_widget_view('FloatTextView', FloatTextView);
208
210
209
211
210 var ProgressView = IPython.WidgetView.extend({
212 var ProgressView = IPython.WidgetView.extend({
211
213
212 // Called when view is rendered.
214 // Called when view is rendered.
213 render : function(){
215 render : function(){
214 this.$el
216 this.$el
215 .addClass('widget-hbox-single')
217 .addClass('widget-hbox-single')
216 .html('');
218 .html('');
217 this.$label = $('<div />')
219 this.$label = $('<div />')
218 .appendTo(this.$el)
220 .appendTo(this.$el)
219 .addClass('widget-hlabel')
221 .addClass('widget-hlabel')
220 .hide();
222 .hide();
221 this.$progress = $('<div />')
223 this.$progress = $('<div />')
222 .addClass('progress')
224 .addClass('progress')
223 .addClass('widget-progress')
225 .addClass('widget-progress')
224 .appendTo(this.$el);
226 .appendTo(this.$el);
225 this.$el_to_style = this.$progress; // Set default element to style
227 this.$el_to_style = this.$progress; // Set default element to style
226 this.$bar = $('<div />')
228 this.$bar = $('<div />')
227 .addClass('bar')
229 .addClass('bar')
228 .css('width', '50%')
230 .css('width', '50%')
229 .appendTo(this.$progress);
231 .appendTo(this.$progress);
230 this.update(); // Set defaults.
232 this.update(); // Set defaults.
231 },
233 },
232
234
233 // Handles: Backend -> Frontend Sync
235 // Handles: Backend -> Frontend Sync
234 // Frontent -> Frontend Sync
236 // Frontent -> Frontend Sync
235 update : function(){
237 update : function(){
236 var value = this.model.get('value');
238 var value = this.model.get('value');
237 var max = this.model.get('max');
239 var max = this.model.get('max');
238 var min = this.model.get('min');
240 var min = this.model.get('min');
239 var percent = 100.0 * (value - min) / (max - min);
241 var percent = 100.0 * (value - min) / (max - min);
240 this.$bar.css('width', percent + '%');
242 this.$bar.css('width', percent + '%');
241
243
242 var description = this.model.get('description');
244 var description = this.model.get('description');
243 if (description.length === 0) {
245 if (description.length === 0) {
244 this.$label.hide();
246 this.$label.hide();
245 } else {
247 } else {
246 this.$label.html(description);
248 this.$label.html(description);
247 this.$label.show();
249 this.$label.show();
248 }
250 }
249 return IPython.WidgetView.prototype.update.call(this);
251 return IPython.WidgetView.prototype.update.call(this);
250 },
252 },
251
253
252 });
254 });
253
255
254 widget_manager.register_widget_view('ProgressView', ProgressView);
256 widget_manager.register_widget_view('ProgressView', ProgressView);
255 });
257 });
@@ -1,179 +1,183 b''
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2013 The IPython Development Team
2 // Copyright (C) 2013 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 //============================================================================
8 //============================================================================
9 // MultiContainerWidget
9 // MultiContainerWidget
10 //============================================================================
10 //============================================================================
11
11
12 /**
12 /**
13 * @module IPython
13 * @module IPython
14 * @namespace IPython
14 * @namespace IPython
15 **/
15 **/
16
16
17 define(["notebook/js/widgets/base"], function(widget_manager){
17 define(["notebook/js/widgets/base"], function(widget_manager){
18 var MulticontainerModel = IPython.WidgetModel.extend({});
18 var MulticontainerModel = IPython.WidgetModel.extend({});
19 widget_manager.register_widget_model('MulticontainerWidgetModel', MulticontainerModel);
19 widget_manager.register_widget_model('MulticontainerWidgetModel', MulticontainerModel);
20
20
21 var AccordionView = IPython.WidgetView.extend({
21 var AccordionView = IPython.WidgetView.extend({
22
22
23 render: function(){
23 render: function(){
24 var guid = 'accordion' + IPython.utils.uuid();
24 var guid = 'accordion' + IPython.utils.uuid();
25 this.$el
25 this.$el
26 .attr('id', guid)
26 .attr('id', guid)
27 .addClass('accordion');
27 .addClass('accordion');
28 this.containers = [];
28 this.containers = [];
29 },
29 },
30
31 update: function() {
30 update: function() {
32 // Set tab titles
31 // Set tab titles
33 var titles = this.model.get('_titles');
32 var titles = this.model.get('_titles');
34 for (var page_index in titles) {
33 for (var page_index in titles) {
35
34
36 var accordian = this.containers[page_index];
35 var accordian = this.containers[page_index];
37 if (accordian !== undefined) {
36 if (accordian !== undefined) {
38 accordian
37 accordian
39 .find('.accordion-heading')
38 .find('.accordion-heading')
40 .find('.accordion-toggle')
39 .find('.accordion-toggle')
41 .html(titles[page_index]);
40 .html(titles[page_index]);
42 }
41 }
43 }
42 }
44
43
45 // Set selected page
44 // Set selected page
46 var selected_index = this.model.get("selected_index");
45 var selected_index = this.model.get("selected_index");
47 if (0 <= selected_index && selected_index < this.containers.length) {
46 if (0 <= selected_index && selected_index < this.containers.length) {
48 for (var index in this.containers) {
47 for (var index in this.containers) {
49 if (index==selected_index) {
48 if (index==selected_index) {
50 this.containers[index].find('.accordion-body').collapse('show');
49 this.containers[index].find('.accordion-body').collapse('show');
51 } else {
50 } else {
52 this.containers[index].find('.accordion-body').collapse('hide');
51 this.containers[index].find('.accordion-body').collapse('hide');
53 }
52 }
54
53
55 }
54 }
56 }
55 }
57
56
58 return IPython.WidgetView.prototype.update.call(this);
57 return IPython.WidgetView.prototype.update.call(this);
59 },
58 },
60
59
61 display_child: function(view) {
60 add_child_view: function(attr, view) {
62
61
63 var index = this.containers.length;
62 var index = this.containers.length;
64 var uuid = IPython.utils.uuid();
63 var uuid = IPython.utils.uuid();
65 var accordion_group = $('<div />')
64 var accordion_group = $('<div />')
66 .addClass('accordion-group')
65 .addClass('accordion-group')
67 .appendTo(this.$el);
66 .appendTo(this.$el);
68 var accordion_heading = $('<div />')
67 var accordion_heading = $('<div />')
69 .addClass('accordion-heading')
68 .addClass('accordion-heading')
70 .appendTo(accordion_group);
69 .appendTo(accordion_group);
71 var that = this;
70 var that = this;
72 var accordion_toggle = $('<a />')
71 var accordion_toggle = $('<a />')
73 .addClass('accordion-toggle')
72 .addClass('accordion-toggle')
74 .attr('data-toggle', 'collapse')
73 .attr('data-toggle', 'collapse')
75 .attr('data-parent', '#' + this.$el.attr('id'))
74 .attr('data-parent', '#' + this.$el.attr('id'))
76 .attr('href', '#' + uuid)
75 .attr('href', '#' + uuid)
77 .click(function(evt){
76 .click(function(evt){
78 that.model.set("selected_index", index);
77 that.model.set("selected_index", index);
79 that.touch();
78 that.touch();
80 })
79 })
81 .html('Page ' + index)
80 .html('Page ' + index)
82 .appendTo(accordion_heading);
81 .appendTo(accordion_heading);
83 var accordion_body = $('<div />', {id: uuid})
82 var accordion_body = $('<div />', {id: uuid})
84 .addClass('accordion-body collapse')
83 .addClass('accordion-body collapse')
85 .appendTo(accordion_group);
84 .appendTo(accordion_group);
86 var accordion_inner = $('<div />')
85 var accordion_inner = $('<div />')
87 .addClass('accordion-inner')
86 .addClass('accordion-inner')
88 .appendTo(accordion_body);
87 .appendTo(accordion_body);
89 this.containers.push(accordion_group);
88 this.containers.push(accordion_group);
90 accordion_inner.append(view.$el);
89 accordion_inner.append(view.$el);
91
90
92 this.update();
91 this.update();
93
92
94 // Stupid workaround to close the bootstrap accordion tabs which
93 // Stupid workaround to close the bootstrap accordion tabs which
95 // open by default even though they don't have the `in` class
94 // open by default even though they don't have the `in` class
96 // attached to them. For some reason a delay is required.
95 // attached to them. For some reason a delay is required.
97 // TODO: Better fix.
96 // TODO: Better fix.
98 setTimeout(function(){ that.update(); }, 500);
97 setTimeout(function(){ that.update(); }, 500);
99 },
98 },
100 });
99 });
101
100
102 widget_manager.register_widget_view('AccordionView', AccordionView);
101 widget_manager.register_widget_view('AccordionView', AccordionView);
103
102
104 var TabView = IPython.WidgetView.extend({
103 var TabView = IPython.WidgetView.extend({
105
104
105 initialize: function() {
106 this.containers = [];
107 IPython.WidgetView.prototype.initialize.apply(this, arguments);
108 },
109
106 render: function(){
110 render: function(){
111 console.log('rendering tabs', this);
107 var uuid = 'tabs'+IPython.utils.uuid();
112 var uuid = 'tabs'+IPython.utils.uuid();
108 var that = this;
113 var that = this;
109 this.$tabs = $('<div />', {id: uuid})
114 this.$tabs = $('<div />', {id: uuid})
110 .addClass('nav')
115 .addClass('nav')
111 .addClass('nav-tabs')
116 .addClass('nav-tabs')
112 .appendTo(this.$el);
117 .appendTo(this.$el);
113 this.$tab_contents = $('<div />', {id: uuid + 'Content'})
118 this.$tab_contents = $('<div />', {id: uuid + 'Content'})
114 .addClass('tab-content')
119 .addClass('tab-content')
115 .appendTo(this.$el);
120 .appendTo(this.$el);
116
121 this.update();
117 this.containers = [];
118 },
122 },
119
123
120 update: function() {
124 update: function() {
121 // Set tab titles
125 // Set tab titles
122 var titles = this.model.get('_titles');
126 var titles = this.model.get('_titles');
123 for (var page_index in titles) {
127 for (var page_index in titles) {
124 var tab_text = this.containers[page_index];
128 var tab_text = this.containers[page_index];
125 if (tab_text !== undefined) {
129 if (tab_text !== undefined) {
126 tab_text.html(titles[page_index]);
130 tab_text.html(titles[page_index]);
127 }
131 }
128 }
132 }
129
133
130 var selected_index = this.model.get('selected_index');
134 var selected_index = this.model.get('selected_index');
131 if (0 <= selected_index && selected_index < this.containers.length) {
135 if (0 <= selected_index && selected_index < this.containers.length) {
132 this.select_page(selected_index);
136 this.select_page(selected_index);
133 }
137 }
134
138
135 return IPython.WidgetView.prototype.update.call(this);
139 return IPython.WidgetView.prototype.update.call(this);
136 },
140 },
137
141
138 display_child: function(view) {
142 add_child_view: function(attr, view) {
139
143 console.log('adding child view', attr, view);
140 var index = this.containers.length;
144 var index = this.containers.length;
141 var uuid = IPython.utils.uuid();
145 var uuid = IPython.utils.uuid();
142
146
143 var that = this;
147 var that = this;
144 var tab = $('<li />')
148 var tab = $('<li />')
145 .css('list-style-type', 'none')
149 .css('list-style-type', 'none')
146 .appendTo(this.$tabs);
150 .appendTo(this.$tabs);
147 var tab_text = $('<a />')
151 var tab_text = $('<a />')
148 .attr('href', '#' + uuid)
152 .attr('href', '#' + uuid)
149 .attr('data-toggle', 'tab')
153 .attr('data-toggle', 'tab')
150 .html('Page ' + index)
154 .html('Page ' + index)
151 .appendTo(tab)
155 .appendTo(tab)
152 .click(function (e) {
156 .click(function (e) {
153 that.model.set("selected_index", index);
157 that.model.set("selected_index", index);
154 that.touch();
158 that.touch();
155 that.select_page(index);
159 that.select_page(index);
156 });
160 });
157 this.containers.push(tab_text);
161 this.containers.push(tab_text);
158
162
159 var contents_div = $('<div />', {id: uuid})
163 var contents_div = $('<div />', {id: uuid})
160 .addClass('tab-pane')
164 .addClass('tab-pane')
161 .addClass('fade')
165 .addClass('fade')
162 .append(view.$el)
166 .append(view.$el)
163 .appendTo(this.$tab_contents);
167 .appendTo(this.$tab_contents);
164
168
165 if (index === 0) {
169 if (index === 0) {
166 tab_text.tab('show');
170 tab_text.tab('show');
167 }
171 }
168 this.update();
172 this.update();
169 },
173 },
170
174
171 select_page: function(index) {
175 select_page: function(index) {
172 this.$tabs.find('li')
176 this.$tabs.find('li')
173 .removeClass('active');
177 .removeClass('active');
174 this.containers[index].tab('show');
178 this.containers[index].tab('show');
175 },
179 },
176 });
180 });
177
181
178 widget_manager.register_widget_view('TabView', TabView);
182 widget_manager.register_widget_view('TabView', TabView);
179 });
183 });
@@ -1,198 +1,198 b''
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2013 The IPython Development Team
2 // Copyright (C) 2013 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 //============================================================================
8 //============================================================================
9 // Comm and CommManager bases
9 // Comm and CommManager bases
10 //============================================================================
10 //============================================================================
11 /**
11 /**
12 * Base Comm classes
12 * Base Comm classes
13 * @module IPython
13 * @module IPython
14 * @namespace IPython
14 * @namespace IPython
15 * @submodule comm
15 * @submodule comm
16 */
16 */
17
17
18 var IPython = (function (IPython) {
18 var IPython = (function (IPython) {
19 "use strict";
19 "use strict";
20
20
21 //-----------------------------------------------------------------------
21 //-----------------------------------------------------------------------
22 // CommManager class
22 // CommManager class
23 //-----------------------------------------------------------------------
23 //-----------------------------------------------------------------------
24
24
25 var CommManager = function (kernel) {
25 var CommManager = function (kernel) {
26 this.comms = {};
26 this.comms = {};
27 this.targets = {};
27 this.targets = {};
28 if (kernel !== undefined) {
28 if (kernel !== undefined) {
29 this.init_kernel(kernel);
29 this.init_kernel(kernel);
30 }
30 }
31 };
31 };
32
32
33 CommManager.prototype.init_kernel = function (kernel) {
33 CommManager.prototype.init_kernel = function (kernel) {
34 // connect the kernel, and register message handlers
34 // connect the kernel, and register message handlers
35 this.kernel = kernel;
35 this.kernel = kernel;
36 var msg_types = ['comm_open', 'comm_msg', 'comm_close'];
36 var msg_types = ['comm_open', 'comm_msg', 'comm_close'];
37 for (var i = 0; i < msg_types.length; i++) {
37 for (var i = 0; i < msg_types.length; i++) {
38 var msg_type = msg_types[i];
38 var msg_type = msg_types[i];
39 kernel.register_iopub_handler(msg_type, $.proxy(this[msg_type], this));
39 kernel.register_iopub_handler(msg_type, $.proxy(this[msg_type], this));
40 }
40 }
41 };
41 };
42
42
43 CommManager.prototype.new_comm = function (target_name, data, callbacks, metadata) {
43 CommManager.prototype.new_comm = function (target_name, data, callbacks, metadata) {
44 // Create a new Comm, register it, and open its Kernel-side counterpart
44 // Create a new Comm, register it, and open its Kernel-side counterpart
45 // Mimics the auto-registration in `Comm.__init__` in the IPython Comm
45 // Mimics the auto-registration in `Comm.__init__` in the IPython Comm
46 var comm = new Comm(target_name);
46 var comm = new Comm(target_name);
47 this.register_comm(comm);
47 this.register_comm(comm);
48 comm.open(data, callbacks, metadata);
48 comm.open(data, callbacks, metadata);
49 return comm;
49 return comm;
50 };
50 };
51
51
52 CommManager.prototype.register_target = function (target_name, f) {
52 CommManager.prototype.register_target = function (target_name, f) {
53 // Register a target function for a given target name
53 // Register a target function for a given target name
54 this.targets[target_name] = f;
54 this.targets[target_name] = f;
55 };
55 };
56
56
57 CommManager.prototype.unregister_target = function (target_name, f) {
57 CommManager.prototype.unregister_target = function (target_name, f) {
58 // Unregister a target function for a given target name
58 // Unregister a target function for a given target name
59 delete this.targets[target_name];
59 delete this.targets[target_name];
60 };
60 };
61
61
62 CommManager.prototype.register_comm = function (comm) {
62 CommManager.prototype.register_comm = function (comm) {
63 // Register a comm in the mapping
63 // Register a comm in the mapping
64 this.comms[comm.comm_id] = comm;
64 this.comms[comm.comm_id] = comm;
65 comm.kernel = this.kernel;
65 comm.kernel = this.kernel;
66 return comm.comm_id;
66 return comm.comm_id;
67 };
67 };
68
68
69 CommManager.prototype.unregister_comm = function (comm_id) {
69 CommManager.prototype.unregister_comm = function (comm_id) {
70 // Remove a comm from the mapping
70 // Remove a comm from the mapping
71 delete this.comms[comm_id];
71 delete this.comms[comm_id];
72 };
72 };
73
73
74 // comm message handlers
74 // comm message handlers
75
75
76 CommManager.prototype.comm_open = function (msg) {
76 CommManager.prototype.comm_open = function (msg) {
77 var content = msg.content;
77 var content = msg.content;
78 var f = this.targets[content.target_name];
78 var f = this.targets[content.target_name];
79 if (f === undefined) {
79 if (f === undefined) {
80 console.log("No such target registered: ", content.target_name);
80 console.log("No such target registered: ", content.target_name);
81 console.log("Available targets are: ", this.targets);
81 console.log("Available targets are: ", this.targets);
82 return;
82 return;
83 }
83 }
84 var comm = new Comm(content.target_name, content.comm_id);
84 var comm = new Comm(content.target_name, content.comm_id);
85 this.register_comm(comm);
85 this.register_comm(comm);
86 try {
86 try {
87 f(comm, msg);
87 f(comm, msg);
88 } catch (e) {
88 } catch (e) {
89 console.log("Exception opening new comm:", e, msg);
89 console.log("Exception opening new comm:", e, e.stack, msg);
90 comm.close();
90 comm.close();
91 this.unregister_comm(comm);
91 this.unregister_comm(comm);
92 }
92 }
93 };
93 };
94
94
95 CommManager.prototype.comm_close = function (msg) {
95 CommManager.prototype.comm_close = function (msg) {
96 var content = msg.content;
96 var content = msg.content;
97 var comm = this.comms[content.comm_id];
97 var comm = this.comms[content.comm_id];
98 if (comm === undefined) {
98 if (comm === undefined) {
99 return;
99 return;
100 }
100 }
101 delete this.comms[content.comm_id];
101 delete this.comms[content.comm_id];
102 try {
102 try {
103 comm.handle_close(msg);
103 comm.handle_close(msg);
104 } catch (e) {
104 } catch (e) {
105 console.log("Exception closing comm: ", e, msg);
105 console.log("Exception closing comm: ", e, e.stack, msg);
106 }
106 }
107 };
107 };
108
108
109 CommManager.prototype.comm_msg = function (msg) {
109 CommManager.prototype.comm_msg = function (msg) {
110 var content = msg.content;
110 var content = msg.content;
111 var comm = this.comms[content.comm_id];
111 var comm = this.comms[content.comm_id];
112 if (comm === undefined) {
112 if (comm === undefined) {
113 return;
113 return;
114 }
114 }
115 try {
115 try {
116 comm.handle_msg(msg);
116 comm.handle_msg(msg);
117 } catch (e) {
117 } catch (e) {
118 console.log("Exception handling comm msg: ", e, msg);
118 console.log("Exception handling comm msg: ", e, e.stack, msg);
119 }
119 }
120 };
120 };
121
121
122 //-----------------------------------------------------------------------
122 //-----------------------------------------------------------------------
123 // Comm base class
123 // Comm base class
124 //-----------------------------------------------------------------------
124 //-----------------------------------------------------------------------
125
125
126 var Comm = function (target_name, comm_id) {
126 var Comm = function (target_name, comm_id) {
127 this.target_name = target_name;
127 this.target_name = target_name;
128 this.comm_id = comm_id || IPython.utils.uuid();
128 this.comm_id = comm_id || IPython.utils.uuid();
129 this._msg_callback = this._close_callback = null;
129 this._msg_callback = this._close_callback = null;
130 };
130 };
131
131
132 // methods for sending messages
132 // methods for sending messages
133 Comm.prototype.open = function (data, callbacks, metadata) {
133 Comm.prototype.open = function (data, callbacks, metadata) {
134 var content = {
134 var content = {
135 comm_id : this.comm_id,
135 comm_id : this.comm_id,
136 target_name : this.target_name,
136 target_name : this.target_name,
137 data : data || {},
137 data : data || {},
138 };
138 };
139 return this.kernel.send_shell_message("comm_open", content, callbacks, metadata);
139 return this.kernel.send_shell_message("comm_open", content, callbacks, metadata);
140 };
140 };
141
141
142 Comm.prototype.send = function (data, callbacks, metadata) {
142 Comm.prototype.send = function (data, callbacks, metadata) {
143 var content = {
143 var content = {
144 comm_id : this.comm_id,
144 comm_id : this.comm_id,
145 data : data || {},
145 data : data || {},
146 };
146 };
147 return this.kernel.send_shell_message("comm_msg", content, callbacks, metadata);
147 return this.kernel.send_shell_message("comm_msg", content, callbacks, metadata);
148 };
148 };
149
149
150 Comm.prototype.close = function (data, callbacks, metadata) {
150 Comm.prototype.close = function (data, callbacks, metadata) {
151 var content = {
151 var content = {
152 comm_id : this.comm_id,
152 comm_id : this.comm_id,
153 data : data || {},
153 data : data || {},
154 };
154 };
155 return this.kernel.send_shell_message("comm_close", content, callbacks, metadata);
155 return this.kernel.send_shell_message("comm_close", content, callbacks, metadata);
156 };
156 };
157
157
158 // methods for registering callbacks for incoming messages
158 // methods for registering callbacks for incoming messages
159 Comm.prototype._register_callback = function (key, callback) {
159 Comm.prototype._register_callback = function (key, callback) {
160 this['_' + key + '_callback'] = callback;
160 this['_' + key + '_callback'] = callback;
161 };
161 };
162
162
163 Comm.prototype.on_msg = function (callback) {
163 Comm.prototype.on_msg = function (callback) {
164 this._register_callback('msg', callback);
164 this._register_callback('msg', callback);
165 };
165 };
166
166
167 Comm.prototype.on_close = function (callback) {
167 Comm.prototype.on_close = function (callback) {
168 this._register_callback('close', callback);
168 this._register_callback('close', callback);
169 };
169 };
170
170
171 // methods for handling incoming messages
171 // methods for handling incoming messages
172
172
173 Comm.prototype._maybe_callback = function (key, msg) {
173 Comm.prototype._maybe_callback = function (key, msg) {
174 var callback = this['_' + key + '_callback'];
174 var callback = this['_' + key + '_callback'];
175 if (callback) {
175 if (callback) {
176 try {
176 try {
177 callback(msg);
177 callback(msg);
178 } catch (e) {
178 } catch (e) {
179 console.log("Exception in Comm callback", e, msg);
179 console.log("Exception in Comm callback", e, e.stack, msg);
180 }
180 }
181 }
181 }
182 };
182 };
183
183
184 Comm.prototype.handle_msg = function (msg) {
184 Comm.prototype.handle_msg = function (msg) {
185 this._maybe_callback('msg', msg);
185 this._maybe_callback('msg', msg);
186 };
186 };
187
187
188 Comm.prototype.handle_close = function (msg) {
188 Comm.prototype.handle_close = function (msg) {
189 this._maybe_callback('close', msg);
189 this._maybe_callback('close', msg);
190 };
190 };
191
191
192 IPython.CommManager = CommManager;
192 IPython.CommManager = CommManager;
193 IPython.Comm = Comm;
193 IPython.Comm = Comm;
194
194
195 return IPython;
195 return IPython;
196
196
197 }(IPython));
197 }(IPython));
198
198
@@ -1,445 +1,450 b''
1 """Base Widget class. Allows user to create widgets in the backend that render
1 """Base Widget class. Allows user to create widgets in the backend that render
2 in the IPython notebook frontend.
2 in the IPython notebook frontend.
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 copy import copy
15 from copy import copy
16 from glob import glob
16 from glob import glob
17 import uuid
17 import uuid
18 import sys
18 import sys
19 import os
19 import os
20 import inspect
20 import inspect
21 import types
21 import types
22
22
23 import IPython
23 import IPython
24 from IPython.kernel.comm import Comm
24 from IPython.kernel.comm import Comm
25 from IPython.config import LoggingConfigurable
25 from IPython.config import LoggingConfigurable
26 from IPython.utils.traitlets import Unicode, Dict, List, Instance, Bool
26 from IPython.utils.traitlets import Unicode, Dict, List, Instance, Bool
27 from IPython.display import Javascript, display
27 from IPython.display import Javascript, display
28 from IPython.utils.py3compat import string_types
28 from IPython.utils.py3compat import string_types
29
29
30 #-----------------------------------------------------------------------------
30 #-----------------------------------------------------------------------------
31 # Classes
31 # Classes
32 #-----------------------------------------------------------------------------
32 #-----------------------------------------------------------------------------
33
33
34 class BaseWidget(LoggingConfigurable):
34 class BaseWidget(LoggingConfigurable):
35
35
36 # Shared declarations (Class level)
36 # Shared declarations (Class level)
37 _keys = List(Unicode, help="List of keys comprising the state of the model.")
37 _keys = List(Unicode, default_value = [],
38 _children_attr = List(Unicode, help="List of keys of children objects of the model.")
38 help="List of keys comprising the state of the model.", allow_none=False)
39 _children_lists_attr = List(Unicode, help="List of keys containing lists of children objects of the model.")
39 _children_attr = List(Unicode, default_value = [],
40 help="List of keys of children objects of the model.", allow_none=False)
41 _children_lists_attr = List(Unicode, default_value = [],
42 help="List of keys containing lists of children objects of the model.",
43 allow_none=False)
40 widget_construction_callback = None
44 widget_construction_callback = None
41
45
42 def on_widget_constructed(callback):
46 def on_widget_constructed(callback):
43 """Class method, registers a callback to be called when a widget is
47 """Class method, registers a callback to be called when a widget is
44 constructed. The callback must have the following signature:
48 constructed. The callback must have the following signature:
45 callback(widget)"""
49 callback(widget)"""
46 Widget.widget_construction_callback = callback
50 Widget.widget_construction_callback = callback
47
51
48 def _handle_widget_constructed(widget):
52 def _handle_widget_constructed(widget):
49 """Class method, called when a widget is constructed."""
53 """Class method, called when a widget is constructed."""
50 if Widget.widget_construction_callback is not None and callable(Widget.widget_construction_callback):
54 if Widget.widget_construction_callback is not None and callable(Widget.widget_construction_callback):
51 Widget.widget_construction_callback(widget)
55 Widget.widget_construction_callback(widget)
52
56
53
57
54
58
55 # Public declarations (Instance level)
59 # Public declarations (Instance level)
56 target_name = Unicode('widget', help="""Name of the backbone model
60 target_name = Unicode('widget', help="""Name of the backbone model
57 registered in the frontend to create and sync this widget with.""")
61 registered in the frontend to create and sync this widget with.""")
58 default_view_name = Unicode(help="""Default view registered in the frontend
62 default_view_name = Unicode(help="""Default view registered in the frontend
59 to use to represent the widget.""")
63 to use to represent the widget.""")
60
64
61 # Private/protected declarations
65 # Private/protected declarations
66 # todo: change this to a context manager
62 _property_lock = (None, None) # Last updated (key, value) from the front-end. Prevents echo.
67 _property_lock = (None, None) # Last updated (key, value) from the front-end. Prevents echo.
63 _displayed = False
68 _displayed = False
64 _comm = None
69 _comm = Instance('IPython.kernel.comm.Comm')
65
70
66 def __init__(self, **kwargs):
71 def __init__(self, **kwargs):
67 """Public constructor
72 """Public constructor
68 """
73 """
69 self._display_callbacks = []
74 self._display_callbacks = []
70 self._msg_callbacks = []
75 self._msg_callbacks = []
71 super(BaseWidget, self).__init__(**kwargs)
76 super(BaseWidget, self).__init__(**kwargs)
72
77
73 # Register after init to allow default values to be specified
78 # Register after init to allow default values to be specified
74 # TODO: register three different handlers, one for each list, and abstract out the common parts
79 # TODO: register three different handlers, one for each list, and abstract out the common parts
80 #print self.keys, self._children_attr, self._children_lists_attr
75 self.on_trait_change(self._handle_property_changed, self.keys+self._children_attr+self._children_lists_attr)
81 self.on_trait_change(self._handle_property_changed, self.keys+self._children_attr+self._children_lists_attr)
76 Widget._handle_widget_constructed(self)
82 Widget._handle_widget_constructed(self)
77
83
78 def __del__(self):
84 def __del__(self):
79 """Object disposal"""
85 """Object disposal"""
80 self.close()
86 self.close()
81
87
82
88
83 def close(self):
89 def close(self):
84 """Close method. Closes the widget which closes the underlying comm.
90 """Close method. Closes the widget which closes the underlying comm.
85 When the comm is closed, all of the widget views are automatically
91 When the comm is closed, all of the widget views are automatically
86 removed from the frontend."""
92 removed from the frontend."""
87 self._close_communication()
93 self._close_communication()
88
94
89
95
90 # Properties
96 # Properties
91 @property
97 @property
92 def keys(self):
98 def keys(self):
93 keys = ['_children_attr', '_children_lists_attr']
99 keys = ['_children_attr', '_children_lists_attr', 'default_view_name']
94 keys.extend(self._keys)
100 keys.extend(self._keys)
95 return keys
101 return keys
96
102
97 @property
103 @property
98 def comm(self):
104 def comm(self):
99 if self._comm is None:
105 if self._comm is None:
100 self._open_communication()
106 self._open_communication()
101 return self._comm
107 return self._comm
102
108
103 # Event handlers
109 # Event handlers
104 def _handle_msg(self, msg):
110 def _handle_msg(self, msg):
105 """Called when a msg is recieved from the frontend"""
111 """Called when a msg is recieved from the frontend"""
106 data = msg['content']['data']
112 data = msg['content']['data']
107 method = data['method']
113 method = data['method']
108
114
109 # Handle backbone sync methods CREATE, PATCH, and UPDATE
115 # Handle backbone sync methods CREATE, PATCH, and UPDATE
110 if method == 'backbone':
116 if method == 'backbone':
111 if 'sync_method' in data and 'sync_data' in data:
117 if 'sync_method' in data and 'sync_data' in data:
112 sync_method = data['sync_method']
118 sync_method = data['sync_method']
113 sync_data = data['sync_data']
119 sync_data = data['sync_data']
114 self._handle_recieve_state(sync_data) # handles all methods
120 self._handle_recieve_state(sync_data) # handles all methods
115
121
116 # Handle a custom msg from the front-end
122 # Handle a custom msg from the front-end
117 elif method == 'custom':
123 elif method == 'custom':
118 if 'custom_content' in data:
124 if 'custom_content' in data:
119 self._handle_custom_msg(data['custom_content'])
125 self._handle_custom_msg(data['custom_content'])
120
126
121
122 def _handle_custom_msg(self, content):
127 def _handle_custom_msg(self, content):
123 """Called when a custom msg is recieved."""
128 """Called when a custom msg is recieved."""
124 for handler in self._msg_callbacks:
129 for handler in self._msg_callbacks:
125 if callable(handler):
130 if callable(handler):
126 argspec = inspect.getargspec(handler)
131 argspec = inspect.getargspec(handler)
127 nargs = len(argspec[0])
132 nargs = len(argspec[0])
128
133
129 # Bound methods have an additional 'self' argument
134 # Bound methods have an additional 'self' argument
130 if isinstance(handler, types.MethodType):
135 if isinstance(handler, types.MethodType):
131 nargs -= 1
136 nargs -= 1
132
137
133 # Call the callback
138 # Call the callback
134 if nargs == 1:
139 if nargs == 1:
135 handler(content)
140 handler(content)
136 elif nargs == 2:
141 elif nargs == 2:
137 handler(self, content)
142 handler(self, content)
138 else:
143 else:
139 raise TypeError('Widget msg callback must ' \
144 raise TypeError('Widget msg callback must ' \
140 'accept 1 or 2 arguments, not %d.' % nargs)
145 'accept 1 or 2 arguments, not %d.' % nargs)
141
146
142
147
143 def _handle_recieve_state(self, sync_data):
148 def _handle_recieve_state(self, sync_data):
144 """Called when a state is recieved from the frontend."""
149 """Called when a state is recieved from the frontend."""
145 # Use _keys instead of keys - Don't get retrieve the css from the client side.
150 # Use _keys instead of keys - Don't get retrieve the css from the client side.
146 for name in self._keys:
151 for name in self._keys:
147 if name in sync_data:
152 if name in sync_data:
148 try:
153 try:
149 self._property_lock = (name, sync_data[name])
154 self._property_lock = (name, sync_data[name])
150 setattr(self, name, sync_data[name])
155 setattr(self, name, sync_data[name])
151 finally:
156 finally:
152 self._property_lock = (None, None)
157 self._property_lock = (None, None)
153
158
154
159
155 def _handle_property_changed(self, name, old, new):
160 def _handle_property_changed(self, name, old, new):
156 """Called when a proeprty has been changed."""
161 """Called when a property has been changed."""
157 # Make sure this isn't information that the front-end just sent us.
162 # Make sure this isn't information that the front-end just sent us.
158 if self._property_lock[0] != name and self._property_lock[1] != new:
163 if self._property_lock[0] != name and self._property_lock[1] != new:
159 # Send new state to frontend
164 # Send new state to frontend
160 self.send_state(key=name)
165 self.send_state(key=name)
161
166
162 def _handle_displayed(self, **kwargs):
167 def _handle_displayed(self, **kwargs):
163 """Called when a view has been displayed for this widget instance
168 """Called when a view has been displayed for this widget instance
164
169
165 Parameters
170 Parameters
166 ----------
171 ----------
167 [view_name]: unicode (optional kwarg)
172 [view_name]: unicode (optional kwarg)
168 Name of the view that was displayed."""
173 Name of the view that was displayed."""
169 for handler in self._display_callbacks:
174 for handler in self._display_callbacks:
170 if callable(handler):
175 if callable(handler):
171 argspec = inspect.getargspec(handler)
176 argspec = inspect.getargspec(handler)
172 nargs = len(argspec[0])
177 nargs = len(argspec[0])
173
178
174 # Bound methods have an additional 'self' argument
179 # Bound methods have an additional 'self' argument
175 if isinstance(handler, types.MethodType):
180 if isinstance(handler, types.MethodType):
176 nargs -= 1
181 nargs -= 1
177
182
178 # Call the callback
183 # Call the callback
179 if nargs == 0:
184 if nargs == 0:
180 handler()
185 handler()
181 elif nargs == 1:
186 elif nargs == 1:
182 handler(self)
187 handler(self)
183 elif nargs == 2:
188 elif nargs == 2:
184 handler(self, kwargs.get('view_name', None))
189 handler(self, kwargs.get('view_name', None))
185 else:
190 else:
186 handler(self, **kwargs)
191 handler(self, **kwargs)
187
192
188 # Public methods
193 # Public methods
189 def send_state(self, key=None):
194 def send_state(self, key=None):
190 """Sends the widget state, or a piece of it, to the frontend.
195 """Sends the widget state, or a piece of it, to the frontend.
191
196
192 Parameters
197 Parameters
193 ----------
198 ----------
194 key : unicode (optional)
199 key : unicode (optional)
195 A single property's name to sync with the frontend.
200 A single property's name to sync with the frontend.
196 """
201 """
197 self._send({"method": "update",
202 self._send({"method": "update",
198 "state": self.get_state()})
203 "state": self.get_state()})
199
204
200 def get_state(self, key=None)
205 def get_state(self, key=None):
201 """Gets the widget state, or a piece of it.
206 """Gets the widget state, or a piece of it.
202
207
203 Parameters
208 Parameters
204 ----------
209 ----------
205 key : unicode (optional)
210 key : unicode (optional)
206 A single property's name to get.
211 A single property's name to get.
207 """
212 """
208 state = {}
213 state = {}
209
214
210 # If a key is provided, just send the state of that key.
215 # If a key is provided, just send the state of that key.
211 if key is None:
216 if key is None:
212 keys = self.keys[:]
217 keys = self.keys[:]
213 children_attr = self._children_attr[:]
218 children_attr = self._children_attr[:]
214 children_lists_attr = self._children_lists_attr[:]
219 children_lists_attr = self._children_lists_attr[:]
215 else:
220 else:
216 keys = []
221 keys = []
217 children_attr = []
222 children_attr = []
218 children_lists_attr = []
223 children_lists_attr = []
219 if key in self._children_attr:
224 if key in self._children_attr:
220 children_attr.append(key)
225 children_attr.append(key)
221 elif key in self._children_lists_attr:
226 elif key in self._children_lists_attr:
222 children_lists_attr.append(key)
227 children_lists_attr.append(key)
223 else:
228 else:
224 keys.append(key)
229 keys.append(key)
225 for k in keys:
230 for k in keys:
226 state[k] = getattr(self, k)
231 state[k] = getattr(self, k)
227 for k in children_attr:
232 for k in children_attr:
228 # automatically create models on the browser side if they aren't already created
233 # automatically create models on the browser side if they aren't already created
229 state[k] = getattr(self, k).comm.comm_id
234 state[k] = getattr(self, k).comm.comm_id
230 for k in children_lists_attr:
235 for k in children_lists_attr:
231 # automatically create models on the browser side if they aren't already created
236 # automatically create models on the browser side if they aren't already created
232 state[k] = [i.comm.comm_id for i in getattr(self, k)]
237 state[k] = [i.comm.comm_id for i in getattr(self, k)]
233 return state
238 return state
234
239
235
240
236 def send(self, content):
241 def send(self, content):
237 """Sends a custom msg to the widget model in the front-end.
242 """Sends a custom msg to the widget model in the front-end.
238
243
239 Parameters
244 Parameters
240 ----------
245 ----------
241 content : dict
246 content : dict
242 Content of the message to send.
247 Content of the message to send.
243 """
248 """
244 self._send({"method": "custom",
249 self._send({"method": "custom",
245 "custom_content": content})
250 "custom_content": content})
246
251
247
252
248 def on_msg(self, callback, remove=False):
253 def on_msg(self, callback, remove=False):
249 """Register a callback for when a custom msg is recieved from the front-end
254 """Register a callback for when a custom msg is recieved from the front-end
250
255
251 Parameters
256 Parameters
252 ----------
257 ----------
253 callback: method handler
258 callback: method handler
254 Can have a signature of:
259 Can have a signature of:
255 - callback(content)
260 - callback(content)
256 - callback(sender, content)
261 - callback(sender, content)
257 remove: bool
262 remove: bool
258 True if the callback should be unregistered."""
263 True if the callback should be unregistered."""
259 if remove and callback in self._msg_callbacks:
264 if remove and callback in self._msg_callbacks:
260 self._msg_callbacks.remove(callback)
265 self._msg_callbacks.remove(callback)
261 elif not remove and not callback in self._msg_callbacks:
266 elif not remove and not callback in self._msg_callbacks:
262 self._msg_callbacks.append(callback)
267 self._msg_callbacks.append(callback)
263
268
264
269
265 def on_displayed(self, callback, remove=False):
270 def on_displayed(self, callback, remove=False):
266 """Register a callback to be called when the widget has been displayed
271 """Register a callback to be called when the widget has been displayed
267
272
268 Parameters
273 Parameters
269 ----------
274 ----------
270 callback: method handler
275 callback: method handler
271 Can have a signature of:
276 Can have a signature of:
272 - callback()
277 - callback()
273 - callback(sender)
278 - callback(sender)
274 - callback(sender, view_name)
279 - callback(sender, view_name)
275 - callback(sender, **kwargs)
280 - callback(sender, **kwargs)
276 kwargs from display call passed through without modification.
281 kwargs from display call passed through without modification.
277 remove: bool
282 remove: bool
278 True if the callback should be unregistered."""
283 True if the callback should be unregistered."""
279 if remove and callback in self._display_callbacks:
284 if remove and callback in self._display_callbacks:
280 self._display_callbacks.remove(callback)
285 self._display_callbacks.remove(callback)
281 elif not remove and not callback in self._display_callbacks:
286 elif not remove and not callback in self._display_callbacks:
282 self._display_callbacks.append(callback)
287 self._display_callbacks.append(callback)
283
288
284
289
285 # Support methods
290 # Support methods
286 def _repr_widget_(self, **kwargs):
291 def _repr_widget_(self, **kwargs):
287 """Function that is called when `IPython.display.display` is called on
292 """Function that is called when `IPython.display.display` is called on
288 the widget.
293 the widget.
289
294
290 Parameters
295 Parameters
291 ----------
296 ----------
292 view_name: unicode (optional)
297 view_name: unicode (optional)
293 View to display in the frontend. Overrides default_view_name."""
298 View to display in the frontend. Overrides default_view_name."""
294 view_name = kwargs.get('view_name', self.default_view_name)
299 view_name = kwargs.get('view_name', self.default_view_name)
295
300
296 # Create a communication.
301 # Create a communication.
297 self._open_communication()
302 self._open_communication()
298
303
299 # Make sure model is syncronized
304 # Make sure model is syncronized
300 self.send_state()
305 self.send_state()
301
306
302 # Show view.
307 # Show view.
303 self._send({"method": "display", "view_name": view_name})
308 self._send({"method": "display", "view_name": view_name})
304 self._displayed = True
309 self._displayed = True
305 self._handle_displayed(**kwargs)
310 self._handle_displayed(**kwargs)
306
311
307
312
308 def _open_communication(self):
313 def _open_communication(self):
309 """Opens a communication with the front-end."""
314 """Opens a communication with the front-end."""
310 # Create a comm.
315 # Create a comm.
311 if not hasattr(self, '_comm') or self._comm is None:
316 if self._comm is None:
312 self._comm = Comm(target_name=self.target_name)
317 self._comm = Comm(target_name=self.target_name)
313 self._comm.on_msg(self._handle_msg)
318 self._comm.on_msg(self._handle_msg)
314 self._comm.on_close(self._close_communication)
319 self._comm.on_close(self._close_communication)
315
320
321 # first update
322 self.send_state()
323
316
324
317 def _close_communication(self):
325 def _close_communication(self):
318 """Closes a communication with the front-end."""
326 """Closes a communication with the front-end."""
319 if hasattr(self, '_comm') and self._comm is not None:
327 if self._comm is not None:
320 try:
328 try:
321 self._comm.close()
329 self._comm.close()
322 finally:
330 finally:
323 self._comm = None
331 self._comm = None
324
332
325
333
326 def _send(self, msg):
334 def _send(self, msg):
327 """Sends a message to the model in the front-end"""
335 """Sends a message to the model in the front-end"""
328 if self._comm is not None:
336 if self._comm is not None:
329 self._comm.send(msg)
337 self._comm.send(msg)
330 return True
338 return True
331 else:
339 else:
332 return False
340 return False
333
341
334 class Widget(BaseWidget):
342 class Widget(BaseWidget):
335
336 _children = List(Instance('IPython.html.widgets.widget.Widget'))
337 _children_lists_attr = List(Unicode, ['_children'])
338 visible = Bool(True, help="Whether or not the widget is visible.")
343 visible = Bool(True, help="Whether or not the widget is visible.")
339
344
340 # Private/protected declarations
345 # Private/protected declarations
341 _css = Dict() # Internal CSS property dict
346 _css = Dict() # Internal CSS property dict
342
347
343 # Properties
348 # Properties
344 @property
349 @property
345 def keys(self):
350 def keys(self):
346 keys = ['visible', '_css']
351 keys = ['visible', '_css']
347 keys.extend(super(Widget, self).keys)
352 keys.extend(super(Widget, self).keys)
348 return keys
353 return keys
349
354
350 def get_css(self, key, selector=""):
355 def get_css(self, key, selector=""):
351 """Get a CSS property of the widget. Note, this function does not
356 """Get a CSS property of the widget. Note, this function does not
352 actually request the CSS from the front-end; Only properties that have
357 actually request the CSS from the front-end; Only properties that have
353 been set with set_css can be read.
358 been set with set_css can be read.
354
359
355 Parameters
360 Parameters
356 ----------
361 ----------
357 key: unicode
362 key: unicode
358 CSS key
363 CSS key
359 selector: unicode (optional)
364 selector: unicode (optional)
360 JQuery selector used when the CSS key/value was set.
365 JQuery selector used when the CSS key/value was set.
361 """
366 """
362 if selector in self._css and key in self._css[selector]:
367 if selector in self._css and key in self._css[selector]:
363 return self._css[selector][key]
368 return self._css[selector][key]
364 else:
369 else:
365 return None
370 return None
366
371
367
372
368 def set_css(self, *args, **kwargs):
373 def set_css(self, *args, **kwargs):
369 """Set one or more CSS properties of the widget (shared among all of the
374 """Set one or more CSS properties of the widget (shared among all of the
370 views). This function has two signatures:
375 views). This function has two signatures:
371 - set_css(css_dict, [selector=''])
376 - set_css(css_dict, [selector=''])
372 - set_css(key, value, [selector=''])
377 - set_css(key, value, [selector=''])
373
378
374 Parameters
379 Parameters
375 ----------
380 ----------
376 css_dict : dict
381 css_dict : dict
377 CSS key/value pairs to apply
382 CSS key/value pairs to apply
378 key: unicode
383 key: unicode
379 CSS key
384 CSS key
380 value
385 value
381 CSS value
386 CSS value
382 selector: unicode (optional)
387 selector: unicode (optional)
383 JQuery selector to use to apply the CSS key/value.
388 JQuery selector to use to apply the CSS key/value.
384 """
389 """
385 selector = kwargs.get('selector', '')
390 selector = kwargs.get('selector', '')
386
391
387 # Signature 1: set_css(css_dict, [selector=''])
392 # Signature 1: set_css(css_dict, [selector=''])
388 if len(args) == 1:
393 if len(args) == 1:
389 if isinstance(args[0], dict):
394 if isinstance(args[0], dict):
390 for (key, value) in args[0].items():
395 for (key, value) in args[0].items():
391 self.set_css(key, value, selector=selector)
396 self.set_css(key, value, selector=selector)
392 else:
397 else:
393 raise Exception('css_dict must be a dict.')
398 raise Exception('css_dict must be a dict.')
394
399
395 # Signature 2: set_css(key, value, [selector=''])
400 # Signature 2: set_css(key, value, [selector=''])
396 elif len(args) == 2 or len(args) == 3:
401 elif len(args) == 2 or len(args) == 3:
397
402
398 # Selector can be a positional arg if it's the 3rd value
403 # Selector can be a positional arg if it's the 3rd value
399 if len(args) == 3:
404 if len(args) == 3:
400 selector = args[2]
405 selector = args[2]
401 if selector not in self._css:
406 if selector not in self._css:
402 self._css[selector] = {}
407 self._css[selector] = {}
403
408
404 # Only update the property if it has changed.
409 # Only update the property if it has changed.
405 key = args[0]
410 key = args[0]
406 value = args[1]
411 value = args[1]
407 if not (key in self._css[selector] and value in self._css[selector][key]):
412 if not (key in self._css[selector] and value in self._css[selector][key]):
408 self._css[selector][key] = value
413 self._css[selector][key] = value
409 self.send_state('_css') # Send new state to client.
414 self.send_state('_css') # Send new state to client.
410 else:
415 else:
411 raise Exception('set_css only accepts 1-3 arguments')
416 raise Exception('set_css only accepts 1-3 arguments')
412
417
413
418
414 def add_class(self, class_name, selector=""):
419 def add_class(self, class_name, selector=""):
415 """Add class[es] to a DOM element
420 """Add class[es] to a DOM element
416
421
417 Parameters
422 Parameters
418 ----------
423 ----------
419 class_name: unicode
424 class_name: unicode
420 Class name(s) to add to the DOM element(s). Multiple class names
425 Class name(s) to add to the DOM element(s). Multiple class names
421 must be space separated.
426 must be space separated.
422 selector: unicode (optional)
427 selector: unicode (optional)
423 JQuery selector to select the DOM element(s) that the class(es) will
428 JQuery selector to select the DOM element(s) that the class(es) will
424 be added to.
429 be added to.
425 """
430 """
426 self._send({"method": "add_class",
431 self._send({"method": "add_class",
427 "class_list": class_name,
432 "class_list": class_name,
428 "selector": selector})
433 "selector": selector})
429
434
430
435
431 def remove_class(self, class_name, selector=""):
436 def remove_class(self, class_name, selector=""):
432 """Remove class[es] from a DOM element
437 """Remove class[es] from a DOM element
433
438
434 Parameters
439 Parameters
435 ----------
440 ----------
436 class_name: unicode
441 class_name: unicode
437 Class name(s) to remove from the DOM element(s). Multiple class
442 Class name(s) to remove from the DOM element(s). Multiple class
438 names must be space separated.
443 names must be space separated.
439 selector: unicode (optional)
444 selector: unicode (optional)
440 JQuery selector to select the DOM element(s) that the class(es) will
445 JQuery selector to select the DOM element(s) that the class(es) will
441 be removed from.
446 be removed from.
442 """
447 """
443 self._send({"method": "remove_class",
448 self._send({"method": "remove_class",
444 "class_list": class_name,
449 "class_list": class_name,
445 "selector": selector})
450 "selector": selector})
@@ -1,93 +1,93 b''
1 """ButtonWidget class.
1 """ButtonWidget class.
2
2
3 Represents a button in the frontend using a widget. Allows user to listen for
3 Represents a button in the frontend using a widget. Allows user to listen for
4 click events on the button and trigger backend code when the clicks are fired.
4 click events on the button and trigger backend code when the clicks are fired.
5 """
5 """
6 #-----------------------------------------------------------------------------
6 #-----------------------------------------------------------------------------
7 # Copyright (c) 2013, the IPython Development Team.
7 # Copyright (c) 2013, the IPython Development Team.
8 #
8 #
9 # Distributed under the terms of the Modified BSD License.
9 # Distributed under the terms of the Modified BSD License.
10 #
10 #
11 # The full license is in the file COPYING.txt, distributed with this software.
11 # The full license is in the file COPYING.txt, distributed with this software.
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13
13
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15 # Imports
15 # Imports
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17 import inspect
17 import inspect
18 import types
18 import types
19
19
20 from .widget import Widget
20 from .widget import Widget
21 from IPython.utils.traitlets import Unicode, Bool, Int
21 from IPython.utils.traitlets import Unicode, Bool, Int
22
22
23 #-----------------------------------------------------------------------------
23 #-----------------------------------------------------------------------------
24 # Classes
24 # Classes
25 #-----------------------------------------------------------------------------
25 #-----------------------------------------------------------------------------
26 class ButtonWidget(Widget):
26 class ButtonWidget(Widget):
27 target_name = Unicode('ButtonWidgetModel')
27 target_name = Unicode('ButtonWidgetModel')
28 default_view_name = Unicode('ButtonView')
28 default_view_name = Unicode('ButtonView')
29
29
30 # Keys
30 # Keys
31 _keys = ['description', 'disabled']
31 _keys = ['description', 'disabled']
32 description = Unicode('', help="Description of the button (label).")
32 description = Unicode('', help="Description of the button (label).")
33 disabled = Bool(False, help="Enable or disable user changes.")
33 disabled = Bool(False, help="Enable or disable user changes.")
34
34
35
35
36 def __init__(self, **kwargs):
36 def __init__(self, **kwargs):
37 super(ButtonWidget, self).__init__(**kwargs)
37 super(ButtonWidget, self).__init__(**kwargs)
38
38
39 self._click_handlers = []
39 self._click_handlers = []
40 self.on_msg(self._handle_button_msg)
40 self.on_msg(self._handle_button_msg)
41
41
42
42
43 def on_click(self, callback, remove=False):
43 def on_click(self, callback, remove=False):
44 """Register a callback to execute when the button is clicked. The
44 """Register a callback to execute when the button is clicked. The
45 callback can either accept no parameters or one sender parameter:
45 callback can either accept no parameters or one sender parameter:
46 - callback()
46 - callback()
47 - callback(sender)
47 - callback(sender)
48 If the callback has a sender parameter, the ButtonWidget instance that
48 If the callback has a sender parameter, the ButtonWidget instance that
49 called the callback will be passed into the method as the sender.
49 called the callback will be passed into the method as the sender.
50
50
51 Parameters
51 Parameters
52 ----------
52 ----------
53 remove : bool (optional)
53 remove : bool (optional)
54 Set to true to remove the callback from the list of callbacks."""
54 Set to true to remove the callback from the list of callbacks."""
55 if remove:
55 if remove:
56 self._click_handlers.remove(callback)
56 self._click_handlers.remove(callback)
57 elif not callback in self._click_handlers:
57 elif not callback in self._click_handlers:
58 self._click_handlers.append(callback)
58 self._click_handlers.append(callback)
59
59
60
60
61 def _handle_button_msg(self, content):
61 def _handle_button_msg(self, content):
62 """Hanlde a msg from the front-end
62 """Handle a msg from the front-end
63
63
64 Parameters
64 Parameters
65 ----------
65 ----------
66 content: dict
66 content: dict
67 Content of the msg."""
67 Content of the msg."""
68 if 'event' in content and content['event'] == 'click':
68 if 'event' in content and content['event'] == 'click':
69 self._handle_click()
69 self._handle_click()
70
70
71
71
72 def _handle_click(self):
72 def _handle_click(self):
73 """Handles when the button has been clicked. Fires on_click
73 """Handles when the button has been clicked. Fires on_click
74 callbacks when appropriate."""
74 callbacks when appropriate."""
75
75
76 for handler in self._click_handlers:
76 for handler in self._click_handlers:
77 if callable(handler):
77 if callable(handler):
78 argspec = inspect.getargspec(handler)
78 argspec = inspect.getargspec(handler)
79 nargs = len(argspec[0])
79 nargs = len(argspec[0])
80
80
81 # Bound methods have an additional 'self' argument
81 # Bound methods have an additional 'self' argument
82 if isinstance(handler, types.MethodType):
82 if isinstance(handler, types.MethodType):
83 nargs -= 1
83 nargs -= 1
84
84
85 # Call the callback
85 # Call the callback
86 if nargs == 0:
86 if nargs == 0:
87 handler()
87 handler()
88 elif nargs == 1:
88 elif nargs == 1:
89 handler(self)
89 handler(self)
90 else:
90 else:
91 raise TypeError('ButtonWidget click callback must ' \
91 raise TypeError('ButtonWidget click callback must ' \
92 'accept 0 or 1 arguments.')
92 'accept 0 or 1 arguments.')
93
93
@@ -1,200 +1,203 b''
1 """ContainerWidget class.
1 """ContainerWidget class.
2
2
3 Represents a container that can be used to group other widgets.
3 Represents a container that can be used to group other widgets.
4 """
4 """
5 #-----------------------------------------------------------------------------
5 #-----------------------------------------------------------------------------
6 # Copyright (c) 2013, the IPython Development Team.
6 # Copyright (c) 2013, the IPython Development Team.
7 #
7 #
8 # Distributed under the terms of the Modified BSD License.
8 # Distributed under the terms of the Modified BSD License.
9 #
9 #
10 # The full license is in the file COPYING.txt, distributed with this software.
10 # The full license is in the file COPYING.txt, distributed with this software.
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12
12
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14 # Imports
14 # Imports
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 from .widget import Widget
16 from .widget import Widget
17 from IPython.utils.traitlets import Unicode, Bool
17 from IPython.utils.traitlets import Unicode, Bool, List, Instance
18
18
19 #-----------------------------------------------------------------------------
19 #-----------------------------------------------------------------------------
20 # Classes
20 # Classes
21 #-----------------------------------------------------------------------------
21 #-----------------------------------------------------------------------------
22 class ContainerWidget(Widget):
22 class ContainerWidget(Widget):
23 target_name = Unicode('ContainerWidgetModel')
23 target_name = Unicode('ContainerWidgetModel')
24 default_view_name = Unicode('ContainerView')
24 default_view_name = Unicode('ContainerView')
25
25
26 children = []#List(Instance('IPython.html.widgets.widget.Widget'))
27 _children_lists_attr = List(Unicode, ['children'])
28
26 # Keys, all private and managed by helper methods. Flexible box model
29 # Keys, all private and managed by helper methods. Flexible box model
27 # classes...
30 # classes...
28 _keys = ['_vbox', '_hbox', '_align_start', '_align_end', '_align_center',
31 _keys = ['_vbox', '_hbox', '_align_start', '_align_end', '_align_center',
29 '_pack_start', '_pack_end', '_pack_center', '_flex0', '_flex1',
32 '_pack_start', '_pack_end', '_pack_center', '_flex0', '_flex1',
30 '_flex2', 'description', 'button_text']
33 '_flex2', 'description', 'button_text']
31 description = Unicode()
34 description = Unicode()
32 button_text = Unicode()
35 button_text = Unicode()
33 _hbox = Bool(False)
36 _hbox = Bool(False)
34 _vbox = Bool(False)
37 _vbox = Bool(False)
35 _align_start = Bool(False)
38 _align_start = Bool(False)
36 _align_end = Bool(False)
39 _align_end = Bool(False)
37 _align_center = Bool(False)
40 _align_center = Bool(False)
38 _pack_start = Bool(False)
41 _pack_start = Bool(False)
39 _pack_end = Bool(False)
42 _pack_end = Bool(False)
40 _pack_center = Bool(False)
43 _pack_center = Bool(False)
41 _flex0 = Bool(False)
44 _flex0 = Bool(False)
42 _flex1 = Bool(False)
45 _flex1 = Bool(False)
43 _flex2 = Bool(False)
46 _flex2 = Bool(False)
44
47
45 def hbox(self, enabled=True):
48 def hbox(self, enabled=True):
46 """Make this container an hbox. Automatically disables conflicting
49 """Make this container an hbox. Automatically disables conflicting
47 features.
50 features.
48
51
49 Parameters
52 Parameters
50 ----------
53 ----------
51 enabled: bool (optional)
54 enabled: bool (optional)
52 Enabled or disable the hbox feature of the container, defaults to
55 Enabled or disable the hbox feature of the container, defaults to
53 True."""
56 True."""
54 self._hbox = enabled
57 self._hbox = enabled
55 if enabled:
58 if enabled:
56 self._vbox = False
59 self._vbox = False
57
60
58 def vbox(self, enabled=True):
61 def vbox(self, enabled=True):
59 """Make this container an vbox. Automatically disables conflicting
62 """Make this container an vbox. Automatically disables conflicting
60 features.
63 features.
61
64
62 Parameters
65 Parameters
63 ----------
66 ----------
64 enabled: bool (optional)
67 enabled: bool (optional)
65 Enabled or disable the vbox feature of the container, defaults to
68 Enabled or disable the vbox feature of the container, defaults to
66 True."""
69 True."""
67 self._vbox = enabled
70 self._vbox = enabled
68 if enabled:
71 if enabled:
69 self._hbox = False
72 self._hbox = False
70
73
71 def align_start(self, enabled=True):
74 def align_start(self, enabled=True):
72 """Make the contents of this container align to the start of the axis.
75 """Make the contents of this container align to the start of the axis.
73 Automatically disables conflicting alignments.
76 Automatically disables conflicting alignments.
74
77
75 Parameters
78 Parameters
76 ----------
79 ----------
77 enabled: bool (optional)
80 enabled: bool (optional)
78 Enabled or disable the start alignment of the container, defaults to
81 Enabled or disable the start alignment of the container, defaults to
79 True."""
82 True."""
80 self._align_start = enabled
83 self._align_start = enabled
81 if enabled:
84 if enabled:
82 self._align_end = False
85 self._align_end = False
83 self._align_center = False
86 self._align_center = False
84
87
85 def align_end(self, enabled=True):
88 def align_end(self, enabled=True):
86 """Make the contents of this container align to the end of the axis.
89 """Make the contents of this container align to the end of the axis.
87 Automatically disables conflicting alignments.
90 Automatically disables conflicting alignments.
88
91
89 Parameters
92 Parameters
90 ----------
93 ----------
91 enabled: bool (optional)
94 enabled: bool (optional)
92 Enabled or disable the end alignment of the container, defaults to
95 Enabled or disable the end alignment of the container, defaults to
93 True."""
96 True."""
94 self._align_end = enabled
97 self._align_end = enabled
95 if enabled:
98 if enabled:
96 self._align_start = False
99 self._align_start = False
97 self._align_center = False
100 self._align_center = False
98
101
99 def align_center(self, enabled=True):
102 def align_center(self, enabled=True):
100 """Make the contents of this container align to the center of the axis.
103 """Make the contents of this container align to the center of the axis.
101 Automatically disables conflicting alignments.
104 Automatically disables conflicting alignments.
102
105
103 Parameters
106 Parameters
104 ----------
107 ----------
105 enabled: bool (optional)
108 enabled: bool (optional)
106 Enabled or disable the center alignment of the container, defaults to
109 Enabled or disable the center alignment of the container, defaults to
107 True."""
110 True."""
108 self._align_center = enabled
111 self._align_center = enabled
109 if enabled:
112 if enabled:
110 self._align_start = False
113 self._align_start = False
111 self._align_end = False
114 self._align_end = False
112
115
113
116
114 def pack_start(self, enabled=True):
117 def pack_start(self, enabled=True):
115 """Make the contents of this container pack to the start of the axis.
118 """Make the contents of this container pack to the start of the axis.
116 Automatically disables conflicting packings.
119 Automatically disables conflicting packings.
117
120
118 Parameters
121 Parameters
119 ----------
122 ----------
120 enabled: bool (optional)
123 enabled: bool (optional)
121 Enabled or disable the start packing of the container, defaults to
124 Enabled or disable the start packing of the container, defaults to
122 True."""
125 True."""
123 self._pack_start = enabled
126 self._pack_start = enabled
124 if enabled:
127 if enabled:
125 self._pack_end = False
128 self._pack_end = False
126 self._pack_center = False
129 self._pack_center = False
127
130
128 def pack_end(self, enabled=True):
131 def pack_end(self, enabled=True):
129 """Make the contents of this container pack to the end of the axis.
132 """Make the contents of this container pack to the end of the axis.
130 Automatically disables conflicting packings.
133 Automatically disables conflicting packings.
131
134
132 Parameters
135 Parameters
133 ----------
136 ----------
134 enabled: bool (optional)
137 enabled: bool (optional)
135 Enabled or disable the end packing of the container, defaults to
138 Enabled or disable the end packing of the container, defaults to
136 True."""
139 True."""
137 self._pack_end = enabled
140 self._pack_end = enabled
138 if enabled:
141 if enabled:
139 self._pack_start = False
142 self._pack_start = False
140 self._pack_center = False
143 self._pack_center = False
141
144
142 def pack_center(self, enabled=True):
145 def pack_center(self, enabled=True):
143 """Make the contents of this container pack to the center of the axis.
146 """Make the contents of this container pack to the center of the axis.
144 Automatically disables conflicting packings.
147 Automatically disables conflicting packings.
145
148
146 Parameters
149 Parameters
147 ----------
150 ----------
148 enabled: bool (optional)
151 enabled: bool (optional)
149 Enabled or disable the center packing of the container, defaults to
152 Enabled or disable the center packing of the container, defaults to
150 True."""
153 True."""
151 self._pack_center = enabled
154 self._pack_center = enabled
152 if enabled:
155 if enabled:
153 self._pack_start = False
156 self._pack_start = False
154 self._pack_end = False
157 self._pack_end = False
155
158
156
159
157 def flex0(self, enabled=True):
160 def flex0(self, enabled=True):
158 """Put this container in flex0 mode. Automatically disables conflicting
161 """Put this container in flex0 mode. Automatically disables conflicting
159 flex modes. See the widget tutorial part 5 example notebook for more
162 flex modes. See the widget tutorial part 5 example notebook for more
160 information.
163 information.
161
164
162 Parameters
165 Parameters
163 ----------
166 ----------
164 enabled: bool (optional)
167 enabled: bool (optional)
165 Enabled or disable the flex0 attribute of the container, defaults to
168 Enabled or disable the flex0 attribute of the container, defaults to
166 True."""
169 True."""
167 self._flex0 = enabled
170 self._flex0 = enabled
168 if enabled:
171 if enabled:
169 self._flex1 = False
172 self._flex1 = False
170 self._flex2 = False
173 self._flex2 = False
171
174
172 def flex1(self, enabled=True):
175 def flex1(self, enabled=True):
173 """Put this container in flex1 mode. Automatically disables conflicting
176 """Put this container in flex1 mode. Automatically disables conflicting
174 flex modes. See the widget tutorial part 5 example notebook for more
177 flex modes. See the widget tutorial part 5 example notebook for more
175 information.
178 information.
176
179
177 Parameters
180 Parameters
178 ----------
181 ----------
179 enabled: bool (optional)
182 enabled: bool (optional)
180 Enabled or disable the flex1 attribute of the container, defaults to
183 Enabled or disable the flex1 attribute of the container, defaults to
181 True."""
184 True."""
182 self._flex1 = enabled
185 self._flex1 = enabled
183 if enabled:
186 if enabled:
184 self._flex0 = False
187 self._flex0 = False
185 self._flex2 = False
188 self._flex2 = False
186
189
187 def flex2(self, enabled=True):
190 def flex2(self, enabled=True):
188 """Put this container in flex2 mode. Automatically disables conflicting
191 """Put this container in flex2 mode. Automatically disables conflicting
189 flex modes. See the widget tutorial part 5 example notebook for more
192 flex modes. See the widget tutorial part 5 example notebook for more
190 information.
193 information.
191
194
192 Parameters
195 Parameters
193 ----------
196 ----------
194 enabled: bool (optional)
197 enabled: bool (optional)
195 Enabled or disable the flex2 attribute of the container, defaults to
198 Enabled or disable the flex2 attribute of the container, defaults to
196 True."""
199 True."""
197 self._flex2 = enabled
200 self._flex2 = enabled
198 if enabled:
201 if enabled:
199 self._flex0 = False
202 self._flex0 = False
200 self._flex1 = False
203 self._flex1 = False
@@ -1,56 +1,59 b''
1 """MulticontainerWidget class.
1 """MulticontainerWidget class.
2
2
3 Represents a multipage container that can be used to group other widgets into
3 Represents a multipage container that can be used to group other widgets into
4 pages.
4 pages.
5 """
5 """
6 #-----------------------------------------------------------------------------
6 #-----------------------------------------------------------------------------
7 # Copyright (c) 2013, the IPython Development Team.
7 # Copyright (c) 2013, the IPython Development Team.
8 #
8 #
9 # Distributed under the terms of the Modified BSD License.
9 # Distributed under the terms of the Modified BSD License.
10 #
10 #
11 # The full license is in the file COPYING.txt, distributed with this software.
11 # The full license is in the file COPYING.txt, distributed with this software.
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13
13
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15 # Imports
15 # Imports
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17 from .widget import Widget
17 from .widget import Widget
18 from IPython.utils.traitlets import Unicode, Dict, Int
18 from IPython.utils.traitlets import Unicode, Dict, Int, List, Instance
19
19
20 #-----------------------------------------------------------------------------
20 #-----------------------------------------------------------------------------
21 # Classes
21 # Classes
22 #-----------------------------------------------------------------------------
22 #-----------------------------------------------------------------------------
23 class MulticontainerWidget(Widget):
23 class MulticontainerWidget(Widget):
24 target_name = Unicode('MulticontainerWidgetModel')
24 target_name = Unicode('MulticontainerWidgetModel')
25 default_view_name = Unicode('TabView')
25 default_view_name = Unicode('TabView')
26
26
27 # Keys
27 # Keys
28 _keys = ['_titles', 'selected_index']
28 _keys = ['_titles', 'selected_index']
29 _titles = Dict(help="Titles of the pages")
29 _titles = Dict(help="Titles of the pages")
30 selected_index = Int(0)
30 selected_index = Int(0)
31
31
32 children = []#List(Instance('IPython.html.widgets.widget.Widget'))
33 _children_lists_attr = List(Unicode, ['children'])
34
32 # Public methods
35 # Public methods
33 def set_title(self, index, title):
36 def set_title(self, index, title):
34 """Sets the title of a container pages
37 """Sets the title of a container page
35
38
36 Parameters
39 Parameters
37 ----------
40 ----------
38 index : int
41 index : int
39 Index of the container page
42 Index of the container page
40 title : unicode
43 title : unicode
41 New title"""
44 New title"""
42 self._titles[index] = title
45 self._titles[index] = title
43 self.send_state('_titles')
46 self.send_state('_titles')
44
47
45
48
46 def get_title(self, index):
49 def get_title(self, index):
47 """Gets the title of a container pages
50 """Gets the title of a container pages
48
51
49 Parameters
52 Parameters
50 ----------
53 ----------
51 index : int
54 index : int
52 Index of the container page"""
55 Index of the container page"""
53 if index in self._titles:
56 if index in self._titles:
54 return self._titles[index]
57 return self._titles[index]
55 else:
58 else:
56 return None
59 return None
@@ -1,1442 +1,1445 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 A lightweight Traits like module.
3 A lightweight Traits like module.
4
4
5 This is designed to provide a lightweight, simple, pure Python version of
5 This is designed to provide a lightweight, simple, pure Python version of
6 many of the capabilities of enthought.traits. This includes:
6 many of the capabilities of enthought.traits. This includes:
7
7
8 * Validation
8 * Validation
9 * Type specification with defaults
9 * Type specification with defaults
10 * Static and dynamic notification
10 * Static and dynamic notification
11 * Basic predefined types
11 * Basic predefined types
12 * An API that is similar to enthought.traits
12 * An API that is similar to enthought.traits
13
13
14 We don't support:
14 We don't support:
15
15
16 * Delegation
16 * Delegation
17 * Automatic GUI generation
17 * Automatic GUI generation
18 * A full set of trait types. Most importantly, we don't provide container
18 * A full set of trait types. Most importantly, we don't provide container
19 traits (list, dict, tuple) that can trigger notifications if their
19 traits (list, dict, tuple) that can trigger notifications if their
20 contents change.
20 contents change.
21 * API compatibility with enthought.traits
21 * API compatibility with enthought.traits
22
22
23 There are also some important difference in our design:
23 There are also some important difference in our design:
24
24
25 * enthought.traits does not validate default values. We do.
25 * enthought.traits does not validate default values. We do.
26
26
27 We choose to create this module because we need these capabilities, but
27 We choose to create this module because we need these capabilities, but
28 we need them to be pure Python so they work in all Python implementations,
28 we need them to be pure Python so they work in all Python implementations,
29 including Jython and IronPython.
29 including Jython and IronPython.
30
30
31 Inheritance diagram:
31 Inheritance diagram:
32
32
33 .. inheritance-diagram:: IPython.utils.traitlets
33 .. inheritance-diagram:: IPython.utils.traitlets
34 :parts: 3
34 :parts: 3
35
35
36 Authors:
36 Authors:
37
37
38 * Brian Granger
38 * Brian Granger
39 * Enthought, Inc. Some of the code in this file comes from enthought.traits
39 * Enthought, Inc. Some of the code in this file comes from enthought.traits
40 and is licensed under the BSD license. Also, many of the ideas also come
40 and is licensed under the BSD license. Also, many of the ideas also come
41 from enthought.traits even though our implementation is very different.
41 from enthought.traits even though our implementation is very different.
42 """
42 """
43
43
44 #-----------------------------------------------------------------------------
44 #-----------------------------------------------------------------------------
45 # Copyright (C) 2008-2011 The IPython Development Team
45 # Copyright (C) 2008-2011 The IPython Development Team
46 #
46 #
47 # Distributed under the terms of the BSD License. The full license is in
47 # Distributed under the terms of the BSD License. The full license is in
48 # the file COPYING, distributed as part of this software.
48 # the file COPYING, distributed as part of this software.
49 #-----------------------------------------------------------------------------
49 #-----------------------------------------------------------------------------
50
50
51 #-----------------------------------------------------------------------------
51 #-----------------------------------------------------------------------------
52 # Imports
52 # Imports
53 #-----------------------------------------------------------------------------
53 #-----------------------------------------------------------------------------
54
54
55
55
56 import inspect
56 import inspect
57 import re
57 import re
58 import sys
58 import sys
59 import types
59 import types
60 from types import FunctionType
60 from types import FunctionType
61 try:
61 try:
62 from types import ClassType, InstanceType
62 from types import ClassType, InstanceType
63 ClassTypes = (ClassType, type)
63 ClassTypes = (ClassType, type)
64 except:
64 except:
65 ClassTypes = (type,)
65 ClassTypes = (type,)
66
66
67 from .importstring import import_item
67 from .importstring import import_item
68 from IPython.utils import py3compat
68 from IPython.utils import py3compat
69 from IPython.utils.py3compat import iteritems
69 from IPython.utils.py3compat import iteritems
70
70
71 SequenceTypes = (list, tuple, set, frozenset)
71 SequenceTypes = (list, tuple, set, frozenset)
72
72
73 #-----------------------------------------------------------------------------
73 #-----------------------------------------------------------------------------
74 # Basic classes
74 # Basic classes
75 #-----------------------------------------------------------------------------
75 #-----------------------------------------------------------------------------
76
76
77
77
78 class NoDefaultSpecified ( object ): pass
78 class NoDefaultSpecified ( object ): pass
79 NoDefaultSpecified = NoDefaultSpecified()
79 NoDefaultSpecified = NoDefaultSpecified()
80
80
81
81
82 class Undefined ( object ): pass
82 class Undefined ( object ): pass
83 Undefined = Undefined()
83 Undefined = Undefined()
84
84
85 class TraitError(Exception):
85 class TraitError(Exception):
86 pass
86 pass
87
87
88 #-----------------------------------------------------------------------------
88 #-----------------------------------------------------------------------------
89 # Utilities
89 # Utilities
90 #-----------------------------------------------------------------------------
90 #-----------------------------------------------------------------------------
91
91
92
92
93 def class_of ( object ):
93 def class_of ( object ):
94 """ Returns a string containing the class name of an object with the
94 """ Returns a string containing the class name of an object with the
95 correct indefinite article ('a' or 'an') preceding it (e.g., 'an Image',
95 correct indefinite article ('a' or 'an') preceding it (e.g., 'an Image',
96 'a PlotValue').
96 'a PlotValue').
97 """
97 """
98 if isinstance( object, py3compat.string_types ):
98 if isinstance( object, py3compat.string_types ):
99 return add_article( object )
99 return add_article( object )
100
100
101 return add_article( object.__class__.__name__ )
101 return add_article( object.__class__.__name__ )
102
102
103
103
104 def add_article ( name ):
104 def add_article ( name ):
105 """ Returns a string containing the correct indefinite article ('a' or 'an')
105 """ Returns a string containing the correct indefinite article ('a' or 'an')
106 prefixed to the specified string.
106 prefixed to the specified string.
107 """
107 """
108 if name[:1].lower() in 'aeiou':
108 if name[:1].lower() in 'aeiou':
109 return 'an ' + name
109 return 'an ' + name
110
110
111 return 'a ' + name
111 return 'a ' + name
112
112
113
113
114 def repr_type(obj):
114 def repr_type(obj):
115 """ Return a string representation of a value and its type for readable
115 """ Return a string representation of a value and its type for readable
116 error messages.
116 error messages.
117 """
117 """
118 the_type = type(obj)
118 the_type = type(obj)
119 if (not py3compat.PY3) and the_type is InstanceType:
119 if (not py3compat.PY3) and the_type is InstanceType:
120 # Old-style class.
120 # Old-style class.
121 the_type = obj.__class__
121 the_type = obj.__class__
122 msg = '%r %r' % (obj, the_type)
122 msg = '%r %r' % (obj, the_type)
123 return msg
123 return msg
124
124
125
125
126 def is_trait(t):
126 def is_trait(t):
127 """ Returns whether the given value is an instance or subclass of TraitType.
127 """ Returns whether the given value is an instance or subclass of TraitType.
128 """
128 """
129 return (isinstance(t, TraitType) or
129 return (isinstance(t, TraitType) or
130 (isinstance(t, type) and issubclass(t, TraitType)))
130 (isinstance(t, type) and issubclass(t, TraitType)))
131
131
132
132
133 def parse_notifier_name(name):
133 def parse_notifier_name(name):
134 """Convert the name argument to a list of names.
134 """Convert the name argument to a list of names.
135
135
136 Examples
136 Examples
137 --------
137 --------
138
138
139 >>> parse_notifier_name('a')
139 >>> parse_notifier_name('a')
140 ['a']
140 ['a']
141 >>> parse_notifier_name(['a','b'])
141 >>> parse_notifier_name(['a','b'])
142 ['a', 'b']
142 ['a', 'b']
143 >>> parse_notifier_name(None)
143 >>> parse_notifier_name(None)
144 ['anytrait']
144 ['anytrait']
145 """
145 """
146 if isinstance(name, str):
146 if isinstance(name, str):
147 return [name]
147 return [name]
148 elif name is None:
148 elif name is None:
149 return ['anytrait']
149 return ['anytrait']
150 elif isinstance(name, (list, tuple)):
150 elif isinstance(name, (list, tuple)):
151 for n in name:
151 for n in name:
152 assert isinstance(n, str), "names must be strings"
152 assert isinstance(n, basestring), "names must be strings: %s, %r"%(type(n), n)
153 return name
153 return name
154
154
155
155
156 class _SimpleTest:
156 class _SimpleTest:
157 def __init__ ( self, value ): self.value = value
157 def __init__ ( self, value ): self.value = value
158 def __call__ ( self, test ):
158 def __call__ ( self, test ):
159 return test == self.value
159 return test == self.value
160 def __repr__(self):
160 def __repr__(self):
161 return "<SimpleTest(%r)" % self.value
161 return "<SimpleTest(%r)" % self.value
162 def __str__(self):
162 def __str__(self):
163 return self.__repr__()
163 return self.__repr__()
164
164
165
165
166 def getmembers(object, predicate=None):
166 def getmembers(object, predicate=None):
167 """A safe version of inspect.getmembers that handles missing attributes.
167 """A safe version of inspect.getmembers that handles missing attributes.
168
168
169 This is useful when there are descriptor based attributes that for
169 This is useful when there are descriptor based attributes that for
170 some reason raise AttributeError even though they exist. This happens
170 some reason raise AttributeError even though they exist. This happens
171 in zope.inteface with the __provides__ attribute.
171 in zope.inteface with the __provides__ attribute.
172 """
172 """
173 results = []
173 results = []
174 for key in dir(object):
174 for key in dir(object):
175 try:
175 try:
176 value = getattr(object, key)
176 value = getattr(object, key)
177 except AttributeError:
177 except AttributeError:
178 pass
178 pass
179 else:
179 else:
180 if not predicate or predicate(value):
180 if not predicate or predicate(value):
181 results.append((key, value))
181 results.append((key, value))
182 results.sort()
182 results.sort()
183 return results
183 return results
184
184
185
185
186 #-----------------------------------------------------------------------------
186 #-----------------------------------------------------------------------------
187 # Base TraitType for all traits
187 # Base TraitType for all traits
188 #-----------------------------------------------------------------------------
188 #-----------------------------------------------------------------------------
189
189
190
190
191 class TraitType(object):
191 class TraitType(object):
192 """A base class for all trait descriptors.
192 """A base class for all trait descriptors.
193
193
194 Notes
194 Notes
195 -----
195 -----
196 Our implementation of traits is based on Python's descriptor
196 Our implementation of traits is based on Python's descriptor
197 prototol. This class is the base class for all such descriptors. The
197 prototol. This class is the base class for all such descriptors. The
198 only magic we use is a custom metaclass for the main :class:`HasTraits`
198 only magic we use is a custom metaclass for the main :class:`HasTraits`
199 class that does the following:
199 class that does the following:
200
200
201 1. Sets the :attr:`name` attribute of every :class:`TraitType`
201 1. Sets the :attr:`name` attribute of every :class:`TraitType`
202 instance in the class dict to the name of the attribute.
202 instance in the class dict to the name of the attribute.
203 2. Sets the :attr:`this_class` attribute of every :class:`TraitType`
203 2. Sets the :attr:`this_class` attribute of every :class:`TraitType`
204 instance in the class dict to the *class* that declared the trait.
204 instance in the class dict to the *class* that declared the trait.
205 This is used by the :class:`This` trait to allow subclasses to
205 This is used by the :class:`This` trait to allow subclasses to
206 accept superclasses for :class:`This` values.
206 accept superclasses for :class:`This` values.
207 """
207 """
208
208
209
209
210 metadata = {}
210 metadata = {}
211 default_value = Undefined
211 default_value = Undefined
212 info_text = 'any value'
212 info_text = 'any value'
213
213
214 def __init__(self, default_value=NoDefaultSpecified, **metadata):
214 def __init__(self, default_value=NoDefaultSpecified, **metadata):
215 """Create a TraitType.
215 """Create a TraitType.
216 """
216 """
217 if default_value is not NoDefaultSpecified:
217 if default_value is not NoDefaultSpecified:
218 self.default_value = default_value
218 self.default_value = default_value
219
219
220 if len(metadata) > 0:
220 if len(metadata) > 0:
221 if len(self.metadata) > 0:
221 if len(self.metadata) > 0:
222 self._metadata = self.metadata.copy()
222 self._metadata = self.metadata.copy()
223 self._metadata.update(metadata)
223 self._metadata.update(metadata)
224 else:
224 else:
225 self._metadata = metadata
225 self._metadata = metadata
226 else:
226 else:
227 self._metadata = self.metadata
227 self._metadata = self.metadata
228
228
229 self.init()
229 self.init()
230
230
231 def init(self):
231 def init(self):
232 pass
232 pass
233
233
234 def get_default_value(self):
234 def get_default_value(self):
235 """Create a new instance of the default value."""
235 """Create a new instance of the default value."""
236 return self.default_value
236 return self.default_value
237
237
238 def instance_init(self, obj):
238 def instance_init(self, obj):
239 """This is called by :meth:`HasTraits.__new__` to finish init'ing.
239 """This is called by :meth:`HasTraits.__new__` to finish init'ing.
240
240
241 Some stages of initialization must be delayed until the parent
241 Some stages of initialization must be delayed until the parent
242 :class:`HasTraits` instance has been created. This method is
242 :class:`HasTraits` instance has been created. This method is
243 called in :meth:`HasTraits.__new__` after the instance has been
243 called in :meth:`HasTraits.__new__` after the instance has been
244 created.
244 created.
245
245
246 This method trigger the creation and validation of default values
246 This method trigger the creation and validation of default values
247 and also things like the resolution of str given class names in
247 and also things like the resolution of str given class names in
248 :class:`Type` and :class`Instance`.
248 :class:`Type` and :class`Instance`.
249
249
250 Parameters
250 Parameters
251 ----------
251 ----------
252 obj : :class:`HasTraits` instance
252 obj : :class:`HasTraits` instance
253 The parent :class:`HasTraits` instance that has just been
253 The parent :class:`HasTraits` instance that has just been
254 created.
254 created.
255 """
255 """
256 self.set_default_value(obj)
256 self.set_default_value(obj)
257
257
258 def set_default_value(self, obj):
258 def set_default_value(self, obj):
259 """Set the default value on a per instance basis.
259 """Set the default value on a per instance basis.
260
260
261 This method is called by :meth:`instance_init` to create and
261 This method is called by :meth:`instance_init` to create and
262 validate the default value. The creation and validation of
262 validate the default value. The creation and validation of
263 default values must be delayed until the parent :class:`HasTraits`
263 default values must be delayed until the parent :class:`HasTraits`
264 class has been instantiated.
264 class has been instantiated.
265 """
265 """
266 # Check for a deferred initializer defined in the same class as the
266 # Check for a deferred initializer defined in the same class as the
267 # trait declaration or above.
267 # trait declaration or above.
268 mro = type(obj).mro()
268 mro = type(obj).mro()
269 meth_name = '_%s_default' % self.name
269 meth_name = '_%s_default' % self.name
270 for cls in mro[:mro.index(self.this_class)+1]:
270 for cls in mro[:mro.index(self.this_class)+1]:
271 if meth_name in cls.__dict__:
271 if meth_name in cls.__dict__:
272 break
272 break
273 else:
273 else:
274 # We didn't find one. Do static initialization.
274 # We didn't find one. Do static initialization.
275 dv = self.get_default_value()
275 dv = self.get_default_value()
276 newdv = self._validate(obj, dv)
276 newdv = self._validate(obj, dv)
277 obj._trait_values[self.name] = newdv
277 obj._trait_values[self.name] = newdv
278 return
278 return
279 # Complete the dynamic initialization.
279 # Complete the dynamic initialization.
280 obj._trait_dyn_inits[self.name] = cls.__dict__[meth_name]
280 obj._trait_dyn_inits[self.name] = cls.__dict__[meth_name]
281
281
282 def __get__(self, obj, cls=None):
282 def __get__(self, obj, cls=None):
283 """Get the value of the trait by self.name for the instance.
283 """Get the value of the trait by self.name for the instance.
284
284
285 Default values are instantiated when :meth:`HasTraits.__new__`
285 Default values are instantiated when :meth:`HasTraits.__new__`
286 is called. Thus by the time this method gets called either the
286 is called. Thus by the time this method gets called either the
287 default value or a user defined value (they called :meth:`__set__`)
287 default value or a user defined value (they called :meth:`__set__`)
288 is in the :class:`HasTraits` instance.
288 is in the :class:`HasTraits` instance.
289 """
289 """
290 if obj is None:
290 if obj is None:
291 return self
291 return self
292 else:
292 else:
293 try:
293 try:
294 value = obj._trait_values[self.name]
294 value = obj._trait_values[self.name]
295 except KeyError:
295 except KeyError:
296 # Check for a dynamic initializer.
296 # Check for a dynamic initializer.
297 if self.name in obj._trait_dyn_inits:
297 if self.name in obj._trait_dyn_inits:
298 value = obj._trait_dyn_inits[self.name](obj)
298 value = obj._trait_dyn_inits[self.name](obj)
299 # FIXME: Do we really validate here?
299 # FIXME: Do we really validate here?
300 value = self._validate(obj, value)
300 value = self._validate(obj, value)
301 obj._trait_values[self.name] = value
301 obj._trait_values[self.name] = value
302 return value
302 return value
303 else:
303 else:
304 raise TraitError('Unexpected error in TraitType: '
304 raise TraitError('Unexpected error in TraitType: '
305 'both default value and dynamic initializer are '
305 'both default value and dynamic initializer are '
306 'absent.')
306 'absent.')
307 except Exception:
307 except Exception:
308 # HasTraits should call set_default_value to populate
308 # HasTraits should call set_default_value to populate
309 # this. So this should never be reached.
309 # this. So this should never be reached.
310 raise TraitError('Unexpected error in TraitType: '
310 raise TraitError('Unexpected error in TraitType: '
311 'default value not set properly')
311 'default value not set properly')
312 else:
312 else:
313 return value
313 return value
314
314
315 def __set__(self, obj, value):
315 def __set__(self, obj, value):
316 new_value = self._validate(obj, value)
316 new_value = self._validate(obj, value)
317 old_value = self.__get__(obj)
317 old_value = self.__get__(obj)
318 obj._trait_values[self.name] = new_value
318 obj._trait_values[self.name] = new_value
319 if old_value != new_value:
319 if old_value != new_value:
320 obj._notify_trait(self.name, old_value, new_value)
320 obj._notify_trait(self.name, old_value, new_value)
321
321
322 def _validate(self, obj, value):
322 def _validate(self, obj, value):
323 if hasattr(self, 'validate'):
323 if hasattr(self, 'validate'):
324 return self.validate(obj, value)
324 return self.validate(obj, value)
325 elif hasattr(self, 'is_valid_for'):
325 elif hasattr(self, 'is_valid_for'):
326 valid = self.is_valid_for(value)
326 valid = self.is_valid_for(value)
327 if valid:
327 if valid:
328 return value
328 return value
329 else:
329 else:
330 raise TraitError('invalid value for type: %r' % value)
330 raise TraitError('invalid value for type: %r' % value)
331 elif hasattr(self, 'value_for'):
331 elif hasattr(self, 'value_for'):
332 return self.value_for(value)
332 return self.value_for(value)
333 else:
333 else:
334 return value
334 return value
335
335
336 def info(self):
336 def info(self):
337 return self.info_text
337 return self.info_text
338
338
339 def error(self, obj, value):
339 def error(self, obj, value):
340 if obj is not None:
340 if obj is not None:
341 e = "The '%s' trait of %s instance must be %s, but a value of %s was specified." \
341 e = "The '%s' trait of %s instance must be %s, but a value of %s was specified." \
342 % (self.name, class_of(obj),
342 % (self.name, class_of(obj),
343 self.info(), repr_type(value))
343 self.info(), repr_type(value))
344 else:
344 else:
345 e = "The '%s' trait must be %s, but a value of %r was specified." \
345 e = "The '%s' trait must be %s, but a value of %r was specified." \
346 % (self.name, self.info(), repr_type(value))
346 % (self.name, self.info(), repr_type(value))
347 raise TraitError(e)
347 raise TraitError(e)
348
348
349 def get_metadata(self, key):
349 def get_metadata(self, key):
350 return getattr(self, '_metadata', {}).get(key, None)
350 return getattr(self, '_metadata', {}).get(key, None)
351
351
352 def set_metadata(self, key, value):
352 def set_metadata(self, key, value):
353 getattr(self, '_metadata', {})[key] = value
353 getattr(self, '_metadata', {})[key] = value
354
354
355
355
356 #-----------------------------------------------------------------------------
356 #-----------------------------------------------------------------------------
357 # The HasTraits implementation
357 # The HasTraits implementation
358 #-----------------------------------------------------------------------------
358 #-----------------------------------------------------------------------------
359
359
360
360
361 class MetaHasTraits(type):
361 class MetaHasTraits(type):
362 """A metaclass for HasTraits.
362 """A metaclass for HasTraits.
363
363
364 This metaclass makes sure that any TraitType class attributes are
364 This metaclass makes sure that any TraitType class attributes are
365 instantiated and sets their name attribute.
365 instantiated and sets their name attribute.
366 """
366 """
367
367
368 def __new__(mcls, name, bases, classdict):
368 def __new__(mcls, name, bases, classdict):
369 """Create the HasTraits class.
369 """Create the HasTraits class.
370
370
371 This instantiates all TraitTypes in the class dict and sets their
371 This instantiates all TraitTypes in the class dict and sets their
372 :attr:`name` attribute.
372 :attr:`name` attribute.
373 """
373 """
374 # print "MetaHasTraitlets (mcls, name): ", mcls, name
374 # print "MetaHasTraitlets (mcls, name): ", mcls, name
375 # print "MetaHasTraitlets (bases): ", bases
375 # print "MetaHasTraitlets (bases): ", bases
376 # print "MetaHasTraitlets (classdict): ", classdict
376 # print "MetaHasTraitlets (classdict): ", classdict
377 for k,v in iteritems(classdict):
377 for k,v in iteritems(classdict):
378 if isinstance(v, TraitType):
378 if isinstance(v, TraitType):
379 v.name = k
379 v.name = k
380 elif inspect.isclass(v):
380 elif inspect.isclass(v):
381 if issubclass(v, TraitType):
381 if issubclass(v, TraitType):
382 vinst = v()
382 vinst = v()
383 vinst.name = k
383 vinst.name = k
384 classdict[k] = vinst
384 classdict[k] = vinst
385 return super(MetaHasTraits, mcls).__new__(mcls, name, bases, classdict)
385 return super(MetaHasTraits, mcls).__new__(mcls, name, bases, classdict)
386
386
387 def __init__(cls, name, bases, classdict):
387 def __init__(cls, name, bases, classdict):
388 """Finish initializing the HasTraits class.
388 """Finish initializing the HasTraits class.
389
389
390 This sets the :attr:`this_class` attribute of each TraitType in the
390 This sets the :attr:`this_class` attribute of each TraitType in the
391 class dict to the newly created class ``cls``.
391 class dict to the newly created class ``cls``.
392 """
392 """
393 for k, v in iteritems(classdict):
393 for k, v in iteritems(classdict):
394 if isinstance(v, TraitType):
394 if isinstance(v, TraitType):
395 v.this_class = cls
395 v.this_class = cls
396 super(MetaHasTraits, cls).__init__(name, bases, classdict)
396 super(MetaHasTraits, cls).__init__(name, bases, classdict)
397
397
398 class HasTraits(py3compat.with_metaclass(MetaHasTraits, object)):
398 class HasTraits(py3compat.with_metaclass(MetaHasTraits, object)):
399
399
400 def __new__(cls, *args, **kw):
400 def __new__(cls, *args, **kw):
401 # This is needed because in Python 2.6 object.__new__ only accepts
401 # This is needed because in Python 2.6 object.__new__ only accepts
402 # the cls argument.
402 # the cls argument.
403 new_meth = super(HasTraits, cls).__new__
403 new_meth = super(HasTraits, cls).__new__
404 if new_meth is object.__new__:
404 if new_meth is object.__new__:
405 inst = new_meth(cls)
405 inst = new_meth(cls)
406 else:
406 else:
407 inst = new_meth(cls, **kw)
407 inst = new_meth(cls, **kw)
408 inst._trait_values = {}
408 inst._trait_values = {}
409 inst._trait_notifiers = {}
409 inst._trait_notifiers = {}
410 inst._trait_dyn_inits = {}
410 inst._trait_dyn_inits = {}
411 # Here we tell all the TraitType instances to set their default
411 # Here we tell all the TraitType instances to set their default
412 # values on the instance.
412 # values on the instance.
413 for key in dir(cls):
413 for key in dir(cls):
414 # Some descriptors raise AttributeError like zope.interface's
414 # Some descriptors raise AttributeError like zope.interface's
415 # __provides__ attributes even though they exist. This causes
415 # __provides__ attributes even though they exist. This causes
416 # AttributeErrors even though they are listed in dir(cls).
416 # AttributeErrors even though they are listed in dir(cls).
417 try:
417 try:
418 value = getattr(cls, key)
418 value = getattr(cls, key)
419 except AttributeError:
419 except AttributeError:
420 pass
420 pass
421 else:
421 else:
422 if isinstance(value, TraitType):
422 if isinstance(value, TraitType):
423 value.instance_init(inst)
423 value.instance_init(inst)
424
424
425 return inst
425 return inst
426
426
427 def __init__(self, *args, **kw):
427 def __init__(self, *args, **kw):
428 # Allow trait values to be set using keyword arguments.
428 # Allow trait values to be set using keyword arguments.
429 # We need to use setattr for this to trigger validation and
429 # We need to use setattr for this to trigger validation and
430 # notifications.
430 # notifications.
431 for key, value in iteritems(kw):
431 for key, value in iteritems(kw):
432 setattr(self, key, value)
432 setattr(self, key, value)
433
433
434 def _notify_trait(self, name, old_value, new_value):
434 def _notify_trait(self, name, old_value, new_value):
435
435
436 # First dynamic ones
436 # First dynamic ones
437 callables = []
437 callables = []
438 callables.extend(self._trait_notifiers.get(name,[]))
438 callables.extend(self._trait_notifiers.get(name,[]))
439 callables.extend(self._trait_notifiers.get('anytrait',[]))
439 callables.extend(self._trait_notifiers.get('anytrait',[]))
440
440
441 # Now static ones
441 # Now static ones
442 try:
442 try:
443 cb = getattr(self, '_%s_changed' % name)
443 cb = getattr(self, '_%s_changed' % name)
444 except:
444 except:
445 pass
445 pass
446 else:
446 else:
447 callables.append(cb)
447 callables.append(cb)
448
448
449 # Call them all now
449 # Call them all now
450 for c in callables:
450 for c in callables:
451 # Traits catches and logs errors here. I allow them to raise
451 # Traits catches and logs errors here. I allow them to raise
452 if callable(c):
452 if callable(c):
453 argspec = inspect.getargspec(c)
453 argspec = inspect.getargspec(c)
454 nargs = len(argspec[0])
454 nargs = len(argspec[0])
455 # Bound methods have an additional 'self' argument
455 # Bound methods have an additional 'self' argument
456 # I don't know how to treat unbound methods, but they
456 # I don't know how to treat unbound methods, but they
457 # can't really be used for callbacks.
457 # can't really be used for callbacks.
458 if isinstance(c, types.MethodType):
458 if isinstance(c, types.MethodType):
459 offset = -1
459 offset = -1
460 else:
460 else:
461 offset = 0
461 offset = 0
462 if nargs + offset == 0:
462 if nargs + offset == 0:
463 c()
463 c()
464 elif nargs + offset == 1:
464 elif nargs + offset == 1:
465 c(name)
465 c(name)
466 elif nargs + offset == 2:
466 elif nargs + offset == 2:
467 c(name, new_value)
467 c(name, new_value)
468 elif nargs + offset == 3:
468 elif nargs + offset == 3:
469 c(name, old_value, new_value)
469 c(name, old_value, new_value)
470 else:
470 else:
471 raise TraitError('a trait changed callback '
471 raise TraitError('a trait changed callback '
472 'must have 0-3 arguments.')
472 'must have 0-3 arguments.')
473 else:
473 else:
474 raise TraitError('a trait changed callback '
474 raise TraitError('a trait changed callback '
475 'must be callable.')
475 'must be callable.')
476
476
477
477
478 def _add_notifiers(self, handler, name):
478 def _add_notifiers(self, handler, name):
479 if name not in self._trait_notifiers:
479 if name not in self._trait_notifiers:
480 nlist = []
480 nlist = []
481 self._trait_notifiers[name] = nlist
481 self._trait_notifiers[name] = nlist
482 else:
482 else:
483 nlist = self._trait_notifiers[name]
483 nlist = self._trait_notifiers[name]
484 if handler not in nlist:
484 if handler not in nlist:
485 nlist.append(handler)
485 nlist.append(handler)
486
486
487 def _remove_notifiers(self, handler, name):
487 def _remove_notifiers(self, handler, name):
488 if name in self._trait_notifiers:
488 if name in self._trait_notifiers:
489 nlist = self._trait_notifiers[name]
489 nlist = self._trait_notifiers[name]
490 try:
490 try:
491 index = nlist.index(handler)
491 index = nlist.index(handler)
492 except ValueError:
492 except ValueError:
493 pass
493 pass
494 else:
494 else:
495 del nlist[index]
495 del nlist[index]
496
496
497 def on_trait_change(self, handler, name=None, remove=False):
497 def on_trait_change(self, handler, name=None, remove=False):
498 """Setup a handler to be called when a trait changes.
498 """Setup a handler to be called when a trait changes.
499
499
500 This is used to setup dynamic notifications of trait changes.
500 This is used to setup dynamic notifications of trait changes.
501
501
502 Static handlers can be created by creating methods on a HasTraits
502 Static handlers can be created by creating methods on a HasTraits
503 subclass with the naming convention '_[traitname]_changed'. Thus,
503 subclass with the naming convention '_[traitname]_changed'. Thus,
504 to create static handler for the trait 'a', create the method
504 to create static handler for the trait 'a', create the method
505 _a_changed(self, name, old, new) (fewer arguments can be used, see
505 _a_changed(self, name, old, new) (fewer arguments can be used, see
506 below).
506 below).
507
507
508 Parameters
508 Parameters
509 ----------
509 ----------
510 handler : callable
510 handler : callable
511 A callable that is called when a trait changes. Its
511 A callable that is called when a trait changes. Its
512 signature can be handler(), handler(name), handler(name, new)
512 signature can be handler(), handler(name), handler(name, new)
513 or handler(name, old, new).
513 or handler(name, old, new).
514 name : list, str, None
514 name : list, str, None
515 If None, the handler will apply to all traits. If a list
515 If None, the handler will apply to all traits. If a list
516 of str, handler will apply to all names in the list. If a
516 of str, handler will apply to all names in the list. If a
517 str, the handler will apply just to that name.
517 str, the handler will apply just to that name.
518 remove : bool
518 remove : bool
519 If False (the default), then install the handler. If True
519 If False (the default), then install the handler. If True
520 then unintall it.
520 then unintall it.
521 """
521 """
522 if remove:
522 if remove:
523 names = parse_notifier_name(name)
523 names = parse_notifier_name(name)
524 for n in names:
524 for n in names:
525 self._remove_notifiers(handler, n)
525 self._remove_notifiers(handler, n)
526 else:
526 else:
527 names = parse_notifier_name(name)
527 names = parse_notifier_name(name)
528 for n in names:
528 for n in names:
529 self._add_notifiers(handler, n)
529 self._add_notifiers(handler, n)
530
530
531 @classmethod
531 @classmethod
532 def class_trait_names(cls, **metadata):
532 def class_trait_names(cls, **metadata):
533 """Get a list of all the names of this classes traits.
533 """Get a list of all the names of this classes traits.
534
534
535 This method is just like the :meth:`trait_names` method, but is unbound.
535 This method is just like the :meth:`trait_names` method, but is unbound.
536 """
536 """
537 return cls.class_traits(**metadata).keys()
537 return cls.class_traits(**metadata).keys()
538
538
539 @classmethod
539 @classmethod
540 def class_traits(cls, **metadata):
540 def class_traits(cls, **metadata):
541 """Get a list of all the traits of this class.
541 """Get a list of all the traits of this class.
542
542
543 This method is just like the :meth:`traits` method, but is unbound.
543 This method is just like the :meth:`traits` method, but is unbound.
544
544
545 The TraitTypes returned don't know anything about the values
545 The TraitTypes returned don't know anything about the values
546 that the various HasTrait's instances are holding.
546 that the various HasTrait's instances are holding.
547
547
548 This follows the same algorithm as traits does and does not allow
548 This follows the same algorithm as traits does and does not allow
549 for any simple way of specifying merely that a metadata name
549 for any simple way of specifying merely that a metadata name
550 exists, but has any value. This is because get_metadata returns
550 exists, but has any value. This is because get_metadata returns
551 None if a metadata key doesn't exist.
551 None if a metadata key doesn't exist.
552 """
552 """
553 traits = dict([memb for memb in getmembers(cls) if \
553 traits = dict([memb for memb in getmembers(cls) if \
554 isinstance(memb[1], TraitType)])
554 isinstance(memb[1], TraitType)])
555
555
556 if len(metadata) == 0:
556 if len(metadata) == 0:
557 return traits
557 return traits
558
558
559 for meta_name, meta_eval in metadata.items():
559 for meta_name, meta_eval in metadata.items():
560 if type(meta_eval) is not FunctionType:
560 if type(meta_eval) is not FunctionType:
561 metadata[meta_name] = _SimpleTest(meta_eval)
561 metadata[meta_name] = _SimpleTest(meta_eval)
562
562
563 result = {}
563 result = {}
564 for name, trait in traits.items():
564 for name, trait in traits.items():
565 for meta_name, meta_eval in metadata.items():
565 for meta_name, meta_eval in metadata.items():
566 if not meta_eval(trait.get_metadata(meta_name)):
566 if not meta_eval(trait.get_metadata(meta_name)):
567 break
567 break
568 else:
568 else:
569 result[name] = trait
569 result[name] = trait
570
570
571 return result
571 return result
572
572
573 def trait_names(self, **metadata):
573 def trait_names(self, **metadata):
574 """Get a list of all the names of this classes traits."""
574 """Get a list of all the names of this classes traits."""
575 return self.traits(**metadata).keys()
575 return self.traits(**metadata).keys()
576
576
577 def traits(self, **metadata):
577 def traits(self, **metadata):
578 """Get a list of all the traits of this class.
578 """Get a list of all the traits of this class.
579
579
580 The TraitTypes returned don't know anything about the values
580 The TraitTypes returned don't know anything about the values
581 that the various HasTrait's instances are holding.
581 that the various HasTrait's instances are holding.
582
582
583 This follows the same algorithm as traits does and does not allow
583 This follows the same algorithm as traits does and does not allow
584 for any simple way of specifying merely that a metadata name
584 for any simple way of specifying merely that a metadata name
585 exists, but has any value. This is because get_metadata returns
585 exists, but has any value. This is because get_metadata returns
586 None if a metadata key doesn't exist.
586 None if a metadata key doesn't exist.
587 """
587 """
588 traits = dict([memb for memb in getmembers(self.__class__) if \
588 traits = dict([memb for memb in getmembers(self.__class__) if \
589 isinstance(memb[1], TraitType)])
589 isinstance(memb[1], TraitType)])
590
590
591 if len(metadata) == 0:
591 if len(metadata) == 0:
592 return traits
592 return traits
593
593
594 for meta_name, meta_eval in metadata.items():
594 for meta_name, meta_eval in metadata.items():
595 if type(meta_eval) is not FunctionType:
595 if type(meta_eval) is not FunctionType:
596 metadata[meta_name] = _SimpleTest(meta_eval)
596 metadata[meta_name] = _SimpleTest(meta_eval)
597
597
598 result = {}
598 result = {}
599 for name, trait in traits.items():
599 for name, trait in traits.items():
600 for meta_name, meta_eval in metadata.items():
600 for meta_name, meta_eval in metadata.items():
601 if not meta_eval(trait.get_metadata(meta_name)):
601 if not meta_eval(trait.get_metadata(meta_name)):
602 break
602 break
603 else:
603 else:
604 result[name] = trait
604 result[name] = trait
605
605
606 return result
606 return result
607
607
608 def trait_metadata(self, traitname, key):
608 def trait_metadata(self, traitname, key):
609 """Get metadata values for trait by key."""
609 """Get metadata values for trait by key."""
610 try:
610 try:
611 trait = getattr(self.__class__, traitname)
611 trait = getattr(self.__class__, traitname)
612 except AttributeError:
612 except AttributeError:
613 raise TraitError("Class %s does not have a trait named %s" %
613 raise TraitError("Class %s does not have a trait named %s" %
614 (self.__class__.__name__, traitname))
614 (self.__class__.__name__, traitname))
615 else:
615 else:
616 return trait.get_metadata(key)
616 return trait.get_metadata(key)
617
617
618 #-----------------------------------------------------------------------------
618 #-----------------------------------------------------------------------------
619 # Actual TraitTypes implementations/subclasses
619 # Actual TraitTypes implementations/subclasses
620 #-----------------------------------------------------------------------------
620 #-----------------------------------------------------------------------------
621
621
622 #-----------------------------------------------------------------------------
622 #-----------------------------------------------------------------------------
623 # TraitTypes subclasses for handling classes and instances of classes
623 # TraitTypes subclasses for handling classes and instances of classes
624 #-----------------------------------------------------------------------------
624 #-----------------------------------------------------------------------------
625
625
626
626
627 class ClassBasedTraitType(TraitType):
627 class ClassBasedTraitType(TraitType):
628 """A trait with error reporting for Type, Instance and This."""
628 """A trait with error reporting for Type, Instance and This."""
629
629
630 def error(self, obj, value):
630 def error(self, obj, value):
631 kind = type(value)
631 kind = type(value)
632 if (not py3compat.PY3) and kind is InstanceType:
632 if (not py3compat.PY3) and kind is InstanceType:
633 msg = 'class %s' % value.__class__.__name__
633 msg = 'class %s' % value.__class__.__name__
634 else:
634 else:
635 msg = '%s (i.e. %s)' % ( str( kind )[1:-1], repr( value ) )
635 msg = '%s (i.e. %s)' % ( str( kind )[1:-1], repr( value ) )
636
636
637 if obj is not None:
637 if obj is not None:
638 e = "The '%s' trait of %s instance must be %s, but a value of %s was specified." \
638 e = "The '%s' trait of %s instance must be %s, but a value of %s was specified." \
639 % (self.name, class_of(obj),
639 % (self.name, class_of(obj),
640 self.info(), msg)
640 self.info(), msg)
641 else:
641 else:
642 e = "The '%s' trait must be %s, but a value of %r was specified." \
642 e = "The '%s' trait must be %s, but a value of %r was specified." \
643 % (self.name, self.info(), msg)
643 % (self.name, self.info(), msg)
644
644
645 raise TraitError(e)
645 raise TraitError(e)
646
646
647
647
648 class Type(ClassBasedTraitType):
648 class Type(ClassBasedTraitType):
649 """A trait whose value must be a subclass of a specified class."""
649 """A trait whose value must be a subclass of a specified class."""
650
650
651 def __init__ (self, default_value=None, klass=None, allow_none=True, **metadata ):
651 def __init__ (self, default_value=None, klass=None, allow_none=True, **metadata ):
652 """Construct a Type trait
652 """Construct a Type trait
653
653
654 A Type trait specifies that its values must be subclasses of
654 A Type trait specifies that its values must be subclasses of
655 a particular class.
655 a particular class.
656
656
657 If only ``default_value`` is given, it is used for the ``klass`` as
657 If only ``default_value`` is given, it is used for the ``klass`` as
658 well.
658 well.
659
659
660 Parameters
660 Parameters
661 ----------
661 ----------
662 default_value : class, str or None
662 default_value : class, str or None
663 The default value must be a subclass of klass. If an str,
663 The default value must be a subclass of klass. If an str,
664 the str must be a fully specified class name, like 'foo.bar.Bah'.
664 the str must be a fully specified class name, like 'foo.bar.Bah'.
665 The string is resolved into real class, when the parent
665 The string is resolved into real class, when the parent
666 :class:`HasTraits` class is instantiated.
666 :class:`HasTraits` class is instantiated.
667 klass : class, str, None
667 klass : class, str, None
668 Values of this trait must be a subclass of klass. The klass
668 Values of this trait must be a subclass of klass. The klass
669 may be specified in a string like: 'foo.bar.MyClass'.
669 may be specified in a string like: 'foo.bar.MyClass'.
670 The string is resolved into real class, when the parent
670 The string is resolved into real class, when the parent
671 :class:`HasTraits` class is instantiated.
671 :class:`HasTraits` class is instantiated.
672 allow_none : boolean
672 allow_none : boolean
673 Indicates whether None is allowed as an assignable value. Even if
673 Indicates whether None is allowed as an assignable value. Even if
674 ``False``, the default value may be ``None``.
674 ``False``, the default value may be ``None``.
675 """
675 """
676 if default_value is None:
676 if default_value is None:
677 if klass is None:
677 if klass is None:
678 klass = object
678 klass = object
679 elif klass is None:
679 elif klass is None:
680 klass = default_value
680 klass = default_value
681
681
682 if not (inspect.isclass(klass) or isinstance(klass, py3compat.string_types)):
682 if not (inspect.isclass(klass) or isinstance(klass, py3compat.string_types)):
683 raise TraitError("A Type trait must specify a class.")
683 raise TraitError("A Type trait must specify a class.")
684
684
685 self.klass = klass
685 self.klass = klass
686 self._allow_none = allow_none
686 self._allow_none = allow_none
687
687
688 super(Type, self).__init__(default_value, **metadata)
688 super(Type, self).__init__(default_value, **metadata)
689
689
690 def validate(self, obj, value):
690 def validate(self, obj, value):
691 """Validates that the value is a valid object instance."""
691 """Validates that the value is a valid object instance."""
692 try:
692 try:
693 if issubclass(value, self.klass):
693 if issubclass(value, self.klass):
694 return value
694 return value
695 except:
695 except:
696 if (value is None) and (self._allow_none):
696 if (value is None) and (self._allow_none):
697 return value
697 return value
698
698
699 self.error(obj, value)
699 self.error(obj, value)
700
700
701 def info(self):
701 def info(self):
702 """ Returns a description of the trait."""
702 """ Returns a description of the trait."""
703 if isinstance(self.klass, py3compat.string_types):
703 if isinstance(self.klass, py3compat.string_types):
704 klass = self.klass
704 klass = self.klass
705 else:
705 else:
706 klass = self.klass.__name__
706 klass = self.klass.__name__
707 result = 'a subclass of ' + klass
707 result = 'a subclass of ' + klass
708 if self._allow_none:
708 if self._allow_none:
709 return result + ' or None'
709 return result + ' or None'
710 return result
710 return result
711
711
712 def instance_init(self, obj):
712 def instance_init(self, obj):
713 self._resolve_classes()
713 self._resolve_classes()
714 super(Type, self).instance_init(obj)
714 super(Type, self).instance_init(obj)
715
715
716 def _resolve_classes(self):
716 def _resolve_classes(self):
717 if isinstance(self.klass, py3compat.string_types):
717 if isinstance(self.klass, py3compat.string_types):
718 self.klass = import_item(self.klass)
718 self.klass = import_item(self.klass)
719 if isinstance(self.default_value, py3compat.string_types):
719 if isinstance(self.default_value, py3compat.string_types):
720 self.default_value = import_item(self.default_value)
720 self.default_value = import_item(self.default_value)
721
721
722 def get_default_value(self):
722 def get_default_value(self):
723 return self.default_value
723 return self.default_value
724
724
725
725
726 class DefaultValueGenerator(object):
726 class DefaultValueGenerator(object):
727 """A class for generating new default value instances."""
727 """A class for generating new default value instances."""
728
728
729 def __init__(self, *args, **kw):
729 def __init__(self, *args, **kw):
730 self.args = args
730 self.args = args
731 self.kw = kw
731 self.kw = kw
732
732
733 def generate(self, klass):
733 def generate(self, klass):
734 return klass(*self.args, **self.kw)
734 return klass(*self.args, **self.kw)
735
735
736
736
737 class Instance(ClassBasedTraitType):
737 class Instance(ClassBasedTraitType):
738 """A trait whose value must be an instance of a specified class.
738 """A trait whose value must be an instance of a specified class.
739
739
740 The value can also be an instance of a subclass of the specified class.
740 The value can also be an instance of a subclass of the specified class.
741 """
741 """
742
742
743 def __init__(self, klass=None, args=None, kw=None,
743 def __init__(self, klass=None, args=None, kw=None,
744 allow_none=True, **metadata ):
744 allow_none=True, **metadata ):
745 """Construct an Instance trait.
745 """Construct an Instance trait.
746
746
747 This trait allows values that are instances of a particular
747 This trait allows values that are instances of a particular
748 class or its sublclasses. Our implementation is quite different
748 class or its sublclasses. Our implementation is quite different
749 from that of enthough.traits as we don't allow instances to be used
749 from that of enthough.traits as we don't allow instances to be used
750 for klass and we handle the ``args`` and ``kw`` arguments differently.
750 for klass and we handle the ``args`` and ``kw`` arguments differently.
751
751
752 Parameters
752 Parameters
753 ----------
753 ----------
754 klass : class, str
754 klass : class, str
755 The class that forms the basis for the trait. Class names
755 The class that forms the basis for the trait. Class names
756 can also be specified as strings, like 'foo.bar.Bar'.
756 can also be specified as strings, like 'foo.bar.Bar'.
757 args : tuple
757 args : tuple
758 Positional arguments for generating the default value.
758 Positional arguments for generating the default value.
759 kw : dict
759 kw : dict
760 Keyword arguments for generating the default value.
760 Keyword arguments for generating the default value.
761 allow_none : bool
761 allow_none : bool
762 Indicates whether None is allowed as a value.
762 Indicates whether None is allowed as a value.
763
763
764 Notes
764 Notes
765 -----
765 -----
766 If both ``args`` and ``kw`` are None, then the default value is None.
766 If both ``args`` and ``kw`` are None, then the default value is None.
767 If ``args`` is a tuple and ``kw`` is a dict, then the default is
767 If ``args`` is a tuple and ``kw`` is a dict, then the default is
768 created as ``klass(*args, **kw)``. If either ``args`` or ``kw`` is
768 created as ``klass(*args, **kw)``. If either ``args`` or ``kw`` is
769 not (but not both), None is replace by ``()`` or ``{}``.
769 not (but not both), None is replace by ``()`` or ``{}``.
770 """
770 """
771
771
772 self._allow_none = allow_none
772 self._allow_none = allow_none
773
773
774 if (klass is None) or (not (inspect.isclass(klass) or isinstance(klass, py3compat.string_types))):
774 if (klass is None) or (not (inspect.isclass(klass) or isinstance(klass, py3compat.string_types))):
775 raise TraitError('The klass argument must be a class'
775 raise TraitError('The klass argument must be a class'
776 ' you gave: %r' % klass)
776 ' you gave: %r' % klass)
777 self.klass = klass
777 self.klass = klass
778
778
779 # self.klass is a class, so handle default_value
779 # self.klass is a class, so handle default_value
780 if args is None and kw is None:
780 if args is None and kw is None:
781 default_value = None
781 default_value = None
782 else:
782 else:
783 if args is None:
783 if args is None:
784 # kw is not None
784 # kw is not None
785 args = ()
785 args = ()
786 elif kw is None:
786 elif kw is None:
787 # args is not None
787 # args is not None
788 kw = {}
788 kw = {}
789
789
790 if not isinstance(kw, dict):
790 if not isinstance(kw, dict):
791 raise TraitError("The 'kw' argument must be a dict or None.")
791 raise TraitError("The 'kw' argument must be a dict or None.")
792 if not isinstance(args, tuple):
792 if not isinstance(args, tuple):
793 raise TraitError("The 'args' argument must be a tuple or None.")
793 raise TraitError("The 'args' argument must be a tuple or None.")
794
794
795 default_value = DefaultValueGenerator(*args, **kw)
795 default_value = DefaultValueGenerator(*args, **kw)
796
796
797 super(Instance, self).__init__(default_value, **metadata)
797 super(Instance, self).__init__(default_value, **metadata)
798
798
799 def validate(self, obj, value):
799 def validate(self, obj, value):
800 if value is None:
800 if value is None:
801 if self._allow_none:
801 if self._allow_none:
802 return value
802 return value
803 self.error(obj, value)
803 self.error(obj, value)
804
804 try:
805 if isinstance(value, self.klass):
805 if isinstance(value, self.klass):
806 return value
806 return value
807 else:
807 else:
808 self.error(obj, value)
808 self.error(obj, value)
809 except TypeError as e:
810 print self.klass, type(self.klass)
811 raise TypeError("validate, %s, %s"%(self.klass, type(self.klass)))
809
812
810 def info(self):
813 def info(self):
811 if isinstance(self.klass, py3compat.string_types):
814 if isinstance(self.klass, py3compat.string_types):
812 klass = self.klass
815 klass = self.klass
813 else:
816 else:
814 klass = self.klass.__name__
817 klass = self.klass.__name__
815 result = class_of(klass)
818 result = class_of(klass)
816 if self._allow_none:
819 if self._allow_none:
817 return result + ' or None'
820 return result + ' or None'
818
821
819 return result
822 return result
820
823
821 def instance_init(self, obj):
824 def instance_init(self, obj):
822 self._resolve_classes()
825 self._resolve_classes()
823 super(Instance, self).instance_init(obj)
826 super(Instance, self).instance_init(obj)
824
827
825 def _resolve_classes(self):
828 def _resolve_classes(self):
826 if isinstance(self.klass, py3compat.string_types):
829 if isinstance(self.klass, py3compat.string_types):
827 self.klass = import_item(self.klass)
830 self.klass = import_item(self.klass)
828
831
829 def get_default_value(self):
832 def get_default_value(self):
830 """Instantiate a default value instance.
833 """Instantiate a default value instance.
831
834
832 This is called when the containing HasTraits classes'
835 This is called when the containing HasTraits classes'
833 :meth:`__new__` method is called to ensure that a unique instance
836 :meth:`__new__` method is called to ensure that a unique instance
834 is created for each HasTraits instance.
837 is created for each HasTraits instance.
835 """
838 """
836 dv = self.default_value
839 dv = self.default_value
837 if isinstance(dv, DefaultValueGenerator):
840 if isinstance(dv, DefaultValueGenerator):
838 return dv.generate(self.klass)
841 return dv.generate(self.klass)
839 else:
842 else:
840 return dv
843 return dv
841
844
842
845
843 class This(ClassBasedTraitType):
846 class This(ClassBasedTraitType):
844 """A trait for instances of the class containing this trait.
847 """A trait for instances of the class containing this trait.
845
848
846 Because how how and when class bodies are executed, the ``This``
849 Because how how and when class bodies are executed, the ``This``
847 trait can only have a default value of None. This, and because we
850 trait can only have a default value of None. This, and because we
848 always validate default values, ``allow_none`` is *always* true.
851 always validate default values, ``allow_none`` is *always* true.
849 """
852 """
850
853
851 info_text = 'an instance of the same type as the receiver or None'
854 info_text = 'an instance of the same type as the receiver or None'
852
855
853 def __init__(self, **metadata):
856 def __init__(self, **metadata):
854 super(This, self).__init__(None, **metadata)
857 super(This, self).__init__(None, **metadata)
855
858
856 def validate(self, obj, value):
859 def validate(self, obj, value):
857 # What if value is a superclass of obj.__class__? This is
860 # What if value is a superclass of obj.__class__? This is
858 # complicated if it was the superclass that defined the This
861 # complicated if it was the superclass that defined the This
859 # trait.
862 # trait.
860 if isinstance(value, self.this_class) or (value is None):
863 if isinstance(value, self.this_class) or (value is None):
861 return value
864 return value
862 else:
865 else:
863 self.error(obj, value)
866 self.error(obj, value)
864
867
865
868
866 #-----------------------------------------------------------------------------
869 #-----------------------------------------------------------------------------
867 # Basic TraitTypes implementations/subclasses
870 # Basic TraitTypes implementations/subclasses
868 #-----------------------------------------------------------------------------
871 #-----------------------------------------------------------------------------
869
872
870
873
871 class Any(TraitType):
874 class Any(TraitType):
872 default_value = None
875 default_value = None
873 info_text = 'any value'
876 info_text = 'any value'
874
877
875
878
876 class Int(TraitType):
879 class Int(TraitType):
877 """An int trait."""
880 """An int trait."""
878
881
879 default_value = 0
882 default_value = 0
880 info_text = 'an int'
883 info_text = 'an int'
881
884
882 def validate(self, obj, value):
885 def validate(self, obj, value):
883 if isinstance(value, int):
886 if isinstance(value, int):
884 return value
887 return value
885 self.error(obj, value)
888 self.error(obj, value)
886
889
887 class CInt(Int):
890 class CInt(Int):
888 """A casting version of the int trait."""
891 """A casting version of the int trait."""
889
892
890 def validate(self, obj, value):
893 def validate(self, obj, value):
891 try:
894 try:
892 return int(value)
895 return int(value)
893 except:
896 except:
894 self.error(obj, value)
897 self.error(obj, value)
895
898
896 if py3compat.PY3:
899 if py3compat.PY3:
897 Long, CLong = Int, CInt
900 Long, CLong = Int, CInt
898 Integer = Int
901 Integer = Int
899 else:
902 else:
900 class Long(TraitType):
903 class Long(TraitType):
901 """A long integer trait."""
904 """A long integer trait."""
902
905
903 default_value = 0
906 default_value = 0
904 info_text = 'a long'
907 info_text = 'a long'
905
908
906 def validate(self, obj, value):
909 def validate(self, obj, value):
907 if isinstance(value, long):
910 if isinstance(value, long):
908 return value
911 return value
909 if isinstance(value, int):
912 if isinstance(value, int):
910 return long(value)
913 return long(value)
911 self.error(obj, value)
914 self.error(obj, value)
912
915
913
916
914 class CLong(Long):
917 class CLong(Long):
915 """A casting version of the long integer trait."""
918 """A casting version of the long integer trait."""
916
919
917 def validate(self, obj, value):
920 def validate(self, obj, value):
918 try:
921 try:
919 return long(value)
922 return long(value)
920 except:
923 except:
921 self.error(obj, value)
924 self.error(obj, value)
922
925
923 class Integer(TraitType):
926 class Integer(TraitType):
924 """An integer trait.
927 """An integer trait.
925
928
926 Longs that are unnecessary (<= sys.maxint) are cast to ints."""
929 Longs that are unnecessary (<= sys.maxint) are cast to ints."""
927
930
928 default_value = 0
931 default_value = 0
929 info_text = 'an integer'
932 info_text = 'an integer'
930
933
931 def validate(self, obj, value):
934 def validate(self, obj, value):
932 if isinstance(value, int):
935 if isinstance(value, int):
933 return value
936 return value
934 if isinstance(value, long):
937 if isinstance(value, long):
935 # downcast longs that fit in int:
938 # downcast longs that fit in int:
936 # note that int(n > sys.maxint) returns a long, so
939 # note that int(n > sys.maxint) returns a long, so
937 # we don't need a condition on this cast
940 # we don't need a condition on this cast
938 return int(value)
941 return int(value)
939 if sys.platform == "cli":
942 if sys.platform == "cli":
940 from System import Int64
943 from System import Int64
941 if isinstance(value, Int64):
944 if isinstance(value, Int64):
942 return int(value)
945 return int(value)
943 self.error(obj, value)
946 self.error(obj, value)
944
947
945
948
946 class Float(TraitType):
949 class Float(TraitType):
947 """A float trait."""
950 """A float trait."""
948
951
949 default_value = 0.0
952 default_value = 0.0
950 info_text = 'a float'
953 info_text = 'a float'
951
954
952 def validate(self, obj, value):
955 def validate(self, obj, value):
953 if isinstance(value, float):
956 if isinstance(value, float):
954 return value
957 return value
955 if isinstance(value, int):
958 if isinstance(value, int):
956 return float(value)
959 return float(value)
957 self.error(obj, value)
960 self.error(obj, value)
958
961
959
962
960 class CFloat(Float):
963 class CFloat(Float):
961 """A casting version of the float trait."""
964 """A casting version of the float trait."""
962
965
963 def validate(self, obj, value):
966 def validate(self, obj, value):
964 try:
967 try:
965 return float(value)
968 return float(value)
966 except:
969 except:
967 self.error(obj, value)
970 self.error(obj, value)
968
971
969 class Complex(TraitType):
972 class Complex(TraitType):
970 """A trait for complex numbers."""
973 """A trait for complex numbers."""
971
974
972 default_value = 0.0 + 0.0j
975 default_value = 0.0 + 0.0j
973 info_text = 'a complex number'
976 info_text = 'a complex number'
974
977
975 def validate(self, obj, value):
978 def validate(self, obj, value):
976 if isinstance(value, complex):
979 if isinstance(value, complex):
977 return value
980 return value
978 if isinstance(value, (float, int)):
981 if isinstance(value, (float, int)):
979 return complex(value)
982 return complex(value)
980 self.error(obj, value)
983 self.error(obj, value)
981
984
982
985
983 class CComplex(Complex):
986 class CComplex(Complex):
984 """A casting version of the complex number trait."""
987 """A casting version of the complex number trait."""
985
988
986 def validate (self, obj, value):
989 def validate (self, obj, value):
987 try:
990 try:
988 return complex(value)
991 return complex(value)
989 except:
992 except:
990 self.error(obj, value)
993 self.error(obj, value)
991
994
992 # We should always be explicit about whether we're using bytes or unicode, both
995 # We should always be explicit about whether we're using bytes or unicode, both
993 # for Python 3 conversion and for reliable unicode behaviour on Python 2. So
996 # for Python 3 conversion and for reliable unicode behaviour on Python 2. So
994 # we don't have a Str type.
997 # we don't have a Str type.
995 class Bytes(TraitType):
998 class Bytes(TraitType):
996 """A trait for byte strings."""
999 """A trait for byte strings."""
997
1000
998 default_value = b''
1001 default_value = b''
999 info_text = 'a bytes object'
1002 info_text = 'a bytes object'
1000
1003
1001 def validate(self, obj, value):
1004 def validate(self, obj, value):
1002 if isinstance(value, bytes):
1005 if isinstance(value, bytes):
1003 return value
1006 return value
1004 self.error(obj, value)
1007 self.error(obj, value)
1005
1008
1006
1009
1007 class CBytes(Bytes):
1010 class CBytes(Bytes):
1008 """A casting version of the byte string trait."""
1011 """A casting version of the byte string trait."""
1009
1012
1010 def validate(self, obj, value):
1013 def validate(self, obj, value):
1011 try:
1014 try:
1012 return bytes(value)
1015 return bytes(value)
1013 except:
1016 except:
1014 self.error(obj, value)
1017 self.error(obj, value)
1015
1018
1016
1019
1017 class Unicode(TraitType):
1020 class Unicode(TraitType):
1018 """A trait for unicode strings."""
1021 """A trait for unicode strings."""
1019
1022
1020 default_value = u''
1023 default_value = u''
1021 info_text = 'a unicode string'
1024 info_text = 'a unicode string'
1022
1025
1023 def validate(self, obj, value):
1026 def validate(self, obj, value):
1024 if isinstance(value, py3compat.unicode_type):
1027 if isinstance(value, py3compat.unicode_type):
1025 return value
1028 return value
1026 if isinstance(value, bytes):
1029 if isinstance(value, bytes):
1027 try:
1030 try:
1028 return value.decode('ascii', 'strict')
1031 return value.decode('ascii', 'strict')
1029 except UnicodeDecodeError:
1032 except UnicodeDecodeError:
1030 msg = "Could not decode {!r} for unicode trait '{}' of {} instance."
1033 msg = "Could not decode {!r} for unicode trait '{}' of {} instance."
1031 raise TraitError(msg.format(value, self.name, class_of(obj)))
1034 raise TraitError(msg.format(value, self.name, class_of(obj)))
1032 self.error(obj, value)
1035 self.error(obj, value)
1033
1036
1034
1037
1035 class CUnicode(Unicode):
1038 class CUnicode(Unicode):
1036 """A casting version of the unicode trait."""
1039 """A casting version of the unicode trait."""
1037
1040
1038 def validate(self, obj, value):
1041 def validate(self, obj, value):
1039 try:
1042 try:
1040 return py3compat.unicode_type(value)
1043 return py3compat.unicode_type(value)
1041 except:
1044 except:
1042 self.error(obj, value)
1045 self.error(obj, value)
1043
1046
1044
1047
1045 class ObjectName(TraitType):
1048 class ObjectName(TraitType):
1046 """A string holding a valid object name in this version of Python.
1049 """A string holding a valid object name in this version of Python.
1047
1050
1048 This does not check that the name exists in any scope."""
1051 This does not check that the name exists in any scope."""
1049 info_text = "a valid object identifier in Python"
1052 info_text = "a valid object identifier in Python"
1050
1053
1051 if py3compat.PY3:
1054 if py3compat.PY3:
1052 # Python 3:
1055 # Python 3:
1053 coerce_str = staticmethod(lambda _,s: s)
1056 coerce_str = staticmethod(lambda _,s: s)
1054
1057
1055 else:
1058 else:
1056 # Python 2:
1059 # Python 2:
1057 def coerce_str(self, obj, value):
1060 def coerce_str(self, obj, value):
1058 "In Python 2, coerce ascii-only unicode to str"
1061 "In Python 2, coerce ascii-only unicode to str"
1059 if isinstance(value, unicode):
1062 if isinstance(value, unicode):
1060 try:
1063 try:
1061 return str(value)
1064 return str(value)
1062 except UnicodeEncodeError:
1065 except UnicodeEncodeError:
1063 self.error(obj, value)
1066 self.error(obj, value)
1064 return value
1067 return value
1065
1068
1066 def validate(self, obj, value):
1069 def validate(self, obj, value):
1067 value = self.coerce_str(obj, value)
1070 value = self.coerce_str(obj, value)
1068
1071
1069 if isinstance(value, str) and py3compat.isidentifier(value):
1072 if isinstance(value, str) and py3compat.isidentifier(value):
1070 return value
1073 return value
1071 self.error(obj, value)
1074 self.error(obj, value)
1072
1075
1073 class DottedObjectName(ObjectName):
1076 class DottedObjectName(ObjectName):
1074 """A string holding a valid dotted object name in Python, such as A.b3._c"""
1077 """A string holding a valid dotted object name in Python, such as A.b3._c"""
1075 def validate(self, obj, value):
1078 def validate(self, obj, value):
1076 value = self.coerce_str(obj, value)
1079 value = self.coerce_str(obj, value)
1077
1080
1078 if isinstance(value, str) and py3compat.isidentifier(value, dotted=True):
1081 if isinstance(value, str) and py3compat.isidentifier(value, dotted=True):
1079 return value
1082 return value
1080 self.error(obj, value)
1083 self.error(obj, value)
1081
1084
1082
1085
1083 class Bool(TraitType):
1086 class Bool(TraitType):
1084 """A boolean (True, False) trait."""
1087 """A boolean (True, False) trait."""
1085
1088
1086 default_value = False
1089 default_value = False
1087 info_text = 'a boolean'
1090 info_text = 'a boolean'
1088
1091
1089 def validate(self, obj, value):
1092 def validate(self, obj, value):
1090 if isinstance(value, bool):
1093 if isinstance(value, bool):
1091 return value
1094 return value
1092 self.error(obj, value)
1095 self.error(obj, value)
1093
1096
1094
1097
1095 class CBool(Bool):
1098 class CBool(Bool):
1096 """A casting version of the boolean trait."""
1099 """A casting version of the boolean trait."""
1097
1100
1098 def validate(self, obj, value):
1101 def validate(self, obj, value):
1099 try:
1102 try:
1100 return bool(value)
1103 return bool(value)
1101 except:
1104 except:
1102 self.error(obj, value)
1105 self.error(obj, value)
1103
1106
1104
1107
1105 class Enum(TraitType):
1108 class Enum(TraitType):
1106 """An enum that whose value must be in a given sequence."""
1109 """An enum that whose value must be in a given sequence."""
1107
1110
1108 def __init__(self, values, default_value=None, allow_none=True, **metadata):
1111 def __init__(self, values, default_value=None, allow_none=True, **metadata):
1109 self.values = values
1112 self.values = values
1110 self._allow_none = allow_none
1113 self._allow_none = allow_none
1111 super(Enum, self).__init__(default_value, **metadata)
1114 super(Enum, self).__init__(default_value, **metadata)
1112
1115
1113 def validate(self, obj, value):
1116 def validate(self, obj, value):
1114 if value is None:
1117 if value is None:
1115 if self._allow_none:
1118 if self._allow_none:
1116 return value
1119 return value
1117
1120
1118 if value in self.values:
1121 if value in self.values:
1119 return value
1122 return value
1120 self.error(obj, value)
1123 self.error(obj, value)
1121
1124
1122 def info(self):
1125 def info(self):
1123 """ Returns a description of the trait."""
1126 """ Returns a description of the trait."""
1124 result = 'any of ' + repr(self.values)
1127 result = 'any of ' + repr(self.values)
1125 if self._allow_none:
1128 if self._allow_none:
1126 return result + ' or None'
1129 return result + ' or None'
1127 return result
1130 return result
1128
1131
1129 class CaselessStrEnum(Enum):
1132 class CaselessStrEnum(Enum):
1130 """An enum of strings that are caseless in validate."""
1133 """An enum of strings that are caseless in validate."""
1131
1134
1132 def validate(self, obj, value):
1135 def validate(self, obj, value):
1133 if value is None:
1136 if value is None:
1134 if self._allow_none:
1137 if self._allow_none:
1135 return value
1138 return value
1136
1139
1137 if not isinstance(value, py3compat.string_types):
1140 if not isinstance(value, py3compat.string_types):
1138 self.error(obj, value)
1141 self.error(obj, value)
1139
1142
1140 for v in self.values:
1143 for v in self.values:
1141 if v.lower() == value.lower():
1144 if v.lower() == value.lower():
1142 return v
1145 return v
1143 self.error(obj, value)
1146 self.error(obj, value)
1144
1147
1145 class Container(Instance):
1148 class Container(Instance):
1146 """An instance of a container (list, set, etc.)
1149 """An instance of a container (list, set, etc.)
1147
1150
1148 To be subclassed by overriding klass.
1151 To be subclassed by overriding klass.
1149 """
1152 """
1150 klass = None
1153 klass = None
1151 _valid_defaults = SequenceTypes
1154 _valid_defaults = SequenceTypes
1152 _trait = None
1155 _trait = None
1153
1156
1154 def __init__(self, trait=None, default_value=None, allow_none=True,
1157 def __init__(self, trait=None, default_value=None, allow_none=True,
1155 **metadata):
1158 **metadata):
1156 """Create a container trait type from a list, set, or tuple.
1159 """Create a container trait type from a list, set, or tuple.
1157
1160
1158 The default value is created by doing ``List(default_value)``,
1161 The default value is created by doing ``List(default_value)``,
1159 which creates a copy of the ``default_value``.
1162 which creates a copy of the ``default_value``.
1160
1163
1161 ``trait`` can be specified, which restricts the type of elements
1164 ``trait`` can be specified, which restricts the type of elements
1162 in the container to that TraitType.
1165 in the container to that TraitType.
1163
1166
1164 If only one arg is given and it is not a Trait, it is taken as
1167 If only one arg is given and it is not a Trait, it is taken as
1165 ``default_value``:
1168 ``default_value``:
1166
1169
1167 ``c = List([1,2,3])``
1170 ``c = List([1,2,3])``
1168
1171
1169 Parameters
1172 Parameters
1170 ----------
1173 ----------
1171
1174
1172 trait : TraitType [ optional ]
1175 trait : TraitType [ optional ]
1173 the type for restricting the contents of the Container. If unspecified,
1176 the type for restricting the contents of the Container. If unspecified,
1174 types are not checked.
1177 types are not checked.
1175
1178
1176 default_value : SequenceType [ optional ]
1179 default_value : SequenceType [ optional ]
1177 The default value for the Trait. Must be list/tuple/set, and
1180 The default value for the Trait. Must be list/tuple/set, and
1178 will be cast to the container type.
1181 will be cast to the container type.
1179
1182
1180 allow_none : Bool [ default True ]
1183 allow_none : Bool [ default True ]
1181 Whether to allow the value to be None
1184 Whether to allow the value to be None
1182
1185
1183 **metadata : any
1186 **metadata : any
1184 further keys for extensions to the Trait (e.g. config)
1187 further keys for extensions to the Trait (e.g. config)
1185
1188
1186 """
1189 """
1187 # allow List([values]):
1190 # allow List([values]):
1188 if default_value is None and not is_trait(trait):
1191 if default_value is None and not is_trait(trait):
1189 default_value = trait
1192 default_value = trait
1190 trait = None
1193 trait = None
1191
1194
1192 if default_value is None:
1195 if default_value is None:
1193 args = ()
1196 args = ()
1194 elif isinstance(default_value, self._valid_defaults):
1197 elif isinstance(default_value, self._valid_defaults):
1195 args = (default_value,)
1198 args = (default_value,)
1196 else:
1199 else:
1197 raise TypeError('default value of %s was %s' %(self.__class__.__name__, default_value))
1200 raise TypeError('default value of %s was %s' %(self.__class__.__name__, default_value))
1198
1201
1199 if is_trait(trait):
1202 if is_trait(trait):
1200 self._trait = trait() if isinstance(trait, type) else trait
1203 self._trait = trait() if isinstance(trait, type) else trait
1201 self._trait.name = 'element'
1204 self._trait.name = 'element'
1202 elif trait is not None:
1205 elif trait is not None:
1203 raise TypeError("`trait` must be a Trait or None, got %s"%repr_type(trait))
1206 raise TypeError("`trait` must be a Trait or None, got %s"%repr_type(trait))
1204
1207
1205 super(Container,self).__init__(klass=self.klass, args=args,
1208 super(Container,self).__init__(klass=self.klass, args=args,
1206 allow_none=allow_none, **metadata)
1209 allow_none=allow_none, **metadata)
1207
1210
1208 def element_error(self, obj, element, validator):
1211 def element_error(self, obj, element, validator):
1209 e = "Element of the '%s' trait of %s instance must be %s, but a value of %s was specified." \
1212 e = "Element of the '%s' trait of %s instance must be %s, but a value of %s was specified." \
1210 % (self.name, class_of(obj), validator.info(), repr_type(element))
1213 % (self.name, class_of(obj), validator.info(), repr_type(element))
1211 raise TraitError(e)
1214 raise TraitError(e)
1212
1215
1213 def validate(self, obj, value):
1216 def validate(self, obj, value):
1214 value = super(Container, self).validate(obj, value)
1217 value = super(Container, self).validate(obj, value)
1215 if value is None:
1218 if value is None:
1216 return value
1219 return value
1217
1220
1218 value = self.validate_elements(obj, value)
1221 value = self.validate_elements(obj, value)
1219
1222
1220 return value
1223 return value
1221
1224
1222 def validate_elements(self, obj, value):
1225 def validate_elements(self, obj, value):
1223 validated = []
1226 validated = []
1224 if self._trait is None or isinstance(self._trait, Any):
1227 if self._trait is None or isinstance(self._trait, Any):
1225 return value
1228 return value
1226 for v in value:
1229 for v in value:
1227 try:
1230 try:
1228 v = self._trait.validate(obj, v)
1231 v = self._trait.validate(obj, v)
1229 except TraitError:
1232 except TraitError:
1230 self.element_error(obj, v, self._trait)
1233 self.element_error(obj, v, self._trait)
1231 else:
1234 else:
1232 validated.append(v)
1235 validated.append(v)
1233 return self.klass(validated)
1236 return self.klass(validated)
1234
1237
1235
1238
1236 class List(Container):
1239 class List(Container):
1237 """An instance of a Python list."""
1240 """An instance of a Python list."""
1238 klass = list
1241 klass = list
1239
1242
1240 def __init__(self, trait=None, default_value=None, minlen=0, maxlen=sys.maxsize,
1243 def __init__(self, trait=None, default_value=None, minlen=0, maxlen=sys.maxsize,
1241 allow_none=True, **metadata):
1244 allow_none=True, **metadata):
1242 """Create a List trait type from a list, set, or tuple.
1245 """Create a List trait type from a list, set, or tuple.
1243
1246
1244 The default value is created by doing ``List(default_value)``,
1247 The default value is created by doing ``List(default_value)``,
1245 which creates a copy of the ``default_value``.
1248 which creates a copy of the ``default_value``.
1246
1249
1247 ``trait`` can be specified, which restricts the type of elements
1250 ``trait`` can be specified, which restricts the type of elements
1248 in the container to that TraitType.
1251 in the container to that TraitType.
1249
1252
1250 If only one arg is given and it is not a Trait, it is taken as
1253 If only one arg is given and it is not a Trait, it is taken as
1251 ``default_value``:
1254 ``default_value``:
1252
1255
1253 ``c = List([1,2,3])``
1256 ``c = List([1,2,3])``
1254
1257
1255 Parameters
1258 Parameters
1256 ----------
1259 ----------
1257
1260
1258 trait : TraitType [ optional ]
1261 trait : TraitType [ optional ]
1259 the type for restricting the contents of the Container. If unspecified,
1262 the type for restricting the contents of the Container. If unspecified,
1260 types are not checked.
1263 types are not checked.
1261
1264
1262 default_value : SequenceType [ optional ]
1265 default_value : SequenceType [ optional ]
1263 The default value for the Trait. Must be list/tuple/set, and
1266 The default value for the Trait. Must be list/tuple/set, and
1264 will be cast to the container type.
1267 will be cast to the container type.
1265
1268
1266 minlen : Int [ default 0 ]
1269 minlen : Int [ default 0 ]
1267 The minimum length of the input list
1270 The minimum length of the input list
1268
1271
1269 maxlen : Int [ default sys.maxsize ]
1272 maxlen : Int [ default sys.maxsize ]
1270 The maximum length of the input list
1273 The maximum length of the input list
1271
1274
1272 allow_none : Bool [ default True ]
1275 allow_none : Bool [ default True ]
1273 Whether to allow the value to be None
1276 Whether to allow the value to be None
1274
1277
1275 **metadata : any
1278 **metadata : any
1276 further keys for extensions to the Trait (e.g. config)
1279 further keys for extensions to the Trait (e.g. config)
1277
1280
1278 """
1281 """
1279 self._minlen = minlen
1282 self._minlen = minlen
1280 self._maxlen = maxlen
1283 self._maxlen = maxlen
1281 super(List, self).__init__(trait=trait, default_value=default_value,
1284 super(List, self).__init__(trait=trait, default_value=default_value,
1282 allow_none=allow_none, **metadata)
1285 allow_none=allow_none, **metadata)
1283
1286
1284 def length_error(self, obj, value):
1287 def length_error(self, obj, value):
1285 e = "The '%s' trait of %s instance must be of length %i <= L <= %i, but a value of %s was specified." \
1288 e = "The '%s' trait of %s instance must be of length %i <= L <= %i, but a value of %s was specified." \
1286 % (self.name, class_of(obj), self._minlen, self._maxlen, value)
1289 % (self.name, class_of(obj), self._minlen, self._maxlen, value)
1287 raise TraitError(e)
1290 raise TraitError(e)
1288
1291
1289 def validate_elements(self, obj, value):
1292 def validate_elements(self, obj, value):
1290 length = len(value)
1293 length = len(value)
1291 if length < self._minlen or length > self._maxlen:
1294 if length < self._minlen or length > self._maxlen:
1292 self.length_error(obj, value)
1295 self.length_error(obj, value)
1293
1296
1294 return super(List, self).validate_elements(obj, value)
1297 return super(List, self).validate_elements(obj, value)
1295
1298
1296
1299
1297 class Set(Container):
1300 class Set(Container):
1298 """An instance of a Python set."""
1301 """An instance of a Python set."""
1299 klass = set
1302 klass = set
1300
1303
1301 class Tuple(Container):
1304 class Tuple(Container):
1302 """An instance of a Python tuple."""
1305 """An instance of a Python tuple."""
1303 klass = tuple
1306 klass = tuple
1304
1307
1305 def __init__(self, *traits, **metadata):
1308 def __init__(self, *traits, **metadata):
1306 """Tuple(*traits, default_value=None, allow_none=True, **medatata)
1309 """Tuple(*traits, default_value=None, allow_none=True, **medatata)
1307
1310
1308 Create a tuple from a list, set, or tuple.
1311 Create a tuple from a list, set, or tuple.
1309
1312
1310 Create a fixed-type tuple with Traits:
1313 Create a fixed-type tuple with Traits:
1311
1314
1312 ``t = Tuple(Int, Str, CStr)``
1315 ``t = Tuple(Int, Str, CStr)``
1313
1316
1314 would be length 3, with Int,Str,CStr for each element.
1317 would be length 3, with Int,Str,CStr for each element.
1315
1318
1316 If only one arg is given and it is not a Trait, it is taken as
1319 If only one arg is given and it is not a Trait, it is taken as
1317 default_value:
1320 default_value:
1318
1321
1319 ``t = Tuple((1,2,3))``
1322 ``t = Tuple((1,2,3))``
1320
1323
1321 Otherwise, ``default_value`` *must* be specified by keyword.
1324 Otherwise, ``default_value`` *must* be specified by keyword.
1322
1325
1323 Parameters
1326 Parameters
1324 ----------
1327 ----------
1325
1328
1326 *traits : TraitTypes [ optional ]
1329 *traits : TraitTypes [ optional ]
1327 the tsype for restricting the contents of the Tuple. If unspecified,
1330 the tsype for restricting the contents of the Tuple. If unspecified,
1328 types are not checked. If specified, then each positional argument
1331 types are not checked. If specified, then each positional argument
1329 corresponds to an element of the tuple. Tuples defined with traits
1332 corresponds to an element of the tuple. Tuples defined with traits
1330 are of fixed length.
1333 are of fixed length.
1331
1334
1332 default_value : SequenceType [ optional ]
1335 default_value : SequenceType [ optional ]
1333 The default value for the Tuple. Must be list/tuple/set, and
1336 The default value for the Tuple. Must be list/tuple/set, and
1334 will be cast to a tuple. If `traits` are specified, the
1337 will be cast to a tuple. If `traits` are specified, the
1335 `default_value` must conform to the shape and type they specify.
1338 `default_value` must conform to the shape and type they specify.
1336
1339
1337 allow_none : Bool [ default True ]
1340 allow_none : Bool [ default True ]
1338 Whether to allow the value to be None
1341 Whether to allow the value to be None
1339
1342
1340 **metadata : any
1343 **metadata : any
1341 further keys for extensions to the Trait (e.g. config)
1344 further keys for extensions to the Trait (e.g. config)
1342
1345
1343 """
1346 """
1344 default_value = metadata.pop('default_value', None)
1347 default_value = metadata.pop('default_value', None)
1345 allow_none = metadata.pop('allow_none', True)
1348 allow_none = metadata.pop('allow_none', True)
1346
1349
1347 # allow Tuple((values,)):
1350 # allow Tuple((values,)):
1348 if len(traits) == 1 and default_value is None and not is_trait(traits[0]):
1351 if len(traits) == 1 and default_value is None and not is_trait(traits[0]):
1349 default_value = traits[0]
1352 default_value = traits[0]
1350 traits = ()
1353 traits = ()
1351
1354
1352 if default_value is None:
1355 if default_value is None:
1353 args = ()
1356 args = ()
1354 elif isinstance(default_value, self._valid_defaults):
1357 elif isinstance(default_value, self._valid_defaults):
1355 args = (default_value,)
1358 args = (default_value,)
1356 else:
1359 else:
1357 raise TypeError('default value of %s was %s' %(self.__class__.__name__, default_value))
1360 raise TypeError('default value of %s was %s' %(self.__class__.__name__, default_value))
1358
1361
1359 self._traits = []
1362 self._traits = []
1360 for trait in traits:
1363 for trait in traits:
1361 t = trait() if isinstance(trait, type) else trait
1364 t = trait() if isinstance(trait, type) else trait
1362 t.name = 'element'
1365 t.name = 'element'
1363 self._traits.append(t)
1366 self._traits.append(t)
1364
1367
1365 if self._traits and default_value is None:
1368 if self._traits and default_value is None:
1366 # don't allow default to be an empty container if length is specified
1369 # don't allow default to be an empty container if length is specified
1367 args = None
1370 args = None
1368 super(Container,self).__init__(klass=self.klass, args=args,
1371 super(Container,self).__init__(klass=self.klass, args=args,
1369 allow_none=allow_none, **metadata)
1372 allow_none=allow_none, **metadata)
1370
1373
1371 def validate_elements(self, obj, value):
1374 def validate_elements(self, obj, value):
1372 if not self._traits:
1375 if not self._traits:
1373 # nothing to validate
1376 # nothing to validate
1374 return value
1377 return value
1375 if len(value) != len(self._traits):
1378 if len(value) != len(self._traits):
1376 e = "The '%s' trait of %s instance requires %i elements, but a value of %s was specified." \
1379 e = "The '%s' trait of %s instance requires %i elements, but a value of %s was specified." \
1377 % (self.name, class_of(obj), len(self._traits), repr_type(value))
1380 % (self.name, class_of(obj), len(self._traits), repr_type(value))
1378 raise TraitError(e)
1381 raise TraitError(e)
1379
1382
1380 validated = []
1383 validated = []
1381 for t,v in zip(self._traits, value):
1384 for t,v in zip(self._traits, value):
1382 try:
1385 try:
1383 v = t.validate(obj, v)
1386 v = t.validate(obj, v)
1384 except TraitError:
1387 except TraitError:
1385 self.element_error(obj, v, t)
1388 self.element_error(obj, v, t)
1386 else:
1389 else:
1387 validated.append(v)
1390 validated.append(v)
1388 return tuple(validated)
1391 return tuple(validated)
1389
1392
1390
1393
1391 class Dict(Instance):
1394 class Dict(Instance):
1392 """An instance of a Python dict."""
1395 """An instance of a Python dict."""
1393
1396
1394 def __init__(self, default_value=None, allow_none=True, **metadata):
1397 def __init__(self, default_value=None, allow_none=True, **metadata):
1395 """Create a dict trait type from a dict.
1398 """Create a dict trait type from a dict.
1396
1399
1397 The default value is created by doing ``dict(default_value)``,
1400 The default value is created by doing ``dict(default_value)``,
1398 which creates a copy of the ``default_value``.
1401 which creates a copy of the ``default_value``.
1399 """
1402 """
1400 if default_value is None:
1403 if default_value is None:
1401 args = ((),)
1404 args = ((),)
1402 elif isinstance(default_value, dict):
1405 elif isinstance(default_value, dict):
1403 args = (default_value,)
1406 args = (default_value,)
1404 elif isinstance(default_value, SequenceTypes):
1407 elif isinstance(default_value, SequenceTypes):
1405 args = (default_value,)
1408 args = (default_value,)
1406 else:
1409 else:
1407 raise TypeError('default value of Dict was %s' % default_value)
1410 raise TypeError('default value of Dict was %s' % default_value)
1408
1411
1409 super(Dict,self).__init__(klass=dict, args=args,
1412 super(Dict,self).__init__(klass=dict, args=args,
1410 allow_none=allow_none, **metadata)
1413 allow_none=allow_none, **metadata)
1411
1414
1412 class TCPAddress(TraitType):
1415 class TCPAddress(TraitType):
1413 """A trait for an (ip, port) tuple.
1416 """A trait for an (ip, port) tuple.
1414
1417
1415 This allows for both IPv4 IP addresses as well as hostnames.
1418 This allows for both IPv4 IP addresses as well as hostnames.
1416 """
1419 """
1417
1420
1418 default_value = ('127.0.0.1', 0)
1421 default_value = ('127.0.0.1', 0)
1419 info_text = 'an (ip, port) tuple'
1422 info_text = 'an (ip, port) tuple'
1420
1423
1421 def validate(self, obj, value):
1424 def validate(self, obj, value):
1422 if isinstance(value, tuple):
1425 if isinstance(value, tuple):
1423 if len(value) == 2:
1426 if len(value) == 2:
1424 if isinstance(value[0], py3compat.string_types) and isinstance(value[1], int):
1427 if isinstance(value[0], py3compat.string_types) and isinstance(value[1], int):
1425 port = value[1]
1428 port = value[1]
1426 if port >= 0 and port <= 65535:
1429 if port >= 0 and port <= 65535:
1427 return value
1430 return value
1428 self.error(obj, value)
1431 self.error(obj, value)
1429
1432
1430 class CRegExp(TraitType):
1433 class CRegExp(TraitType):
1431 """A casting compiled regular expression trait.
1434 """A casting compiled regular expression trait.
1432
1435
1433 Accepts both strings and compiled regular expressions. The resulting
1436 Accepts both strings and compiled regular expressions. The resulting
1434 attribute will be a compiled regular expression."""
1437 attribute will be a compiled regular expression."""
1435
1438
1436 info_text = 'a regular expression'
1439 info_text = 'a regular expression'
1437
1440
1438 def validate(self, obj, value):
1441 def validate(self, obj, value):
1439 try:
1442 try:
1440 return re.compile(value)
1443 return re.compile(value)
1441 except:
1444 except:
1442 self.error(obj, value)
1445 self.error(obj, value)
@@ -1,327 +1,390 b''
1 {
1 {
2 "metadata": {
2 "metadata": {
3 "cell_tags": [
3 "cell_tags": [
4 [
4 [
5 "<None>",
5 "<None>",
6 null
6 null
7 ]
7 ]
8 ],
8 ],
9 "name": ""
9 "name": ""
10 },
10 },
11 "nbformat": 3,
11 "nbformat": 3,
12 "nbformat_minor": 0,
12 "nbformat_minor": 0,
13 "worksheets": [
13 "worksheets": [
14 {
14 {
15 "cells": [
15 "cells": [
16 {
16 {
17 "cell_type": "markdown",
17 "cell_type": "markdown",
18 "metadata": {},
18 "metadata": {},
19 "source": [
19 "source": [
20 "To use IPython widgets in the notebook, the widget namespace and display function need to be imported."
20 "To use IPython widgets in the notebook, the widget namespace and display function need to be imported."
21 ]
21 ]
22 },
22 },
23 {
23 {
24 "cell_type": "code",
24 "cell_type": "code",
25 "collapsed": false,
25 "collapsed": false,
26 "input": [
26 "input": [
27 "from IPython.html import widgets # Widget definitions\n",
27 "from IPython.html import widgets # Widget definitions\n",
28 "from IPython.display import display # Used to display widgets in the notebook"
28 "from IPython.display import display # Used to display widgets in the notebook"
29 ],
29 ],
30 "language": "python",
30 "language": "python",
31 "metadata": {},
31 "metadata": {},
32 "outputs": [],
32 "outputs": [],
33 "prompt_number": 1
33 "prompt_number": 1
34 },
34 },
35 {
35 {
36 "cell_type": "heading",
36 "cell_type": "heading",
37 "level": 1,
37 "level": 1,
38 "metadata": {},
38 "metadata": {},
39 "source": [
39 "source": [
40 "Basic Widgets"
40 "Basic Widgets"
41 ]
41 ]
42 },
42 },
43 {
43 {
44 "cell_type": "markdown",
44 "cell_type": "markdown",
45 "metadata": {},
45 "metadata": {},
46 "source": [
46 "source": [
47 "The IPython notebook comes preloaded with basic widgets that represent common data types. These widgets are\n",
47 "The IPython notebook comes preloaded with basic widgets that represent common data types. These widgets are\n",
48 "\n",
48 "\n",
49 "- BoolWidget : boolean \n",
49 "- BoolWidget : boolean \n",
50 "- FloatRangeWidget : bounded float \n",
50 "- FloatRangeWidget : bounded float \n",
51 "- FloatWidget : unbounded float \n",
51 "- FloatWidget : unbounded float \n",
52 "- ImageWidget : image\n",
52 "- ImageWidget : image\n",
53 "- IntRangeWidget : bounded integer \n",
53 "- IntRangeWidget : bounded integer \n",
54 "- IntWidget : unbounded integer \n",
54 "- IntWidget : unbounded integer \n",
55 "- SelectionWidget : enumeration \n",
55 "- SelectionWidget : enumeration \n",
56 "- StringWidget : string \n",
56 "- StringWidget : string \n",
57 "\n",
57 "\n",
58 "A few special widgets are also included, that can be used to capture events and change how other widgets are displayed. These widgets are\n",
58 "A few special widgets are also included, that can be used to capture events and change how other widgets are displayed. These widgets are\n",
59 "\n",
59 "\n",
60 "- ButtonWidget \n",
60 "- ButtonWidget \n",
61 "- ContainerWidget \n",
61 "- ContainerWidget \n",
62 "- MulticontainerWidget \n",
62 "- MulticontainerWidget \n",
63 "\n",
63 "\n",
64 "To see the complete list of widgets, one can execute the following"
64 "To see the complete list of widgets, one can execute the following"
65 ]
65 ]
66 },
66 },
67 {
67 {
68 "cell_type": "code",
68 "cell_type": "code",
69 "collapsed": false,
69 "collapsed": false,
70 "input": [
70 "input": [
71 "[widget for widget in dir(widgets) if widget.endswith('Widget')]"
71 "[widget for widget in dir(widgets) if widget.endswith('Widget')]"
72 ],
72 ],
73 "language": "python",
73 "language": "python",
74 "metadata": {},
74 "metadata": {},
75 "outputs": [
75 "outputs": [
76 {
76 {
77 "metadata": {},
77 "metadata": {},
78 "output_type": "pyout",
78 "output_type": "pyout",
79 "prompt_number": 2,
79 "prompt_number": 2,
80 "text": [
80 "text": [
81 "['BoolWidget',\n",
81 "['BoolWidget',\n",
82 " 'ButtonWidget',\n",
82 " 'ButtonWidget',\n",
83 " 'ContainerWidget',\n",
83 " 'ContainerWidget',\n",
84 " 'FloatRangeWidget',\n",
84 " 'FloatRangeWidget',\n",
85 " 'FloatWidget',\n",
85 " 'FloatWidget',\n",
86 " 'ImageWidget',\n",
86 " 'ImageWidget',\n",
87 " 'IntRangeWidget',\n",
87 " 'IntRangeWidget',\n",
88 " 'IntWidget',\n",
88 " 'IntWidget',\n",
89 " 'MulticontainerWidget',\n",
89 " 'MulticontainerWidget',\n",
90 " 'SelectionWidget',\n",
90 " 'SelectionWidget',\n",
91 " 'StringWidget',\n",
91 " 'StringWidget',\n",
92 " 'Widget']"
92 " 'Widget']"
93 ]
93 ]
94 }
94 }
95 ],
95 ],
96 "prompt_number": 2
96 "prompt_number": 2
97 },
97 },
98 {
98 {
99 "cell_type": "markdown",
99 "cell_type": "markdown",
100 "metadata": {},
100 "metadata": {},
101 "source": [
101 "source": [
102 "The basic widgets can all be constructed without arguments. The following creates a FloatRangeWidget without displaying it"
102 "The basic widgets can all be constructed without arguments. The following creates a FloatRangeWidget without displaying it"
103 ]
103 ]
104 },
104 },
105 {
105 {
106 "cell_type": "code",
106 "cell_type": "code",
107 "collapsed": false,
107 "collapsed": false,
108 "input": [
108 "input": [
109 "mywidget = widgets.FloatRangeWidget()"
109 "mywidget = widgets.FloatRangeWidget()"
110 ],
110 ],
111 "language": "python",
111 "language": "python",
112 "metadata": {},
112 "metadata": {},
113 "outputs": [],
113 "outputs": [
114 {
115 "output_type": "stream",
116 "stream": "stdout",
117 "text": [
118 "['visible', '_css', '_children_attr', '_children_lists_attr', 'default_view_name', 'value', 'step', 'max', 'min', 'disabled', 'orientation', 'description']\n",
119 "[]\n",
120 "[]\n"
121 ]
122 }
123 ],
114 "prompt_number": 3
124 "prompt_number": 3
115 },
125 },
116 {
126 {
117 "cell_type": "markdown",
127 "cell_type": "markdown",
118 "metadata": {},
128 "metadata": {},
119 "source": [
129 "source": [
120 "Constructing a widget does not display it on the page. To display a widget, the widget must be passed to the IPython `display(object)` method. `mywidget` is displayed by"
130 "Constructing a widget does not display it on the page. To display a widget, the widget must be passed to the IPython `display(object)` method. `mywidget` is displayed by"
121 ]
131 ]
122 },
132 },
123 {
133 {
124 "cell_type": "code",
134 "cell_type": "code",
125 "collapsed": false,
135 "collapsed": false,
126 "input": [
136 "input": [
127 "display(mywidget)"
137 "display(mywidget)"
128 ],
138 ],
129 "language": "python",
139 "language": "python",
130 "metadata": {},
140 "metadata": {},
131 "outputs": [],
141 "outputs": [],
132 "prompt_number": 4
142 "prompt_number": 4
133 },
143 },
134 {
144 {
145 "cell_type": "code",
146 "collapsed": false,
147 "input": [
148 "mywidget.value"
149 ],
150 "language": "python",
151 "metadata": {},
152 "outputs": [
153 {
154 "metadata": {},
155 "output_type": "pyout",
156 "prompt_number": 13,
157 "text": [
158 "55.1"
159 ]
160 }
161 ],
162 "prompt_number": 13
163 },
164 {
135 "cell_type": "markdown",
165 "cell_type": "markdown",
136 "metadata": {},
166 "metadata": {},
137 "source": [
167 "source": [
138 "It's important to realize that widgets are not the same as output, even though they are displayed with `display`. Widgets are drawn in a special widget area. That area is marked with a close button which allows you to collapse the widgets. Widgets cannot be interleaved with output. Doing so would break the ability to make simple animations using `clear_output`.\n",
168 "It's important to realize that widgets are not the same as output, even though they are displayed with `display`. Widgets are drawn in a special widget area. That area is marked with a close button which allows you to collapse the widgets. Widgets cannot be interleaved with output. Doing so would break the ability to make simple animations using `clear_output`.\n",
139 "\n",
169 "\n",
140 "Widgets are manipulated via special instance properties (traitlets). The names of these instance properties are listed in the widget's `keys` property (as seen below). A few of these properties are common to most, if not all, widgets. The common properties are `value`, `description`, `visible`, and `disabled`. `_css`, `_add_class`, and `_remove_class` are internal properties that exist in all widgets and should not be modified."
170 "Widgets are manipulated via special instance properties (traitlets). The names of these instance properties are listed in the widget's `keys` property (as seen below). A few of these properties are common to most, if not all, widgets. The common properties are `value`, `description`, `visible`, and `disabled`. `_css`, `_add_class`, and `_remove_class` are internal properties that exist in all widgets and should not be modified."
141 ]
171 ]
142 },
172 },
143 {
173 {
144 "cell_type": "code",
174 "cell_type": "code",
145 "collapsed": false,
175 "collapsed": false,
146 "input": [
176 "input": [
147 "mywidget.keys"
177 "mywidget.keys"
148 ],
178 ],
149 "language": "python",
179 "language": "python",
150 "metadata": {},
180 "metadata": {},
151 "outputs": [
181 "outputs": [
152 {
182 {
153 "metadata": {},
183 "metadata": {},
154 "output_type": "pyout",
184 "output_type": "pyout",
155 "prompt_number": 5,
185 "prompt_number": 11,
156 "text": [
186 "text": [
157 "['visible',\n",
187 "['visible',\n",
158 " '_css',\n",
188 " '_css',\n",
189 " '_children_attr',\n",
190 " '_children_lists_attr',\n",
191 " 'default_view_name',\n",
159 " 'value',\n",
192 " 'value',\n",
160 " 'step',\n",
193 " 'step',\n",
161 " 'max',\n",
194 " 'max',\n",
162 " 'min',\n",
195 " 'min',\n",
163 " 'disabled',\n",
196 " 'disabled',\n",
164 " 'orientation',\n",
197 " 'orientation',\n",
165 " 'description']"
198 " 'description']"
166 ]
199 ]
167 }
200 }
168 ],
201 ],
169 "prompt_number": 5
202 "prompt_number": 11
170 },
203 },
171 {
204 {
172 "cell_type": "markdown",
205 "cell_type": "markdown",
173 "metadata": {},
206 "metadata": {},
174 "source": [
207 "source": [
175 "Changing a widget's property value will automatically update that widget everywhere it is displayed in the notebook. Here the value of `mywidget` is set. The slider shown above (after input 4) updates automatically to the new value. In reverse, changing the value of the displayed widget will update the property's value."
208 "Changing a widget's property value will automatically update that widget everywhere it is displayed in the notebook. Here the value of `mywidget` is set. The slider shown above (after input 4) updates automatically to the new value. In reverse, changing the value of the displayed widget will update the property's value."
176 ]
209 ]
177 },
210 },
178 {
211 {
179 "cell_type": "code",
212 "cell_type": "code",
180 "collapsed": false,
213 "collapsed": false,
181 "input": [
214 "input": [
182 "mywidget.value = 25.0"
215 "mywidget.value = 25.0"
183 ],
216 ],
184 "language": "python",
217 "language": "python",
185 "metadata": {},
218 "metadata": {},
186 "outputs": [],
219 "outputs": [],
187 "prompt_number": 6
220 "prompt_number": 12
188 },
221 },
189 {
222 {
190 "cell_type": "markdown",
223 "cell_type": "markdown",
191 "metadata": {},
224 "metadata": {},
192 "source": [
225 "source": [
193 "After changing the widget's value in the notebook by hand to 0.0 (sliding the bar to the far left)."
226 "After changing the widget's value in the notebook by hand to 0.0 (sliding the bar to the far left)."
194 ]
227 ]
195 },
228 },
196 {
229 {
197 "cell_type": "code",
230 "cell_type": "code",
198 "collapsed": false,
231 "collapsed": false,
199 "input": [
232 "input": [
200 "mywidget.value"
233 "mywidget.value"
201 ],
234 ],
202 "language": "python",
235 "language": "python",
203 "metadata": {},
236 "metadata": {},
204 "outputs": [
237 "outputs": [
205 {
238 {
206 "metadata": {},
239 "metadata": {},
207 "output_type": "pyout",
240 "output_type": "pyout",
208 "prompt_number": 7,
241 "prompt_number": 30,
209 "text": [
242 "text": [
210 "0.0"
243 "25.0"
211 ]
244 ]
212 }
245 }
213 ],
246 ],
214 "prompt_number": 7
247 "prompt_number": 30
215 },
248 },
216 {
249 {
217 "cell_type": "markdown",
250 "cell_type": "markdown",
218 "metadata": {},
251 "metadata": {},
219 "source": [
252 "source": [
220 "Widget property values can also be set with kwargs during the construction of the widget (as seen below)."
253 "Widget property values can also be set with kwargs during the construction of the widget (as seen below)."
221 ]
254 ]
222 },
255 },
223 {
256 {
224 "cell_type": "code",
257 "cell_type": "code",
225 "collapsed": false,
258 "collapsed": false,
226 "input": [
259 "input": [
227 "mysecondwidget = widgets.SelectionWidget(values=[\"Item A\", \"Item B\", \"Item C\"], value=\"Nothing Selected\")\n",
260 "mysecondwidget = widgets.SelectionWidget(values=[\"Item A\", \"Item B\", \"Item C\"], value=\"Nothing Selected\")\n",
228 "display(mysecondwidget)"
261 "display(mysecondwidget)"
229 ],
262 ],
230 "language": "python",
263 "language": "python",
231 "metadata": {},
264 "metadata": {},
232 "outputs": [],
265 "outputs": [
233 "prompt_number": 8
266 {
267 "output_type": "stream",
268 "stream": "stdout",
269 "text": [
270 "['visible', '_css', '_children_attr', '_children_lists_attr', 'default_view_name', 'value', 'values', 'disabled', 'description']\n",
271 "[]\n",
272 "[]\n"
273 ]
274 }
275 ],
276 "prompt_number": 14
277 },
278 {
279 "cell_type": "code",
280 "collapsed": false,
281 "input": [
282 "mysecondwidget.value"
283 ],
284 "language": "python",
285 "metadata": {},
286 "outputs": [
287 {
288 "metadata": {},
289 "output_type": "pyout",
290 "prompt_number": 15,
291 "text": [
292 "u'Item C'"
293 ]
294 }
295 ],
296 "prompt_number": 15
234 },
297 },
235 {
298 {
236 "cell_type": "heading",
299 "cell_type": "heading",
237 "level": 1,
300 "level": 1,
238 "metadata": {},
301 "metadata": {},
239 "source": [
302 "source": [
240 "Views"
303 "Views"
241 ]
304 ]
242 },
305 },
243 {
306 {
244 "cell_type": "markdown",
307 "cell_type": "markdown",
245 "metadata": {},
308 "metadata": {},
246 "source": [
309 "source": [
247 "The data types that most of the widgets represent can be displayed more than one way. A `view` is a visual representation of a widget in the notebook. In the example in the section above, the default `view` for the `FloatRangeWidget` is used. The default view is set in the widgets `default_view_name` instance property (as seen below)."
310 "The data types that most of the widgets represent can be displayed more than one way. A `view` is a visual representation of a widget in the notebook. In the example in the section above, the default `view` for the `FloatRangeWidget` is used. The default view is set in the widgets `default_view_name` instance property (as seen below)."
248 ]
311 ]
249 },
312 },
250 {
313 {
251 "cell_type": "code",
314 "cell_type": "code",
252 "collapsed": false,
315 "collapsed": false,
253 "input": [
316 "input": [
254 "mywidget.default_view_name"
317 "mywidget.default_view_name"
255 ],
318 ],
256 "language": "python",
319 "language": "python",
257 "metadata": {},
320 "metadata": {},
258 "outputs": [
321 "outputs": [
259 {
322 {
260 "metadata": {},
323 "metadata": {},
261 "output_type": "pyout",
324 "output_type": "pyout",
262 "prompt_number": 9,
325 "prompt_number": 16,
263 "text": [
326 "text": [
264 "u'FloatSliderView'"
327 "u'FloatSliderView'"
265 ]
328 ]
266 }
329 }
267 ],
330 ],
268 "prompt_number": 9
331 "prompt_number": 16
269 },
332 },
270 {
333 {
271 "cell_type": "markdown",
334 "cell_type": "markdown",
272 "metadata": {},
335 "metadata": {},
273 "source": [
336 "source": [
274 "When a widget is displayed using `display(...)`, the `default_view_name` is used to determine what view type should be used to display the widget. View names are case sensitive. Sometimes the default view isn't the best view to represent a piece of data. To change what view is used, either the `default_view_name` can be changed or the `view_name` kwarg of `display` can be set. This also can be used to display one widget multiple ways in one output (as seen below)."
337 "When a widget is displayed using `display(...)`, the `default_view_name` is used to determine what view type should be used to display the widget. View names are case sensitive. Sometimes the default view isn't the best view to represent a piece of data. To change what view is used, either the `default_view_name` can be changed or the `view_name` kwarg of `display` can be set. This also can be used to display one widget multiple ways in one output (as seen below)."
275 ]
338 ]
276 },
339 },
277 {
340 {
278 "cell_type": "code",
341 "cell_type": "code",
279 "collapsed": false,
342 "collapsed": false,
280 "input": [
343 "input": [
281 "display(mywidget)\n",
344 "display(mywidget)\n",
282 "display(mywidget, view_name=\"FloatTextView\")"
345 "display(mywidget, view_name=\"FloatTextView\")"
283 ],
346 ],
284 "language": "python",
347 "language": "python",
285 "metadata": {},
348 "metadata": {},
286 "outputs": [],
349 "outputs": [],
287 "prompt_number": 10
350 "prompt_number": 17
288 },
351 },
289 {
352 {
290 "cell_type": "markdown",
353 "cell_type": "markdown",
291 "metadata": {},
354 "metadata": {},
292 "source": [
355 "source": [
293 "Some views work with multiple different widget types and some views only work with one. The complete list of views and supported widgets is below. The default views are italicized.\n",
356 "Some views work with multiple different widget types and some views only work with one. The complete list of views and supported widgets is below. The default views are italicized.\n",
294 "\n",
357 "\n",
295 "| Widget Name | View Names |\n",
358 "| Widget Name | View Names |\n",
296 "|:-----------------------|:--------------------|\n",
359 "|:-----------------------|:--------------------|\n",
297 "| BoolWidget | *CheckboxView* |\n",
360 "| BoolWidget | *CheckboxView* |\n",
298 "| | ToggleButtonView |\n",
361 "| | ToggleButtonView |\n",
299 "| ButtonWidget | *ButtonView* |\n",
362 "| ButtonWidget | *ButtonView* |\n",
300 "| ContainerWidget | *ContainerView* |\n",
363 "| ContainerWidget | *ContainerView* |\n",
301 "| | ModalView |\n",
364 "| | ModalView |\n",
302 "| FloatRangeWidget | *FloatSliderView* |\n",
365 "| FloatRangeWidget | *FloatSliderView* |\n",
303 "| | FloatTextView |\n",
366 "| | FloatTextView |\n",
304 "| | ProgressView |\n",
367 "| | ProgressView |\n",
305 "| FloatWidget | *FloatTextView* |\n",
368 "| FloatWidget | *FloatTextView* |\n",
306 "| ImageWidget | *ImageView* |\n",
369 "| ImageWidget | *ImageView* |\n",
307 "| IntRangeWidget | *IntSliderView* |\n",
370 "| IntRangeWidget | *IntSliderView* |\n",
308 "| | IntTextView |\n",
371 "| | IntTextView |\n",
309 "| | ProgressView |\n",
372 "| | ProgressView |\n",
310 "| IntWidget | *IntTextView* |\n",
373 "| IntWidget | *IntTextView* |\n",
311 "| MulticontainerWidget | AccordionView |\n",
374 "| MulticontainerWidget | AccordionView |\n",
312 "| | *TabView* |\n",
375 "| | *TabView* |\n",
313 "| SelectionWidget | ToggleButtonsView |\n",
376 "| SelectionWidget | ToggleButtonsView |\n",
314 "| | RadioButtonsView |\n",
377 "| | RadioButtonsView |\n",
315 "| | *DropdownView* |\n",
378 "| | *DropdownView* |\n",
316 "| | ListBoxView |\n",
379 "| | ListBoxView |\n",
317 "| StringWidget | HTMLView |\n",
380 "| StringWidget | HTMLView |\n",
318 "| | LatexView |\n",
381 "| | LatexView |\n",
319 "| | TextAreaView |\n",
382 "| | TextAreaView |\n",
320 "| | *TextBoxView* |\n"
383 "| | *TextBoxView* |\n"
321 ]
384 ]
322 }
385 }
323 ],
386 ],
324 "metadata": {}
387 "metadata": {}
325 }
388 }
326 ]
389 ]
327 } No newline at end of file
390 }
@@ -1,271 +1,479 b''
1 {
1 {
2 "metadata": {
2 "metadata": {
3 "cell_tags": [
3 "cell_tags": [
4 [
4 [
5 "<None>",
5 "<None>",
6 null
6 null
7 ]
7 ]
8 ],
8 ],
9 "name": ""
9 "name": ""
10 },
10 },
11 "nbformat": 3,
11 "nbformat": 3,
12 "nbformat_minor": 0,
12 "nbformat_minor": 0,
13 "worksheets": [
13 "worksheets": [
14 {
14 {
15 "cells": [
15 "cells": [
16 {
16 {
17 "cell_type": "code",
17 "cell_type": "code",
18 "collapsed": false,
18 "collapsed": false,
19 "input": [
19 "input": [
20 "from __future__ import print_function # 2.7 compatability\n",
20 "from __future__ import print_function # 2.7 compatability\n",
21 "\n",
21 "\n",
22 "from IPython.html import widgets # Widget definitions\n",
22 "from IPython.html import widgets # Widget definitions\n",
23 "from IPython.display import display # Used to display widgets in the notebook"
23 "from IPython.display import display # Used to display widgets in the notebook"
24 ],
24 ],
25 "language": "python",
25 "language": "python",
26 "metadata": {},
26 "metadata": {},
27 "outputs": [],
27 "outputs": [],
28 "prompt_number": 1
28 "prompt_number": 1
29 },
29 },
30 {
30 {
31 "cell_type": "heading",
31 "cell_type": "heading",
32 "level": 1,
32 "level": 1,
33 "metadata": {},
33 "metadata": {},
34 "source": [
34 "source": [
35 "Traitlet Events"
35 "Traitlet Events"
36 ]
36 ]
37 },
37 },
38 {
38 {
39 "cell_type": "markdown",
39 "cell_type": "markdown",
40 "metadata": {},
40 "metadata": {},
41 "source": [
41 "source": [
42 "The widget properties are IPython traitlets. Traitlets are eventful. To handle property value changes, the `on_trait_change` method of the widget can be used to register an event handling callback. The doc string for `on_trait_change` can be seen below. Both the `name` and `remove` properties are optional."
42 "The widget properties are IPython traitlets. Traitlets are eventful. To handle property value changes, the `on_trait_change` method of the widget can be used to register an event handling callback. The doc string for `on_trait_change` can be seen below. Both the `name` and `remove` properties are optional."
43 ]
43 ]
44 },
44 },
45 {
45 {
46 "cell_type": "code",
46 "cell_type": "code",
47 "collapsed": false,
47 "collapsed": false,
48 "input": [
48 "input": [
49 "print(widgets.Widget.on_trait_change.__doc__)"
49 "print(widgets.Widget.on_trait_change.__doc__)"
50 ],
50 ],
51 "language": "python",
51 "language": "python",
52 "metadata": {},
52 "metadata": {},
53 "outputs": [
53 "outputs": [
54 {
54 {
55 "output_type": "stream",
55 "output_type": "stream",
56 "stream": "stdout",
56 "stream": "stdout",
57 "text": [
57 "text": [
58 "Setup a handler to be called when a trait changes.\n",
58 "Setup a handler to be called when a trait changes.\n",
59 "\n",
59 "\n",
60 " This is used to setup dynamic notifications of trait changes.\n",
60 " This is used to setup dynamic notifications of trait changes.\n",
61 "\n",
61 "\n",
62 " Static handlers can be created by creating methods on a HasTraits\n",
62 " Static handlers can be created by creating methods on a HasTraits\n",
63 " subclass with the naming convention '_[traitname]_changed'. Thus,\n",
63 " subclass with the naming convention '_[traitname]_changed'. Thus,\n",
64 " to create static handler for the trait 'a', create the method\n",
64 " to create static handler for the trait 'a', create the method\n",
65 " _a_changed(self, name, old, new) (fewer arguments can be used, see\n",
65 " _a_changed(self, name, old, new) (fewer arguments can be used, see\n",
66 " below).\n",
66 " below).\n",
67 "\n",
67 "\n",
68 " Parameters\n",
68 " Parameters\n",
69 " ----------\n",
69 " ----------\n",
70 " handler : callable\n",
70 " handler : callable\n",
71 " A callable that is called when a trait changes. Its\n",
71 " A callable that is called when a trait changes. Its\n",
72 " signature can be handler(), handler(name), handler(name, new)\n",
72 " signature can be handler(), handler(name), handler(name, new)\n",
73 " or handler(name, old, new).\n",
73 " or handler(name, old, new).\n",
74 " name : list, str, None\n",
74 " name : list, str, None\n",
75 " If None, the handler will apply to all traits. If a list\n",
75 " If None, the handler will apply to all traits. If a list\n",
76 " of str, handler will apply to all names in the list. If a\n",
76 " of str, handler will apply to all names in the list. If a\n",
77 " str, the handler will apply just to that name.\n",
77 " str, the handler will apply just to that name.\n",
78 " remove : bool\n",
78 " remove : bool\n",
79 " If False (the default), then install the handler. If True\n",
79 " If False (the default), then install the handler. If True\n",
80 " then unintall it.\n",
80 " then unintall it.\n",
81 " \n"
81 " \n"
82 ]
82 ]
83 }
83 }
84 ],
84 ],
85 "prompt_number": 2
85 "prompt_number": 2
86 },
86 },
87 {
87 {
88 "cell_type": "markdown",
88 "cell_type": "markdown",
89 "metadata": {},
89 "metadata": {},
90 "source": [
90 "source": [
91 "Mentioned in the doc string, the callback registered can have 4 possible signatures:\n",
91 "Mentioned in the doc string, the callback registered can have 4 possible signatures:\n",
92 "\n",
92 "\n",
93 "- callback()\n",
93 "- callback()\n",
94 "- callback(trait_name)\n",
94 "- callback(trait_name)\n",
95 "- callback(trait_name, new_value)\n",
95 "- callback(trait_name, new_value)\n",
96 "- callback(trait_name, old_value, new_value)\n",
96 "- callback(trait_name, old_value, new_value)\n",
97 "\n",
97 "\n",
98 "An example of how to output an IntRangeWiget's value as it is changed can be seen below."
98 "An example of how to output an IntRangeWiget's value as it is changed can be seen below."
99 ]
99 ]
100 },
100 },
101 {
101 {
102 "cell_type": "code",
102 "cell_type": "code",
103 "collapsed": false,
103 "collapsed": false,
104 "input": [
104 "input": [
105 "intrange = widgets.IntRangeWidget()\n",
105 "intrange = widgets.IntRangeWidget()\n",
106 "display(intrange)\n",
106 "display(intrange)\n",
107 "\n",
107 "\n",
108 "def on_value_change(name, value):\n",
108 "def on_value_change(name, value):\n",
109 " print(value)\n",
109 " print(value)\n",
110 "\n",
110 "\n",
111 "intrange.on_trait_change(on_value_change, 'value')"
111 "#intrange.on_trait_change(on_value_change, 'value')"
112 ],
112 ],
113 "language": "python",
113 "language": "python",
114 "metadata": {},
114 "metadata": {},
115 "outputs": [
115 "outputs": [
116 {
116 {
117 "output_type": "stream",
117 "output_type": "stream",
118 "stream": "stdout",
118 "stream": "stdout",
119 "text": [
119 "text": [
120 "28\n"
120 "['visible', '_css', '_children_attr', '_children_lists_attr', 'default_view_name', 'value', 'step', 'max', 'min', 'disabled', 'orientation', 'description']\n",
121 ]
121 "[]\n",
122 },
122 "[]\n"
123 {
124 "output_type": "stream",
125 "stream": "stdout",
126 "text": [
127 "55\n"
128 ]
129 },
130 {
131 "output_type": "stream",
132 "stream": "stdout",
133 "text": [
134 "94\n"
135 ]
123 ]
136 }
124 }
137 ],
125 ],
138 "prompt_number": 3
126 "prompt_number": 9
139 },
127 },
140 {
128 {
141 "cell_type": "heading",
129 "cell_type": "heading",
142 "level": 1,
130 "level": 1,
143 "metadata": {},
131 "metadata": {},
144 "source": [
132 "source": [
145 "Specialized Events"
133 "Specialized Events"
146 ]
134 ]
147 },
135 },
148 {
136 {
149 "cell_type": "heading",
137 "cell_type": "heading",
150 "level": 2,
138 "level": 2,
151 "metadata": {},
139 "metadata": {},
152 "source": [
140 "source": [
153 "Button On Click Event"
141 "Button On Click Event"
154 ]
142 ]
155 },
143 },
156 {
144 {
157 "cell_type": "markdown",
145 "cell_type": "markdown",
158 "metadata": {},
146 "metadata": {},
159 "source": [
147 "source": [
160 "The `ButtonWidget` is a special widget, like the `ContainerWidget` and `MulticontainerWidget`, that isn't used to represent a data type. Instead the button widget is used to handle mouse clicks. The `on_click` method of the `ButtonWidget` can be used to register a click even handler. The doc string of the `on_click` can be seen below."
148 "The `ButtonWidget` is a special widget, like the `ContainerWidget` and `MulticontainerWidget`, that isn't used to represent a data type. Instead the button widget is used to handle mouse clicks. The `on_click` method of the `ButtonWidget` can be used to register a click even handler. The doc string of the `on_click` can be seen below."
161 ]
149 ]
162 },
150 },
163 {
151 {
164 "cell_type": "code",
152 "cell_type": "code",
165 "collapsed": false,
153 "collapsed": false,
166 "input": [
154 "input": [
167 "print(widgets.ButtonWidget.on_click.__doc__)"
155 "print(widgets.ButtonWidget.on_click.__doc__)"
168 ],
156 ],
169 "language": "python",
157 "language": "python",
170 "metadata": {},
158 "metadata": {},
171 "outputs": [
159 "outputs": [
172 {
160 {
173 "output_type": "stream",
161 "output_type": "stream",
174 "stream": "stdout",
162 "stream": "stdout",
175 "text": [
163 "text": [
176 "Register a callback to execute when the button is clicked. The\n",
164 "Register a callback to execute when the button is clicked. The\n",
177 " callback can either accept no parameters or one sender parameter:\n",
165 " callback can either accept no parameters or one sender parameter:\n",
178 " - callback()\n",
166 " - callback()\n",
179 " - callback(sender)\n",
167 " - callback(sender)\n",
180 " If the callback has a sender parameter, the ButtonWidget instance that\n",
168 " If the callback has a sender parameter, the ButtonWidget instance that\n",
181 " called the callback will be passed into the method as the sender.\n",
169 " called the callback will be passed into the method as the sender.\n",
182 "\n",
170 "\n",
183 " Parameters\n",
171 " Parameters\n",
184 " ----------\n",
172 " ----------\n",
185 " remove : bool (optional)\n",
173 " remove : bool (optional)\n",
186 " Set to true to remove the callback from the list of callbacks.\n"
174 " Set to true to remove the callback from the list of callbacks.\n"
187 ]
175 ]
188 }
176 }
189 ],
177 ],
190 "prompt_number": 4
178 "prompt_number": 4
191 },
179 },
192 {
180 {
193 "cell_type": "markdown",
181 "cell_type": "markdown",
194 "metadata": {},
182 "metadata": {},
195 "source": [
183 "source": [
196 "Button clicks are transmitted from the front-end to the back-end using custom messages. By using the `on_click` method, a button that prints a message when it has been clicked is shown below."
184 "Button clicks are transmitted from the front-end to the back-end using custom messages. By using the `on_click` method, a button that prints a message when it has been clicked is shown below."
197 ]
185 ]
198 },
186 },
199 {
187 {
200 "cell_type": "code",
188 "cell_type": "code",
201 "collapsed": false,
189 "collapsed": false,
202 "input": [
190 "input": [
191 "display(intrange)\n",
192 "print('hi')"
193 ],
194 "language": "python",
195 "metadata": {},
196 "outputs": [
197 {
198 "output_type": "stream",
199 "stream": "stdout",
200 "text": [
201 "hi\n"
202 ]
203 }
204 ],
205 "prompt_number": 5
206 },
207 {
208 "cell_type": "code",
209 "collapsed": false,
210 "input": [
203 "button = widgets.ButtonWidget(description=\"Click Me!\")\n",
211 "button = widgets.ButtonWidget(description=\"Click Me!\")\n",
204 "display(button)\n",
212 "display(button)\n",
205 "\n",
213 "\n",
206 "def on_button_clicked(sender):\n",
214 "def on_button_clicked(sender):\n",
207 " print(\"Button clicked.\")\n",
215 " print(\"Button clicked.\")\n",
216 " intrange.value +=1\n",
208 "\n",
217 "\n",
209 "button.on_click(on_button_clicked)"
218 "button.on_click(on_button_clicked)"
210 ],
219 ],
211 "language": "python",
220 "language": "python",
212 "metadata": {},
221 "metadata": {},
213 "outputs": [
222 "outputs": [
214 {
223 {
215 "output_type": "stream",
224 "output_type": "stream",
216 "stream": "stdout",
225 "stream": "stdout",
217 "text": [
226 "text": [
227 "['visible', '_css', '_children_attr', '_children_lists_attr', 'default_view_name', 'description', 'disabled']\n",
228 "[]\n",
229 "[]\n"
230 ]
231 },
232 {
233 "output_type": "stream",
234 "stream": "stdout",
235 "text": [
236 "Button clicked.\n"
237 ]
238 },
239 {
240 "output_type": "stream",
241 "stream": "stdout",
242 "text": [
218 "Button clicked.\n"
243 "Button clicked.\n"
219 ]
244 ]
220 },
245 },
221 {
246 {
222 "output_type": "stream",
247 "output_type": "stream",
223 "stream": "stdout",
248 "stream": "stdout",
224 "text": [
249 "text": [
225 "Button clicked.\n"
250 "Button clicked.\n"
226 ]
251 ]
227 },
252 },
228 {
253 {
229 "output_type": "stream",
254 "output_type": "stream",
230 "stream": "stdout",
255 "stream": "stdout",
231 "text": [
256 "text": [
232 "Button clicked.\n"
257 "Button clicked.\n"
233 ]
258 ]
234 }
259 }
235 ],
260 ],
236 "prompt_number": 5
261 "prompt_number": 12
237 },
262 },
238 {
263 {
239 "cell_type": "markdown",
264 "cell_type": "markdown",
240 "metadata": {},
265 "metadata": {},
241 "source": [
266 "source": [
242 "Event handlers can also be used to create widgets. In the example below, clicking a button spawns another button with a description equal to how many times the parent button had been clicked at the time."
267 "Event handlers can also be used to create widgets. In the example below, clicking a button spawns another button with a description equal to how many times the parent button had been clicked at the time."
243 ]
268 ]
244 },
269 },
245 {
270 {
246 "cell_type": "code",
271 "cell_type": "code",
247 "collapsed": false,
272 "collapsed": false,
273 "input": [],
274 "language": "python",
275 "metadata": {},
276 "outputs": [
277 {
278 "metadata": {},
279 "output_type": "pyout",
280 "prompt_number": 11,
281 "text": [
282 "{'content': {'data': \"{'parent_header': {}, 'msg_type': u'comm_msg', 'msg_id': u'3DBB06AD83C942DD85DC6477B08F1FBF', 'content': {u'data': {u'method': u'custom', u'custom_content': {u'event': u'click'}}, u'comm_id': u'eea5f11ae7aa473993dd0c81d6016648'}, 'header': {u'username': u'username', u'msg_id': u'3DBB06AD83C942DD85DC6477B08F1FBF', u'msg_type': u'comm_msg', u'session': u'0F6D6BE728DA47A38CFC4BDEACF34FC4'}, 'buffers': [], 'metadata': {}}\\ncustom message {'parent_header': {}, 'msg_type': u'comm_msg', 'msg_id': u'3DBB06AD83C942DD85DC6477B08F1FBF', 'content': {u'data': {u'method': u'custom', u'custom_content': {u'event': u'click'}}, u'comm_id': u'eea5f11ae7aa473993dd0c81d6016648'}, 'header': {u'username': u'username', u'msg_id': u'3DBB06AD83C942DD85DC6477B08F1FBF', u'msg_type': u'comm_msg', u'session': u'0F6D6BE728DA47A38CFC4BDEACF34FC4'}, 'buffers': [], 'metadata': {}}\\nhandling click\\n{u'event': u'click'}\\nButton clicked.\\n2\\n\",\n",
283 " 'name': 'stdout'},\n",
284 " 'header': {'msg_id': 'd9dc144a-d86c-42c1-8bab-f8a6bc525723',\n",
285 " 'msg_type': 'stream',\n",
286 " 'session': '9b9408d8-7420-4e0c-976d-cdda9f8d2564',\n",
287 " 'username': 'kernel'},\n",
288 " 'metadata': {},\n",
289 " 'msg_id': 'd9dc144a-d86c-42c1-8bab-f8a6bc525723',\n",
290 " 'msg_type': 'stream',\n",
291 " 'parent_header': {'msg_id': '3DBB06AD83C942DD85DC6477B08F1FBF',\n",
292 " 'msg_type': 'comm_msg',\n",
293 " 'session': '0F6D6BE728DA47A38CFC4BDEACF34FC4',\n",
294 " 'username': 'username'}}"
295 ]
296 }
297 ],
298 "prompt_number": 11
299 },
300 {
301 "cell_type": "code",
302 "collapsed": false,
248 "input": [
303 "input": [
249 "def show_button(sender=None):\n",
304 "def show_button(sender=None):\n",
250 " button = widgets.ButtonWidget()\n",
305 " button = widgets.ButtonWidget()\n",
251 " button.clicks = 0\n",
306 " button.clicks = 0\n",
252 " if sender is None:\n",
307 " if sender is None:\n",
253 " button.description = \"0\"\n",
308 " button.description = \"0\"\n",
254 " else:\n",
309 " else:\n",
255 " sender.clicks += 1\n",
310 " sender.clicks += 1\n",
256 " button.description = \"%d\" % sender.clicks\n",
311 " button.description = \"%d\" % sender.clicks\n",
257 " display(button)\n",
312 " display(button)\n",
258 " button.on_click(show_button)\n",
313 " button.on_click(show_button)\n",
259 "show_button()\n",
314 "show_button()\n",
260 " "
315 " "
261 ],
316 ],
262 "language": "python",
317 "language": "python",
263 "metadata": {},
318 "metadata": {},
264 "outputs": [],
319 "outputs": [
265 "prompt_number": 6
320 {
321 "output_type": "stream",
322 "stream": "stdout",
323 "text": [
324 "['visible', '_css', '_children_attr', '_children_lists_attr', 'default_view_name', 'description', 'disabled']\n",
325 "[]\n",
326 "[]\n"
327 ]
328 },
329 {
330 "output_type": "stream",
331 "stream": "stdout",
332 "text": [
333 "['visible', '_css', '_children_attr', '_children_lists_attr', 'default_view_name', 'description', 'disabled']\n",
334 "[]\n",
335 "[]\n"
336 ]
337 },
338 {
339 "output_type": "stream",
340 "stream": "stdout",
341 "text": [
342 "['visible', '_css', '_children_attr', '_children_lists_attr', 'default_view_name', 'description', 'disabled']\n",
343 "[]\n",
344 "[]\n"
345 ]
346 },
347 {
348 "output_type": "stream",
349 "stream": "stdout",
350 "text": [
351 "['visible', '_css', '_children_attr', '_children_lists_attr', 'default_view_name', 'description', 'disabled']\n",
352 "[]\n",
353 "[]\n"
354 ]
355 },
356 {
357 "output_type": "stream",
358 "stream": "stdout",
359 "text": [
360 "['visible', '_css', '_children_attr', '_children_lists_attr', 'default_view_name', 'description', 'disabled']\n",
361 "[]\n",
362 "[]\n"
363 ]
364 },
365 {
366 "output_type": "stream",
367 "stream": "stdout",
368 "text": [
369 "['visible', '_css', '_children_attr', '_children_lists_attr', 'default_view_name', 'description', 'disabled']\n",
370 "[]\n",
371 "[]\n"
372 ]
373 },
374 {
375 "output_type": "stream",
376 "stream": "stdout",
377 "text": [
378 "['visible', '_css', '_children_attr', '_children_lists_attr', 'default_view_name', 'description', 'disabled']\n",
379 "[]\n",
380 "[]\n"
381 ]
382 },
383 {
384 "output_type": "stream",
385 "stream": "stdout",
386 "text": [
387 "['visible', '_css', '_children_attr', '_children_lists_attr', 'default_view_name', 'description', 'disabled']\n",
388 "[]\n",
389 "[]\n"
390 ]
391 },
392 {
393 "output_type": "stream",
394 "stream": "stdout",
395 "text": [
396 "['visible', '_css', '_children_attr', '_children_lists_attr', 'default_view_name', 'description', 'disabled']\n",
397 "[]\n",
398 "[]\n"
399 ]
400 },
401 {
402 "output_type": "stream",
403 "stream": "stdout",
404 "text": [
405 "['visible', '_css', '_children_attr', '_children_lists_attr', 'default_view_name', 'description', 'disabled']\n",
406 "[]\n",
407 "[]\n"
408 ]
409 },
410 {
411 "output_type": "stream",
412 "stream": "stdout",
413 "text": [
414 "['visible', '_css', '_children_attr', '_children_lists_attr', 'default_view_name', 'description', 'disabled']\n",
415 "[]\n",
416 "[]\n"
417 ]
418 },
419 {
420 "output_type": "stream",
421 "stream": "stdout",
422 "text": [
423 "['visible', '_css', '_children_attr', '_children_lists_attr', 'default_view_name', 'description', 'disabled']\n",
424 "[]\n",
425 "[]\n"
426 ]
427 },
428 {
429 "output_type": "stream",
430 "stream": "stdout",
431 "text": [
432 "['visible', '_css', '_children_attr', '_children_lists_attr', 'default_view_name', 'description', 'disabled']\n",
433 "[]\n",
434 "[]\n"
435 ]
436 },
437 {
438 "output_type": "stream",
439 "stream": "stdout",
440 "text": [
441 "['visible', '_css', '_children_attr', '_children_lists_attr', 'default_view_name', 'description', 'disabled']\n",
442 "[]\n",
443 "[]\n"
444 ]
445 },
446 {
447 "output_type": "stream",
448 "stream": "stdout",
449 "text": [
450 "['visible', '_css', '_children_attr', '_children_lists_attr', 'default_view_name', 'description', 'disabled']\n",
451 "[]\n",
452 "[]\n"
453 ]
454 },
455 {
456 "output_type": "stream",
457 "stream": "stdout",
458 "text": [
459 "['visible', '_css', '_children_attr', '_children_lists_attr', 'default_view_name', 'description', 'disabled']\n",
460 "[]\n",
461 "[]\n"
462 ]
463 }
464 ],
465 "prompt_number": 7
466 },
467 {
468 "cell_type": "code",
469 "collapsed": false,
470 "input": [],
471 "language": "python",
472 "metadata": {},
473 "outputs": []
266 }
474 }
267 ],
475 ],
268 "metadata": {}
476 "metadata": {}
269 }
477 }
270 ]
478 ]
271 } No newline at end of file
479 }
@@ -1,258 +1,345 b''
1 {
1 {
2 "metadata": {
2 "metadata": {
3 "cell_tags": [
3 "cell_tags": [
4 [
4 [
5 "<None>",
5 "<None>",
6 null
6 null
7 ]
7 ]
8 ],
8 ],
9 "name": ""
9 "name": ""
10 },
10 },
11 "nbformat": 3,
11 "nbformat": 3,
12 "nbformat_minor": 0,
12 "nbformat_minor": 0,
13 "worksheets": [
13 "worksheets": [
14 {
14 {
15 "cells": [
15 "cells": [
16 {
16 {
17 "cell_type": "code",
17 "cell_type": "code",
18 "collapsed": false,
18 "collapsed": false,
19 "input": [
19 "input": [
20 "from IPython.html import widgets # Widget definitions\n",
20 "from IPython.html import widgets # Widget definitions\n",
21 "from IPython.display import display # Used to display widgets in the notebook"
21 "from IPython.display import display # Used to display widgets in the notebook"
22 ],
22 ],
23 "language": "python",
23 "language": "python",
24 "metadata": {},
24 "metadata": {},
25 "outputs": [],
25 "outputs": [],
26 "prompt_number": 1
26 "prompt_number": 1
27 },
27 },
28 {
28 {
29 "cell_type": "heading",
29 "cell_type": "heading",
30 "level": 1,
30 "level": 1,
31 "metadata": {},
31 "metadata": {},
32 "source": [
32 "source": [
33 "Parent/Child Relationships"
33 "Parent/Child Relationships"
34 ]
34 ]
35 },
35 },
36 {
36 {
37 "cell_type": "markdown",
37 "cell_type": "markdown",
38 "metadata": {},
38 "metadata": {},
39 "source": [
39 "source": [
40 "To display widget A inside widget B, widget A must be a child of widget B. With IPython widgets, the widgets are instances that live in the back-end (usally Python). There can be multiple views displayed in the front-end that represent one widget in the backend. Each view can be displayed at a different time, or even displayed two or more times in the same output. Because of this, the parent of a widget can only be set before the widget has been displayed.\n",
40 "To display widget A inside widget B, widget A must be a child of widget B. With IPython widgets, the widgets are instances that live in the back-end (usally Python). There can be multiple views displayed in the front-end that represent one widget in the backend. Each view can be displayed at a different time, or even displayed two or more times in the same output. Because of this, the parent of a widget can only be set before the widget has been displayed.\n",
41 "\n",
41 "\n",
42 "Every widget has a `parent` property. This property can be set via a kwarg in the widget's constructor or after construction, but before display. Calling display on an object with children automatically displays those children too (as seen below)."
42 "Every widget has a `parent` property. This property can be set via a kwarg in the widget's constructor or after construction, but before display. Calling display on an object with children automatically displays those children too (as seen below)."
43 ]
43 ]
44 },
44 },
45 {
45 {
46 "cell_type": "code",
46 "cell_type": "code",
47 "collapsed": false,
47 "collapsed": false,
48 "input": [
48 "input": [
49 "container = widgets.MulticontainerWidget()\n",
50 "\n",
49 "\n",
51 "floatrange = widgets.FloatRangeWidget(parent=container) # You can set the parent in the constructor,\n",
50 "floatrange = widgets.FloatRangeWidget() # You can set the parent in the constructor,\n",
52 "\n",
51 "\n",
53 "string = widgets.StringWidget()\n",
52 "string = widgets.StringWidget(value='hi')\n",
54 "string.parent = container # or after the widget has been created.\n",
53 "container = widgets.MulticontainerWidget(children=[floatrange, string])\n",
55 "\n",
54 "\n",
56 "display(container) # Displays the `container` and all of it's children."
55 "display(container) # Displays the `container` and all of it's children."
57 ],
56 ],
58 "language": "python",
57 "language": "python",
59 "metadata": {},
58 "metadata": {},
60 "outputs": [],
59 "outputs": [],
61 "prompt_number": 2
60 "prompt_number": 2
62 },
61 },
63 {
62 {
64 "cell_type": "markdown",
63 "cell_type": "markdown",
65 "metadata": {},
64 "metadata": {},
66 "source": [
65 "source": [
67 "Children can also be added to parents after the parent has been displayed. If the children are added after the parent has already been displayed, the children must be displayed themselves.\n",
66 "Children can also be added to parents after the parent has been displayed. If the children are added after the parent has already been displayed, the children must be displayed themselves.\n",
68 "\n",
67 "\n",
69 "In the example below, the IntRangeWidget is never rendered since display was called on the parent before the parent/child relationship was established."
68 "In the example below, the IntRangeWidget is never rendered since display was called on the parent before the parent/child relationship was established."
70 ]
69 ]
71 },
70 },
72 {
71 {
73 "cell_type": "code",
72 "cell_type": "code",
74 "collapsed": false,
73 "collapsed": false,
75 "input": [
74 "input": [
76 "container = widgets.MulticontainerWidget()\n",
75 "container = widgets.MulticontainerWidget()\n",
77 "display(container)\n",
76 "display(container)\n",
78 "\n",
77 "\n",
79 "intrange = widgets.IntRangeWidget(parent=container) # Never gets displayed."
78 "intrange = widgets.IntRangeWidget(parent=container) # Never gets displayed."
80 ],
79 ],
81 "language": "python",
80 "language": "python",
82 "metadata": {},
81 "metadata": {},
83 "outputs": [],
82 "outputs": [],
84 "prompt_number": 3
83 "prompt_number": 3
85 },
84 },
86 {
85 {
87 "cell_type": "markdown",
86 "cell_type": "markdown",
88 "metadata": {},
87 "metadata": {},
89 "source": [
88 "source": [
90 "Calling display on the child fixes the problem."
89 "Calling display on the child fixes the problem."
91 ]
90 ]
92 },
91 },
93 {
92 {
94 "cell_type": "code",
93 "cell_type": "code",
95 "collapsed": false,
94 "collapsed": false,
96 "input": [
95 "input": [
97 "container = widgets.MulticontainerWidget()\n",
96 "container = widgets.MulticontainerWidget()\n",
98 "display(container)\n",
97 "display(container)\n",
99 "\n",
98 "\n",
100 "intrange = widgets.IntRangeWidget(parent=container)\n",
99 "intrange = widgets.IntRangeWidget(parent=container)\n",
101 "display(intrange) # This line is needed since the `container` has already been displayed."
100 "display(intrange) # This line is needed since the `container` has already been displayed."
102 ],
101 ],
103 "language": "python",
102 "language": "python",
104 "metadata": {},
103 "metadata": {},
105 "outputs": [],
104 "outputs": [],
106 "prompt_number": 4
105 "prompt_number": 4
107 },
106 },
108 {
107 {
109 "cell_type": "heading",
108 "cell_type": "heading",
110 "level": 1,
109 "level": 1,
111 "metadata": {},
110 "metadata": {},
112 "source": [
111 "source": [
113 "Changing Child Views"
112 "Changing Child Views"
114 ]
113 ]
115 },
114 },
116 {
115 {
117 "cell_type": "markdown",
116 "cell_type": "markdown",
118 "metadata": {},
117 "metadata": {},
119 "source": [
118 "source": [
120 "The view used to display a widget must defined by the time the widget is displayed. If children widgets are to be displayed along with the parent in one call, their `view_name`s can't be set since all of the widgets are sharing the same display call. Instead, their `default_view_name`s must be set (as seen below)."
119 "The view used to display a widget must defined by the time the widget is displayed. If children widgets are to be displayed along with the parent in one call, their `view_name`s can't be set since all of the widgets are sharing the same display call. Instead, their `default_view_name`s must be set (as seen below)."
121 ]
120 ]
122 },
121 },
123 {
122 {
124 "cell_type": "code",
123 "cell_type": "code",
125 "collapsed": false,
124 "collapsed": false,
126 "input": [
125 "input": [
127 "container = widgets.MulticontainerWidget()\n",
126 "container = widgets.MulticontainerWidget()\n",
128 "\n",
127 "\n",
129 "floatrange = widgets.FloatRangeWidget(parent=container)\n",
128 "floatrange = widgets.FloatRangeWidget(parent=container)\n",
130 "floatrange.default_view_name = \"FloatTextView\" # It can be set as a property.\n",
129 "floatrange.default_view_name = \"FloatTextView\" # It can be set as a property.\n",
131 "\n",
130 "\n",
132 "string = widgets.StringWidget(default_view_name = \"TextAreaView\") # It can also be set in the constructor.\n",
131 "string = widgets.StringWidget(default_view_name = \"TextAreaView\") # It can also be set in the constructor.\n",
133 "string.parent = container\n",
132 "string.parent = container\n",
134 "\n",
133 "\n",
135 "display(container)"
134 "display(container)"
136 ],
135 ],
137 "language": "python",
136 "language": "python",
138 "metadata": {},
137 "metadata": {},
139 "outputs": [],
138 "outputs": [],
140 "prompt_number": 5
139 "prompt_number": 5
141 },
140 },
142 {
141 {
143 "cell_type": "markdown",
142 "cell_type": "markdown",
144 "metadata": {},
143 "metadata": {},
145 "source": [
144 "source": [
146 "However, if the children are displayed after the parent, their `view_name` can also be set like normal. Both methods will work. The code below produces the same output as the code above."
145 "However, if the children are displayed after the parent, their `view_name` can also be set like normal. Both methods will work. The code below produces the same output as the code above."
147 ]
146 ]
148 },
147 },
149 {
148 {
150 "cell_type": "code",
149 "cell_type": "code",
151 "collapsed": false,
150 "collapsed": false,
152 "input": [
151 "input": [
153 "container = widgets.MulticontainerWidget()\n",
152 "container = widgets.MulticontainerWidget()\n",
154 "display(container)\n",
153 "display(container)\n",
155 "\n",
154 "\n",
156 "floatrange = widgets.FloatRangeWidget()\n",
155 "floatrange = widgets.FloatRangeWidget()\n",
157 "floatrange.parent=container\n",
156 "floatrange.parent=container\n",
158 "display(floatrange, view_name = \"FloatTextView\") # view_name can be set during display.\n",
157 "display(floatrange, view_name = \"FloatTextView\") # view_name can be set during display.\n",
159 "\n",
158 "\n",
160 "string = widgets.StringWidget()\n",
159 "string = widgets.StringWidget()\n",
161 "string.parent = container\n",
160 "string.parent = container\n",
162 "string.default_view_name = \"TextAreaView\" # Setting default_view_name still works.\n",
161 "string.default_view_name = \"TextAreaView\" # Setting default_view_name still works.\n",
163 "display(string)\n"
162 "display(string)\n"
164 ],
163 ],
165 "language": "python",
164 "language": "python",
166 "metadata": {},
165 "metadata": {},
167 "outputs": [],
166 "outputs": [],
168 "prompt_number": 6
167 "prompt_number": 6
169 },
168 },
170 {
169 {
171 "cell_type": "heading",
170 "cell_type": "heading",
172 "level": 1,
171 "level": 1,
173 "metadata": {},
172 "metadata": {},
174 "source": [
173 "source": [
175 "Visibility"
174 "Visibility"
176 ]
175 ]
177 },
176 },
178 {
177 {
179 "cell_type": "markdown",
178 "cell_type": "markdown",
180 "metadata": {},
179 "metadata": {},
181 "source": [
180 "source": [
182 "Sometimes it's necessary to hide/show widget views in place, without ruining the order that they have been displayed on the page. Using the `display` method, the views are always added to the end of their respective containers. Instead the `visibility` property of widgets can be used to hide/show widgets that have already been displayed (as seen below)."
181 "Sometimes it's necessary to hide/show widget views in place, without ruining the order that they have been displayed on the page. Using the `display` method, the views are always added to the end of their respective containers. Instead the `visibility` property of widgets can be used to hide/show widgets that have already been displayed (as seen below)."
183 ]
182 ]
184 },
183 },
185 {
184 {
186 "cell_type": "code",
185 "cell_type": "code",
187 "collapsed": false,
186 "collapsed": false,
188 "input": [
187 "input": [
189 "string = widgets.StringWidget(value=\"Hello World!\")\n",
188 "string = widgets.StringWidget(value=\"Hello World!\")\n",
190 "display(string, view_name=\"LabelView\") "
189 "display(string, view_name=\"HTMLView\") "
191 ],
190 ],
192 "language": "python",
191 "language": "python",
193 "metadata": {},
192 "metadata": {},
194 "outputs": [],
193 "outputs": [
195 "prompt_number": 7
194 {
195 "output_type": "stream",
196 "stream": "stdout",
197 "text": [
198 "['visible', '_css', '_children_attr', '_children_lists_attr', 'default_view_name', 'value', 'disabled', 'description']\n",
199 "[]\n",
200 "[]\n"
201 ]
202 }
203 ],
204 "prompt_number": 11
196 },
205 },
197 {
206 {
198 "cell_type": "code",
207 "cell_type": "code",
199 "collapsed": false,
208 "collapsed": false,
200 "input": [
209 "input": [
201 "string.visible=False"
210 "string.visible=False"
202 ],
211 ],
203 "language": "python",
212 "language": "python",
204 "metadata": {},
213 "metadata": {},
205 "outputs": [],
214 "outputs": [],
206 "prompt_number": 8
215 "prompt_number": 12
207 },
216 },
208 {
217 {
209 "cell_type": "code",
218 "cell_type": "code",
210 "collapsed": false,
219 "collapsed": false,
211 "input": [
220 "input": [
212 "string.visible=True"
221 "string.visible=True"
213 ],
222 ],
214 "language": "python",
223 "language": "python",
215 "metadata": {},
224 "metadata": {},
216 "outputs": [],
225 "outputs": [],
217 "prompt_number": 9
226 "prompt_number": 13
218 },
227 },
219 {
228 {
220 "cell_type": "markdown",
229 "cell_type": "markdown",
221 "metadata": {},
230 "metadata": {},
222 "source": [
231 "source": [
223 "In the example below, a form is rendered which conditionally displays widgets depending on the state of other widgets. Try toggling the student checkbox."
232 "In the example below, a form is rendered which conditionally displays widgets depending on the state of other widgets. Try toggling the student checkbox."
224 ]
233 ]
225 },
234 },
226 {
235 {
227 "cell_type": "code",
236 "cell_type": "code",
228 "collapsed": false,
237 "collapsed": false,
229 "input": [
238 "input": [
230 "form = widgets.ContainerWidget()\n",
239 "form = widgets.ContainerWidget()\n",
231 "first = widgets.StringWidget(description=\"First Name:\", parent=form)\n",
240 "first = widgets.StringWidget(description=\"First Name:\")\n",
232 "last = widgets.StringWidget(description=\"Last Name:\", parent=form)\n",
241 "last = widgets.StringWidget(description=\"Last Name:\")\n",
233 "\n",
242 "\n",
234 "student = widgets.BoolWidget(description=\"Student:\", value=False, parent=form)\n",
243 "student = widgets.BoolWidget(description=\"Student:\", value=False)\n",
235 "school_info = widgets.ContainerWidget(visible=False, parent=form)\n",
244 "form.children=[first, last, student]\n",
236 "school = widgets.StringWidget(description=\"School:\", parent=school_info)\n",
245 "display(form)"
237 "grade = widgets.IntRangeWidget(description=\"Grade:\", min=0, max=12, default_view_name='IntTextView', parent=school_info)\n",
246 ],
247 "language": "python",
248 "metadata": {},
249 "outputs": [],
250 "prompt_number": 2
251 },
252 {
253 "cell_type": "code",
254 "collapsed": false,
255 "input": [
256 "form = widgets.ContainerWidget()\n",
257 "first = widgets.StringWidget(description=\"First Name:\")\n",
258 "last = widgets.StringWidget(description=\"Last Name:\")\n",
259 "\n",
260 "student = widgets.BoolWidget(description=\"Student:\", value=False)\n",
261 "school_info = widgets.ContainerWidget(visible=False, children=[\n",
262 " widgets.StringWidget(description=\"School:\"),\n",
263 " widgets.IntRangeWidget(description=\"Grade:\", min=0, max=12, default_view_name='IntTextView')\n",
264 " ])\n",
238 "\n",
265 "\n",
239 "pet = widgets.StringWidget(description=\"Pet's Name:\", parent=form)\n",
266 "pet = widgets.StringWidget(description=\"Pet's Name:\")\n",
267 "form.children = [first, last, student, school_info, pet]\n",
240 "display(form)\n",
268 "display(form)\n",
241 "\n",
269 "\n",
242 "def on_student_toggle(name, value):\n",
270 "def on_student_toggle(name, value):\n",
271 " print value\n",
243 " if value:\n",
272 " if value:\n",
244 " school_info.visible = True\n",
273 " school_info.visible = True\n",
245 " else:\n",
274 " else:\n",
246 " school_info.visible = False\n",
275 " school_info.visible = False\n",
247 "student.on_trait_change(on_student_toggle, 'value')\n"
276 "student.on_trait_change(on_student_toggle, 'value')\n"
248 ],
277 ],
249 "language": "python",
278 "language": "python",
250 "metadata": {},
279 "metadata": {},
251 "outputs": [],
280 "outputs": [
252 "prompt_number": 10
281 {
282 "output_type": "stream",
283 "stream": "stdout",
284 "text": [
285 "True\n"
286 ]
287 },
288 {
289 "output_type": "stream",
290 "stream": "stdout",
291 "text": [
292 "False\n"
293 ]
294 },
295 {
296 "output_type": "stream",
297 "stream": "stdout",
298 "text": [
299 "True\n"
300 ]
301 },
302 {
303 "output_type": "stream",
304 "stream": "stdout",
305 "text": [
306 "False\n"
307 ]
308 },
309 {
310 "output_type": "stream",
311 "stream": "stdout",
312 "text": [
313 "True\n"
314 ]
315 },
316 {
317 "output_type": "stream",
318 "stream": "stdout",
319 "text": [
320 "False\n"
321 ]
322 },
323 {
324 "output_type": "stream",
325 "stream": "stdout",
326 "text": [
327 "True\n"
328 ]
329 }
330 ],
331 "prompt_number": 2
332 },
333 {
334 "cell_type": "code",
335 "collapsed": false,
336 "input": [],
337 "language": "python",
338 "metadata": {},
339 "outputs": []
253 }
340 }
254 ],
341 ],
255 "metadata": {}
342 "metadata": {}
256 }
343 }
257 ]
344 ]
258 } No newline at end of file
345 }
General Comments 0
You need to be logged in to leave comments. Login now