From ae55f08db3d4d5cd34626c7773b066e910818320 Mon Sep 17 00:00:00 2001 From: Sylvain Corlay Date: Tue, 10 Feb 2015 21:36:50 -0500 Subject: [PATCH 01/24] font awesome icon --- IPython/html/static/widgets/js/widget_bool.js | 7 ++++--- IPython/html/static/widgets/js/widget_button.js | 8 ++++---- IPython/html/tests/widgets/widget_bool.js | 2 +- IPython/html/tests/widgets/widget_button.js | 2 +- IPython/html/widgets/widget_bool.py | 5 +++++ IPython/html/widgets/widget_button.py | 14 ++++++++++++-- 6 files changed, 27 insertions(+), 11 deletions(-) diff --git a/IPython/html/static/widgets/js/widget_bool.js b/IPython/html/static/widgets/js/widget_bool.js index 02340543da2..27e49287500 100644 --- a/IPython/html/static/widgets/js/widget_bool.js +++ b/IPython/html/static/widgets/js/widget_bool.js @@ -129,10 +129,11 @@ define([ var description = this.model.get('description'); this.$el.attr("title", this.model.get("tooltip")); - if (description.trim().length === 0) { + this.$el.text(description); + var icon = this.model.get("icon"); + $('').prependTo(this.$el).addClass(icon); + if (description.trim().length === 0 && icon.trim().length ===0) { this.$el.html(" "); // Preserve button height - } else { - this.$el.text(description); } } return ToggleButtonView.__super__.update.apply(this); diff --git a/IPython/html/static/widgets/js/widget_button.js b/IPython/html/static/widgets/js/widget_button.js index c4765818fb5..cc55f47e1bb 100644 --- a/IPython/html/static/widgets/js/widget_button.js +++ b/IPython/html/static/widgets/js/widget_button.js @@ -32,12 +32,12 @@ define([ */ var description = this.model.get('description'); this.$el.attr("title", this.model.get("tooltip")); - if (description.length === 0) { + this.$el.text(description); + var icon = this.model.get("icon"); + $('').prependTo(this.$el).addClass(icon); + if (description.trim().length === 0 && icon.trim().length ===0) { this.$el.html(" "); // Preserve button height - } else { - this.$el.text(description); } - if (this.model.get('disabled')) { this.$el.attr('disabled','disabled'); } else { diff --git a/IPython/html/tests/widgets/widget_bool.js b/IPython/html/tests/widgets/widget_bool.js index 1d3ec62b273..556031c9b6b 100644 --- a/IPython/html/tests/widgets/widget_bool.js +++ b/IPython/html/tests/widgets/widget_bool.js @@ -49,7 +49,7 @@ casper.notebook_test(function () { 'Toggle button exists.'); this.test.assert(this.cell_element_function(bool_index, - widget_togglebutton_selector, 'html')=="Title", + widget_togglebutton_selector, 'html')=='Title', 'Toggle button labeled correctly.'); this.test.assert(this.cell_element_function(bool_index, diff --git a/IPython/html/tests/widgets/widget_button.js b/IPython/html/tests/widgets/widget_button.js index e98da48d09a..97ea5ee7718 100644 --- a/IPython/html/tests/widgets/widget_button.js +++ b/IPython/html/tests/widgets/widget_button.js @@ -29,7 +29,7 @@ casper.notebook_test(function () { 'Widget button exists.'); this.test.assert(this.cell_element_function(button_index, - widget_button_selector, 'html')=='Title', + widget_button_selector, 'html')=='Title', 'Set button description.'); this.cell_element_function(button_index, diff --git a/IPython/html/widgets/widget_bool.py b/IPython/html/widgets/widget_bool.py index a6f1ff55f44..976c721fd62 100644 --- a/IPython/html/widgets/widget_bool.py +++ b/IPython/html/widgets/widget_bool.py @@ -55,10 +55,15 @@ class ToggleButton(_Bool): value of the toggle button: True-pressed, False-unpressed description : str description displayed next to the button + tooltip: str + tooltip caption of the toggle button + icon: str + font-awesome icon name """ _view_name = Unicode('ToggleButtonView', sync=True) tooltip = Unicode(help="Tooltip caption of the toggle button.", sync=True) + icon = Unicode('', help= "Font-awesome icon.", sync=True) button_style = CaselessStrEnum( values=['primary', 'success', 'info', 'warning', 'danger', ''], diff --git a/IPython/html/widgets/widget_button.py b/IPython/html/widgets/widget_button.py index e91f3d2b735..4d5549c0acd 100644 --- a/IPython/html/widgets/widget_button.py +++ b/IPython/html/widgets/widget_button.py @@ -24,15 +24,25 @@ @register('IPython.Button') class Button(DOMWidget): """Button widget. + This widget has an `on_click` method that allows you to listen for the + user clicking on the button. The click event itself is stateless. - This widget has an `on_click` method that allows you to listen for the - user clicking on the button. The click event itself is stateless.""" + Parameters + ---------- + description : str + description displayed next to the button + tooltip: str + tooltip caption of the toggle button + icon: str + font-awesome icon name + """ _view_name = Unicode('ButtonView', sync=True) # Keys description = Unicode('', help="Button label.", sync=True) tooltip = Unicode(help="Tooltip caption of the button.", sync=True) disabled = Bool(False, help="Enable or disable user changes.", sync=True) + icon = Unicode('', help= "Font-awesome icon.", sync=True) button_style = CaselessStrEnum( values=['primary', 'success', 'info', 'warning', 'danger', ''], From 834cd9c43db06a777f4bb47f19eaf7fa1a41b8e8 Mon Sep 17 00:00:00 2001 From: Jason Grout Date: Fri, 13 Feb 2015 22:41:16 +0000 Subject: [PATCH 02/24] Fix race condition in javascript kernel message processing Because the binary messages are now deserialized using the asynchronous FileReader API, we need to have some way to force the messages to still be processed in the order they are received. This patch implements a simple processing queue using promises. --- .../html/static/services/kernels/kernel.js | 6 ++++- .../html/static/services/kernels/serialize.js | 25 +++++++++++-------- 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/IPython/html/static/services/kernels/kernel.js b/IPython/html/static/services/kernels/kernel.js index f414507e75f..42478ae81af 100644 --- a/IPython/html/static/services/kernels/kernel.js +++ b/IPython/html/static/services/kernels/kernel.js @@ -41,6 +41,7 @@ define([ this.username = "username"; this.session_id = utils.uuid(); this._msg_callbacks = {}; + this._msg_queue = Promise.resolve(); this.info_reply = {}; // kernel_info_reply stored here after starting if (typeof(WebSocket) !== 'undefined') { @@ -854,7 +855,10 @@ define([ }; Kernel.prototype._handle_ws_message = function (e) { - serialize.deserialize(e.data, $.proxy(this._finish_ws_message, this)); + this._msg_queue = this._msg_queue.then(function() { + return serialize.deserialize(e.data); + }).then($.proxy(this._finish_ws_message, this)) + .catch(utils.reject("Couldn't process kernel message", true)); }; Kernel.prototype._finish_ws_message = function (msg) { diff --git a/IPython/html/static/services/kernels/serialize.js b/IPython/html/static/services/kernels/serialize.js index 4da4c9a01ca..9fb8d28f324 100644 --- a/IPython/html/static/services/kernels/serialize.js +++ b/IPython/html/static/services/kernels/serialize.js @@ -30,7 +30,7 @@ define([ return msg; }; - var _deserialize_binary = function(data, callback) { + var _deserialize_binary = function(data) { /** * deserialize the binary message format * callback will be called with a message whose buffers attribute @@ -39,28 +39,31 @@ define([ if (data instanceof Blob) { // data is Blob, have to deserialize from ArrayBuffer in reader callback var reader = new FileReader(); - reader.onload = function () { - var msg = _deserialize_array_buffer(this.result); - callback(msg); - }; + var promise = new Promise(function(resolve, reject) { + reader.onload = function () { + var msg = _deserialize_array_buffer(this.result); + resolve(msg); + }; + }); reader.readAsArrayBuffer(data); + return promise; } else { // data is ArrayBuffer, can deserialize directly var msg = _deserialize_array_buffer(data); - callback(msg); + return msg; } }; - var deserialize = function (data, callback) { + var deserialize = function (data) { /** - * deserialize a message and pass the unpacked message object to callback + * deserialize a message and return a promise for the unpacked message */ if (typeof data === "string") { // text JSON message - callback(JSON.parse(data)); + return Promise.resolve(JSON.parse(data)); } else { // binary message - _deserialize_binary(data, callback); + return Promise.resolve(_deserialize_binary(data)); } }; @@ -117,4 +120,4 @@ define([ serialize: serialize }; return exports; -}); \ No newline at end of file +}); From eeaea1de5b06195b4de46fe1a7342ebd3e580c97 Mon Sep 17 00:00:00 2001 From: Sylvain Corlay Date: Sun, 15 Feb 2015 01:19:56 -0500 Subject: [PATCH 03/24] Tooltips in togglebuttons --- IPython/html/static/widgets/js/widget_selection.js | 4 +++- IPython/html/widgets/widget_selection.py | 9 ++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/IPython/html/static/widgets/js/widget_selection.js b/IPython/html/static/widgets/js/widget_selection.js index c4878341e69..f2e1bb48dd7 100644 --- a/IPython/html/static/widgets/js/widget_selection.js +++ b/IPython/html/static/widgets/js/widget_selection.js @@ -325,6 +325,7 @@ define([ .html(item_html) .appendTo(that.$buttongroup) .attr('data-value', encodeURIComponent(item)) + .attr('data-toggle', 'tooltip') .attr('value', item) .on('click', $.proxy(that.handle_click, that)); that.update_style_traits($item_element); @@ -334,7 +335,8 @@ define([ } else { $item_element.removeClass('active'); } - $item_element.prop('disabled', disabled); + $item_element.prop('disabled', disabled); + $item_element.attr('title', that.model.get('tooltips')[index]); }); // Remove items that no longer exist. diff --git a/IPython/html/widgets/widget_selection.py b/IPython/html/widgets/widget_selection.py index 228498b1c17..bdd649bf307 100644 --- a/IPython/html/widgets/widget_selection.py +++ b/IPython/html/widgets/widget_selection.py @@ -19,7 +19,7 @@ from .widget import DOMWidget, register from IPython.utils.traitlets import ( - Unicode, Bool, Any, Dict, TraitError, CaselessStrEnum, Tuple + Unicode, Bool, Any, Dict, TraitError, CaselessStrEnum, Tuple, List ) from IPython.utils.py3compat import unicode_type from IPython.utils.warn import DeprecatedClass @@ -32,6 +32,12 @@ class _Selection(DOMWidget): ``options`` can be specified as a list or dict. If given as a list, it will be transformed to a dict of the form ``{str(value):value}``. + + When programmatically setting the value, a reverse lookup is performed + among the options to set the value of ``selected_label`` accordingly. The + reverse lookup uses the equality operator by default, but an other + predicate may be provided via the ``equals`` argument. For example, when + dealing with numpy arrays, one may set equals=np.array_equal. """ value = Any(help="Selected value") @@ -194,6 +200,7 @@ class ToggleButtons(_Selection): """Group of toggle buttons that represent an enumeration. Only one toggle button can be toggled at any point in time.""" _view_name = Unicode('ToggleButtonsView', sync=True) + tooltips = List(Unicode(), sync=True) button_style = CaselessStrEnum( values=['primary', 'success', 'info', 'warning', 'danger', ''], From 07ec4b54fe3180279b7a3c39d0f4581f77357406 Mon Sep 17 00:00:00 2001 From: Sylvain Corlay Date: Sun, 15 Feb 2015 02:20:58 -0500 Subject: [PATCH 04/24] Font-awesome icons for toggle buttons --- IPython/html/static/widgets/js/widget_selection.js | 12 ++++++++++-- IPython/html/widgets/widget_selection.py | 1 + 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/IPython/html/static/widgets/js/widget_selection.js b/IPython/html/static/widgets/js/widget_selection.js index f2e1bb48dd7..1eb75509f0f 100644 --- a/IPython/html/static/widgets/js/widget_selection.js +++ b/IPython/html/static/widgets/js/widget_selection.js @@ -307,17 +307,21 @@ define([ if (options === undefined || options.updated_view != this) { // Add missing items to the DOM. var items = this.model.get('_options_labels'); + var icons = this.model.get('icons'); + var previous_icons = this.model.previous('icons') || []; var disabled = this.model.get('disabled'); var that = this; var item_html; _.each(items, function(item, index) { - if (item.trim().length === 0) { + if (item.trim().length === 0 && (!icons[index] || + icons[index].trim().length === 0)) { item_html = " "; } else { item_html = utils.escape_html(item); } var item_query = '[data-value="' + encodeURIComponent(item) + '"]'; var $item_element = that.$buttongroup.find(item_query); + var $icon_element = $item_element.find('.fa'); if (!$item_element.length) { $item_element = $('