diff --git a/IPython/html/services/kernels/handlers.py b/IPython/html/services/kernels/handlers.py index 3d4337f..b51861e 100644 --- a/IPython/html/services/kernels/handlers.py +++ b/IPython/html/services/kernels/handlers.py @@ -84,6 +84,9 @@ class KernelActionHandler(IPythonHandler): class ZMQChannelHandler(AuthenticatedZMQStreamHandler): + def __repr__(self): + return "%s(%s)" % (self.__class__.__name__, getattr(self, 'kernel_id', 'uninitialized')) + def create_stream(self): km = self.kernel_manager meth = getattr(km, 'connect_%s' % self.channel) @@ -145,6 +148,12 @@ class ZMQChannelHandler(AuthenticatedZMQStreamHandler): self.zmq_stream.on_recv(self._on_zmq_reply) def on_message(self, msg): + if self.zmq_stream is None: + return + elif self.zmq_stream.closed(): + self.log.info("%s closed, closing websocket.", self) + self.close() + return msg = json.loads(msg) self.session.send(self.zmq_stream, msg) diff --git a/IPython/html/static/base/less/flexbox.less b/IPython/html/static/base/less/flexbox.less index 4623833..62eaad2 100644 --- a/IPython/html/static/base/less/flexbox.less +++ b/IPython/html/static/base/less/flexbox.less @@ -184,6 +184,30 @@ Browsers not listed, including Safari, are supported via the styling under the justify-content: center; } +.hbox.baseline, +.vbox.baseline, +.baseline { + /* Old browsers */ + -webkit-box-pack: baseline; + -moz-box-pack: baseline; + box-pack: baseline; + + /* Modern browsers */ + justify-content: baseline; +} + +.hbox.stretch, +.vbox.stretch, +.stretch { + /* Old browsers */ + -webkit-box-pack: stretch; + -moz-box-pack: stretch; + box-pack: stretch; + + /* Modern browsers */ + justify-content: stretch; +} + .hbox.align-start, .vbox.align-start, .align-start { @@ -219,3 +243,27 @@ Browsers not listed, including Safari, are supported via the styling under the /* Modern browsers */ align-items: center; } + +.hbox.align-baseline, +.vbox.align-baseline, +.align-baseline { + /* Old browsers */ + -webkit-box-align: baseline; + -moz-box-align: baseline; + box-align: baseline; + + /* Modern browsers */ + align-items: baseline; +} + +.hbox.align-stretch, +.vbox.align-stretch, +.align-stretch { + /* Old browsers */ + -webkit-box-align: stretch; + -moz-box-align: stretch; + box-align: stretch; + + /* Modern browsers */ + align-items: stretch; +} diff --git a/IPython/html/static/notebook/js/kernelselector.js b/IPython/html/static/notebook/js/kernelselector.js index 764db72..ce9b919 100644 --- a/IPython/html/static/notebook/js/kernelselector.js +++ b/IPython/html/static/notebook/js/kernelselector.js @@ -54,9 +54,19 @@ define([ return; } var ks = this.kernelspecs[kernel_name]; + try { + this.notebook.start_session(kernel_name); + } catch (e) { + if (e.name === 'SessionAlreadyStarting') { + console.log("Cannot change kernel while waiting for pending session start."); + } else { + // unhandled error + throw e; + } + // only trigger spec_changed if change was successful + return; + } this.events.trigger('spec_changed.Kernel', ks); - this.notebook.session.delete(); - this.notebook.start_session(kernel_name); }; KernelSelector.prototype.bind_events = function() { diff --git a/IPython/html/static/notebook/js/menubar.js b/IPython/html/static/notebook/js/menubar.js index a6f0b28..67e0d5b 100644 --- a/IPython/html/static/notebook/js/menubar.js +++ b/IPython/html/static/notebook/js/menubar.js @@ -157,12 +157,13 @@ define([ } }); this.element.find('#kill_and_exit').click(function () { - that.notebook.session.delete(); - setTimeout(function(){ + var close_window = function () { // allow closing of new tabs in Chromium, impossible in FF window.open('', '_self', ''); window.close(); - }, 500); + }; + // finish with close on success or failure + that.notebook.session.delete(close_window, close_window); }); // Edit this.element.find('#cut_cell').click(function () { diff --git a/IPython/html/static/notebook/js/notebook.js b/IPython/html/static/notebook/js/notebook.js index e22fd51..8aaf8b4 100644 --- a/IPython/html/static/notebook/js/notebook.js +++ b/IPython/html/static/notebook/js/notebook.js @@ -62,6 +62,7 @@ define([ this.save_widget = options.save_widget; this.tooltip = new tooltip.Tooltip(this.events); this.ws_url = options.ws_url; + this._session_starting = false; // default_kernel_name is a temporary measure while we implement proper // kernel selection and delayed start. Do not rely on it. this.default_kernel_name = 'python'; @@ -1525,9 +1526,38 @@ define([ * @method start_session */ Notebook.prototype.start_session = function (kernel_name) { + var that = this; if (kernel_name === undefined) { kernel_name = this.default_kernel_name; } + if (this._session_starting) { + throw new session.SessionAlreadyStarting(); + } + this._session_starting = true; + + if (this.session !== null) { + var s = this.session; + this.session = null; + // need to start the new session in a callback after delete, + // because javascript does not guarantee the ordering of AJAX requests (?!) + s.delete(function () { + // on successful delete, start new session + that._session_starting = false; + that.start_session(kernel_name); + }, function (jqXHR, status, error) { + // log the failed delete, but still create a new session + // 404 just means it was already deleted by someone else, + // but other errors are possible. + utils.log_ajax_error(jqXHR, status, error); + that._session_starting = false; + that.start_session(kernel_name); + } + ); + return; + } + + + this.session = new session.Session({ base_url: this.base_url, ws_url: this.ws_url, @@ -1539,7 +1569,10 @@ define([ kernel_name: kernel_name, notebook: this}); - this.session.start($.proxy(this._session_started, this)); + this.session.start( + $.proxy(this._session_started, this), + $.proxy(this._session_start_failed, this) + ); }; @@ -1548,7 +1581,8 @@ define([ * comm manager to the widget manager * */ - Notebook.prototype._session_started = function(){ + Notebook.prototype._session_started = function (){ + this._session_starting = false; this.kernel = this.session.kernel; var ncells = this.ncells(); for (var i=0; i') .addClass('widget-modal-title') .html(" ") - .appendTo(this.$title_bar); - this.$body = $('
') + .appendTo(this.$title_bar); + this.$box = $('
') .addClass('modal-body') .addClass('widget-modal-body') - .addClass('widget-container') + .addClass('widget-box') .addClass('vbox') .appendTo(this.$window); @@ -149,7 +195,7 @@ define([ this.$window.draggable({handle: '.popover-title', snap: '#notebook, .modal', snapMode: 'both'}); this.$window.resizable(); this.$window.on('resize', function(){ - that.$body.outerHeight(that.$window.innerHeight() - that.$title_bar.outerHeight()); + that.$box.outerHeight(that.$window.innerHeight() - that.$title_bar.outerHeight()); }); this._shown_once = false; @@ -203,29 +249,6 @@ define([ this.$window.css('z-index', max_zindex); }, - update_children: function(old_list, new_list) { - // Called when the children list is modified. - this.do_diff(old_list, new_list, - $.proxy(this.remove_child_model, this), - $.proxy(this.add_child_model, this)); - }, - - remove_child_model: function(model) { - // Called when a child is removed from children list. - this.pop_child_view(model).remove(); - }, - - add_child_model: function(model) { - // Called when a child is added to children list. - var view = this.create_child_view(model); - this.$body.append(view.$el); - - // Trigger the displayed event of the child view. - this.after_displayed(function() { - view.trigger('displayed'); - }); - }, - update: function(){ // Update the contents of this view // @@ -277,7 +300,8 @@ define([ }); return { - 'ContainerView': ContainerView, + 'BoxView': BoxView, 'PopupView': PopupView, + 'FlexBoxView': FlexBoxView, }; }); diff --git a/IPython/html/static/widgets/less/widgets.less b/IPython/html/static/widgets/less/widgets.less index 28996f6..c26aa27 100644 --- a/IPython/html/static/widgets/less/widgets.less +++ b/IPython/html/static/widgets/less/widgets.less @@ -226,7 +226,7 @@ margin : 5px; .start(); - .widget-container(); + .widget-box(); } .widget-hbox { @@ -258,7 +258,7 @@ } .widget-modal { - /* ContainerWidget - ModalView */ + /* Box - ModalView */ overflow : hidden; position : absolute !important; top : 0px; @@ -267,12 +267,12 @@ } .widget-modal-body { - /* ContainerWidget - ModalView Body */ + /* Box - ModalView Body */ max-height: none !important; } -.widget-container { - /* ContainerWidget */ +.widget-box { + /* Box */ .border-box-sizing(); .align-start(); } diff --git a/IPython/html/tests/services/kernel.js b/IPython/html/tests/services/kernel.js index bcbda9e..4ff1eee 100644 --- a/IPython/html/tests/services/kernel.js +++ b/IPython/html/tests/services/kernel.js @@ -1,12 +1,12 @@ // -// Miscellaneous javascript tests +// Kernel tests // casper.notebook_test(function () { this.evaluate(function () { IPython.notebook.kernel.kernel_info( function(msg){ - IPython._kernel_info_response = msg; + IPython._kernel_info_response = msg; }) }); @@ -24,5 +24,41 @@ casper.notebook_test(function () { this.test.assertTrue( kernel_info_response.msg_type === 'kernel_info_reply', 'Kernel info request return kernel_info_reply'); this.test.assertTrue( kernel_info_response.content !== undefined, 'Kernel_info_reply is not undefined'); }); - + + this.thenEvaluate(function () { + var kernel = IPython.notebook.session.kernel; + IPython._channels = [ + kernel.shell_channel, + kernel.iopub_channel, + kernel.stdin_channel + ]; + kernel.kill(); + }); + + this.waitFor(function () { + return this.evaluate(function(){ + for (var i=0; i < IPython._channels.length; i++) { + var ws = IPython._channels[i]; + if (ws.readyState !== ws.CLOSED) { + return false; + } + } + return true; + }); + }); + + this.then(function () { + var states = this.evaluate(function() { + var states = []; + for (var i = 0; i < IPython._channels.length; i++) { + states.push(IPython._channels[i].readyState); + } + return states; + }); + + for (var i = 0; i < states.length; i++) { + this.test.assertEquals(states[i], WebSocket.CLOSED, + "Kernel.kill closes websockets[" + i + "]"); + } + }); }); diff --git a/IPython/html/tests/services/session.js b/IPython/html/tests/services/session.js new file mode 100644 index 0000000..af066cf --- /dev/null +++ b/IPython/html/tests/services/session.js @@ -0,0 +1,43 @@ + +// +// Tests for the Session object +// + +casper.notebook_test(function () { + this.evaluate(function () { + var kernel = IPython.notebook.session.kernel; + IPython._channels = [ + kernel.shell_channel, + kernel.iopub_channel, + kernel.stdin_channel + ]; + IPython.notebook.session.delete(); + }); + + this.waitFor(function () { + return this.evaluate(function(){ + for (var i=0; i < IPython._channels.length; i++) { + var ws = IPython._channels[i]; + if (ws.readyState !== ws.CLOSED) { + return false; + } + } + return true; + }); + }); + + this.then(function () { + var states = this.evaluate(function() { + var states = []; + for (var i = 0; i < IPython._channels.length; i++) { + states.push(IPython._channels[i].readyState); + } + return states; + }); + + for (var i = 0; i < states.length; i++) { + this.test.assertEquals(states[i], WebSocket.CLOSED, + "Session.delete closes websockets[" + i + "]"); + } + }); +}); diff --git a/IPython/html/tests/widgets/widget.js b/IPython/html/tests/widgets/widget.js index 787b0f2..005d75e 100644 --- a/IPython/html/tests/widgets/widget.js +++ b/IPython/html/tests/widgets/widget.js @@ -147,7 +147,7 @@ casper.notebook_test(function () { var textbox = {}; throttle_index = this.append_cell( 'import time\n' + - 'textbox = widgets.TextWidget()\n' + + 'textbox = widgets.Text()\n' + 'display(textbox)\n' + 'textbox.add_class("my-throttle-textbox", selector="input")\n' + 'def handle_change(name, old, new):\n' + diff --git a/IPython/html/tests/widgets/widget_bool.js b/IPython/html/tests/widgets/widget_bool.js index 4fd1007..0fe80b9 100644 --- a/IPython/html/tests/widgets/widget_bool.js +++ b/IPython/html/tests/widgets/widget_bool.js @@ -7,8 +7,8 @@ casper.notebook_test(function () { this.execute_cell_then(index); var bool_index = this.append_cell( - 'bool_widgets = [widgets.CheckboxWidget(description="Title", value=True),\n' + - ' widgets.ToggleButtonWidget(description="Title", value=True)]\n' + + 'bool_widgets = [widgets.Checkbox(description="Title", value=True),\n' + + ' widgets.ToggleButton(description="Title", value=True)]\n' + 'display(bool_widgets[0])\n' + 'display(bool_widgets[1])\n' + 'print("Success")'); diff --git a/IPython/html/tests/widgets/widget_container.js b/IPython/html/tests/widgets/widget_box.js similarity index 93% rename from IPython/html/tests/widgets/widget_container.js rename to IPython/html/tests/widgets/widget_box.js index e3faa75..c1b422a 100644 --- a/IPython/html/tests/widgets/widget_container.js +++ b/IPython/html/tests/widgets/widget_box.js @@ -7,8 +7,8 @@ casper.notebook_test(function () { this.execute_cell_then(index); var container_index = this.append_cell( - 'container = widgets.ContainerWidget()\n' + - 'button = widgets.ButtonWidget()\n'+ + 'container = widgets.Box()\n' + + 'button = widgets.Button()\n'+ 'container.children = [button]\n'+ 'display(container)\n'+ 'container.add_class("my-test-class")\n'+ @@ -23,7 +23,7 @@ casper.notebook_test(function () { 'Widget subarea exists.'); this.test.assert(this.cell_element_exists(index, - '.widget-area .widget-subarea .widget-container'), + '.widget-area .widget-subarea .widget-box'), 'Widget container exists.'); this.test.assert(this.cell_element_exists(index, @@ -70,7 +70,7 @@ casper.notebook_test(function () { 'Display container child executed with correct output.'); this.test.assert(! this.cell_element_exists(index, - '.widget-area .widget-subarea .widget-container'), + '.widget-area .widget-subarea .widget-box'), 'Parent container not displayed.'); this.test.assert(this.cell_element_exists(index, diff --git a/IPython/html/tests/widgets/widget_button.js b/IPython/html/tests/widgets/widget_button.js index ae0aec6..304f64d 100644 --- a/IPython/html/tests/widgets/widget_button.js +++ b/IPython/html/tests/widgets/widget_button.js @@ -7,7 +7,7 @@ casper.notebook_test(function () { this.execute_cell_then(index); var button_index = this.append_cell( - 'button = widgets.ButtonWidget(description="Title")\n' + + 'button = widgets.Button(description="Title")\n' + 'display(button)\n' + 'print("Success")\n' + 'def handle_click(sender):\n' + diff --git a/IPython/html/tests/widgets/widget_float.js b/IPython/html/tests/widgets/widget_float.js index a26c42d..a2f4e74 100644 --- a/IPython/html/tests/widgets/widget_float.js +++ b/IPython/html/tests/widgets/widget_float.js @@ -9,7 +9,7 @@ casper.notebook_test(function () { var float_text = {}; float_text.query = '.widget-area .widget-subarea .widget-hbox-single .my-second-float-text'; float_text.index = this.append_cell( - 'float_widget = widgets.FloatTextWidget()\n' + + 'float_widget = widgets.FloatText()\n' + 'display(float_widget)\n' + 'float_widget.add_class("my-second-float-text", selector="input")\n' + 'print(float_widget.model_id)\n'); @@ -59,8 +59,8 @@ casper.notebook_test(function () { var slider = {}; slider.query = '.widget-area .widget-subarea .widget-hbox-single .slider'; slider.index = this.append_cell( - 'floatrange = [widgets.BoundedFloatTextWidget(), \n' + - ' widgets.FloatSliderWidget()]\n' + + 'floatrange = [widgets.BoundedFloatText(), \n' + + ' widgets.FloatSlider()]\n' + '[display(floatrange[i]) for i in range(2)]\n' + 'print("Success")\n'); this.execute_cell_then(slider.index, function(index){ diff --git a/IPython/html/tests/widgets/widget_image.js b/IPython/html/tests/widgets/widget_image.js index 04b1451..c858c68 100644 --- a/IPython/html/tests/widgets/widget_image.js +++ b/IPython/html/tests/widgets/widget_image.js @@ -18,7 +18,7 @@ casper.notebook_test(function () { var image_index = this.append_cell( 'import base64\n' + 'data = base64.b64decode("' + test_jpg + '")\n' + - 'image = widgets.ImageWidget()\n' + + 'image = widgets.Image()\n' + 'image.format = "jpeg"\n' + 'image.value = data\n' + 'image.width = "50px"\n' + diff --git a/IPython/html/tests/widgets/widget_int.js b/IPython/html/tests/widgets/widget_int.js index 4393664..a9bc43d 100644 --- a/IPython/html/tests/widgets/widget_int.js +++ b/IPython/html/tests/widgets/widget_int.js @@ -9,7 +9,7 @@ casper.notebook_test(function () { var int_text = {}; int_text.query = '.widget-area .widget-subarea .widget-hbox-single .my-second-int-text'; int_text.index = this.append_cell( - 'int_widget = widgets.IntTextWidget()\n' + + 'int_widget = widgets.IntText()\n' + 'display(int_widget)\n' + 'int_widget.add_class("my-second-int-text", selector="input")\n' + 'print(int_widget.model_id)\n'); diff --git a/IPython/html/tests/widgets/widget_selection.js b/IPython/html/tests/widgets/widget_selection.js index 25d5b86..dac47e0 100644 --- a/IPython/html/tests/widgets/widget_selection.js +++ b/IPython/html/tests/widgets/widget_selection.js @@ -44,10 +44,10 @@ casper.notebook_test(function () { //values=["' + selection_values + '"[i] for i in range(4)] selection_index = this.append_cell( 'values=["' + selection_values + '"[i] for i in range(4)]\n' + - 'selection = [widgets.DropdownWidget(values=values),\n' + - ' widgets.ToggleButtonsWidget(values=values),\n' + - ' widgets.RadioButtonsWidget(values=values),\n' + - ' widgets.SelectWidget(values=values)]\n' + + 'selection = [widgets.Dropdown(values=values),\n' + + ' widgets.ToggleButtons(values=values),\n' + + ' widgets.RadioButtons(values=values),\n' + + ' widgets.Select(values=values)]\n' + '[display(selection[i]) for i in range(4)]\n' + 'for widget in selection:\n' + ' def handle_change(name,old,new):\n' + diff --git a/IPython/html/tests/widgets/widget_selectioncontainer.js b/IPython/html/tests/widgets/widget_selectioncontainer.js index 1cc9526..57e9bd1 100644 --- a/IPython/html/tests/widgets/widget_selectioncontainer.js +++ b/IPython/html/tests/widgets/widget_selectioncontainer.js @@ -9,10 +9,10 @@ casper.notebook_test(function () { // Test tab view var multicontainer1_query = '.widget-area .widget-subarea div div.nav-tabs'; var multicontainer1_index = this.append_cell( - 'multicontainer = widgets.TabWidget()\n' + - 'page1 = widgets.TextWidget()\n' + - 'page2 = widgets.TextWidget()\n' + - 'page3 = widgets.TextWidget()\n' + + 'multicontainer = widgets.Tab()\n' + + 'page1 = widgets.Text()\n' + + 'page2 = widgets.Text()\n' + + 'page3 = widgets.Text()\n' + 'multicontainer.children = [page1, page2, page3]\n' + 'display(multicontainer)\n' + 'multicontainer.selected_index = 0\n' + @@ -64,10 +64,10 @@ casper.notebook_test(function () { // Test accordion view var multicontainer2_query = '.widget-area .widget-subarea .panel-group'; var multicontainer2_index = this.append_cell( - 'multicontainer = widgets.AccordionWidget()\n' + - 'page1 = widgets.TextWidget()\n' + - 'page2 = widgets.TextWidget()\n' + - 'page3 = widgets.TextWidget()\n' + + 'multicontainer = widgets.Accordion()\n' + + 'page1 = widgets.Text()\n' + + 'page2 = widgets.Text()\n' + + 'page3 = widgets.Text()\n' + 'multicontainer.children = [page1, page2, page3]\n' + 'multicontainer.set_title(2, "good")\n' + 'display(multicontainer)\n' + diff --git a/IPython/html/tests/widgets/widget_string.js b/IPython/html/tests/widgets/widget_string.js index 1288cbd..8e9acee 100644 --- a/IPython/html/tests/widgets/widget_string.js +++ b/IPython/html/tests/widgets/widget_string.js @@ -7,10 +7,10 @@ casper.notebook_test(function () { this.execute_cell_then(index); var string_index = this.append_cell( - 'string_widget = [widgets.TextWidget(value = "xyz", placeholder = "abc"),\n' + - ' widgets.TextareaWidget(value = "xyz", placeholder = "def"),\n' + - ' widgets.HTMLWidget(value = "xyz"),\n' + - ' widgets.LatexWidget(value = "$\\\\LaTeX{}$")]\n' + + 'string_widget = [widgets.Text(value = "xyz", placeholder = "abc"),\n' + + ' widgets.Textarea(value = "xyz", placeholder = "def"),\n' + + ' widgets.HTML(value = "xyz"),\n' + + ' widgets.Latex(value = "$\\\\LaTeX{}$")]\n' + '[display(widget) for widget in string_widget]\n'+ 'print("Success")'); this.execute_cell_then(string_index, function(index){ diff --git a/IPython/html/widgets/__init__.py b/IPython/html/widgets/__init__.py index c0b3d2a..d3cdb11 100644 --- a/IPython/html/widgets/__init__.py +++ b/IPython/html/widgets/__init__.py @@ -1,12 +1,23 @@ from .widget import Widget, DOMWidget, CallbackDispatcher +from .widget_bool import Checkbox, ToggleButton +from .widget_button import Button +from .widget_box import Box, Popup, FlexBox, HBox, VBox +from .widget_float import FloatText, BoundedFloatText, FloatSlider, FloatProgress, FloatRangeSlider +from .widget_image import Image +from .widget_int import IntText, BoundedIntText, IntSlider, IntProgress, IntRangeSlider +from .widget_selection import RadioButtons, ToggleButtons, Dropdown, Select +from .widget_selectioncontainer import Tab, Accordion +from .widget_string import HTML, Latex, Text, Textarea +from .interaction import interact, interactive, fixed + +# Deprecated classes from .widget_bool import CheckboxWidget, ToggleButtonWidget from .widget_button import ButtonWidget -from .widget_container import ContainerWidget, PopupWidget -from .widget_float import FloatTextWidget, BoundedFloatTextWidget, FloatSliderWidget, FloatProgressWidget, FloatRangeSliderWidget +from .widget_box import ContainerWidget, PopupWidget +from .widget_float import FloatTextWidget, BoundedFloatTextWidget, FloatSliderWidget, FloatProgressWidget from .widget_image import ImageWidget -from .widget_int import IntTextWidget, BoundedIntTextWidget, IntSliderWidget, IntProgressWidget, IntRangeSliderWidget +from .widget_int import IntTextWidget, BoundedIntTextWidget, IntSliderWidget, IntProgressWidget from .widget_selection import RadioButtonsWidget, ToggleButtonsWidget, DropdownWidget, SelectWidget from .widget_selectioncontainer import TabWidget, AccordionWidget from .widget_string import HTMLWidget, LatexWidget, TextWidget, TextareaWidget -from .interaction import interact, interactive, fixed diff --git a/IPython/html/widgets/interaction.py b/IPython/html/widgets/interaction.py index 1b59de8..86c7125 100644 --- a/IPython/html/widgets/interaction.py +++ b/IPython/html/widgets/interaction.py @@ -21,9 +21,9 @@ except ImportError: from inspect import getcallargs from IPython.core.getipython import get_ipython -from IPython.html.widgets import (Widget, TextWidget, - FloatSliderWidget, IntSliderWidget, CheckboxWidget, DropdownWidget, - ContainerWidget, DOMWidget) +from IPython.html.widgets import (Widget, Text, + FloatSlider, IntSlider, Checkbox, Dropdown, + Box, DOMWidget) from IPython.display import display, clear_output from IPython.utils.py3compat import string_types, unicode_type from IPython.utils.traitlets import HasTraits, Any, Unicode @@ -70,17 +70,17 @@ def _get_min_max_value(min, max, value=None, step=None): def _widget_abbrev_single_value(o): """Make widgets from single values, which can be used as parameter defaults.""" if isinstance(o, string_types): - return TextWidget(value=unicode_type(o)) + return Text(value=unicode_type(o)) elif isinstance(o, dict): - return DropdownWidget(values=o) + return Dropdown(values=o) elif isinstance(o, bool): - return CheckboxWidget(value=o) + return Checkbox(value=o) elif isinstance(o, float): min, max, value = _get_min_max_value(None, None, o) - return FloatSliderWidget(value=o, min=min, max=max) + return FloatSlider(value=o, min=min, max=max) elif isinstance(o, int): min, max, value = _get_min_max_value(None, None, o) - return IntSliderWidget(value=o, min=min, max=max) + return IntSlider(value=o, min=min, max=max) else: return None @@ -89,13 +89,13 @@ def _widget_abbrev(o): float_or_int = (float, int) if isinstance(o, (list, tuple)): if o and all(isinstance(x, string_types) for x in o): - return DropdownWidget(values=[unicode_type(k) for k in o]) + return Dropdown(values=[unicode_type(k) for k in o]) elif _matches(o, (float_or_int, float_or_int)): min, max, value = _get_min_max_value(o[0], o[1]) if all(isinstance(_, int) for _ in o): - cls = IntSliderWidget + cls = IntSlider else: - cls = FloatSliderWidget + cls = FloatSlider return cls(value=value, min=min, max=max) elif _matches(o, (float_or_int, float_or_int, float_or_int)): step = o[2] @@ -103,9 +103,9 @@ def _widget_abbrev(o): raise ValueError("step must be >= 0, not %r" % step) min, max, value = _get_min_max_value(o[0], o[1], step=step) if all(isinstance(_, int) for _ in o): - cls = IntSliderWidget + cls = IntSlider else: - cls = FloatSliderWidget + cls = FloatSlider return cls(value=value, min=min, max=max, step=step) else: return _widget_abbrev_single_value(o) @@ -176,7 +176,7 @@ def interactive(__interact_f, **kwargs): f = __interact_f co = kwargs.pop('clear_output', True) kwargs_widgets = [] - container = ContainerWidget() + container = Box() container.result = None container.args = [] container.kwargs = dict() diff --git a/IPython/html/widgets/tests/test_interaction.py b/IPython/html/widgets/tests/test_interaction.py index df15577..74d00c4 100644 --- a/IPython/html/widgets/tests/test_interaction.py +++ b/IPython/html/widgets/tests/test_interaction.py @@ -92,7 +92,7 @@ def test_single_value_string(): c = interactive(f, a=a) w = c.children[0] check_widget(w, - cls=widgets.TextWidget, + cls=widgets.Text, description='a', value=a, ) @@ -102,7 +102,7 @@ def test_single_value_bool(): c = interactive(f, a=a) w = c.children[0] check_widget(w, - cls=widgets.CheckboxWidget, + cls=widgets.Checkbox, description='a', value=a, ) @@ -115,7 +115,7 @@ def test_single_value_dict(): c = interactive(f, d=d) w = c.children[0] check_widget(w, - cls=widgets.DropdownWidget, + cls=widgets.Dropdown, description='d', values=d, value=next(iter(d.values())), @@ -126,7 +126,7 @@ def test_single_value_float(): c = interactive(f, a=a) w = c.children[0] check_widget(w, - cls=widgets.FloatSliderWidget, + cls=widgets.FloatSlider, description='a', value=a, min= -a if a > 0 else 3*a, @@ -141,7 +141,7 @@ def test_single_value_int(): nt.assert_equal(len(c.children), 1) w = c.children[0] check_widget(w, - cls=widgets.IntSliderWidget, + cls=widgets.IntSlider, description='a', value=a, min= -a if a > 0 else 3*a, @@ -159,7 +159,7 @@ def test_list_tuple_2_int(): c = interactive(f, tup=(min, max), lis=[min, max]) nt.assert_equal(len(c.children), 2) d = dict( - cls=widgets.IntSliderWidget, + cls=widgets.IntSlider, min=min, max=max, step=1, @@ -176,7 +176,7 @@ def test_list_tuple_3_int(): c = interactive(f, tup=(min, max, step), lis=[min, max, step]) nt.assert_equal(len(c.children), 2) d = dict( - cls=widgets.IntSliderWidget, + cls=widgets.IntSlider, min=min, max=max, step=step, @@ -193,7 +193,7 @@ def test_list_tuple_2_float(): c = interactive(f, tup=(min, max), lis=[min, max]) nt.assert_equal(len(c.children), 2) d = dict( - cls=widgets.FloatSliderWidget, + cls=widgets.FloatSlider, min=min, max=max, step=.1, @@ -212,7 +212,7 @@ def test_list_tuple_3_float(): c = interactive(f, tup=(min, max, step), lis=[min, max, step]) nt.assert_equal(len(c.children), 2) d = dict( - cls=widgets.FloatSliderWidget, + cls=widgets.FloatSlider, min=min, max=max, step=step, @@ -227,7 +227,7 @@ def test_list_tuple_str(): c = interactive(f, tup=tuple(values), lis=list(values)) nt.assert_equal(len(c.children), 2) d = dict( - cls=widgets.DropdownWidget, + cls=widgets.Dropdown, value=first, values=dvalues ) @@ -253,15 +253,15 @@ def test_defaults(): c = interactive(f) check_widgets(c, n=dict( - cls=widgets.IntSliderWidget, + cls=widgets.IntSlider, value=10, ), f=dict( - cls=widgets.FloatSliderWidget, + cls=widgets.FloatSlider, value=4.5, ), g=dict( - cls=widgets.IntSliderWidget, + cls=widgets.IntSlider, value=1, ), ) @@ -274,24 +274,24 @@ def test_default_values(): c = interactive(f) check_widgets(c, n=dict( - cls=widgets.IntSliderWidget, + cls=widgets.IntSlider, value=10, ), f=dict( - cls=widgets.FloatSliderWidget, + cls=widgets.FloatSlider, value=4.5, ), g=dict( - cls=widgets.IntSliderWidget, + cls=widgets.IntSlider, value=5, ), h=dict( - cls=widgets.DropdownWidget, + cls=widgets.Dropdown, values={'a': 1, 'b': 2}, value=2 ), j=dict( - cls=widgets.DropdownWidget, + cls=widgets.Dropdown, values={'hi':'hi', 'there':'there'}, value='there' ), @@ -305,34 +305,34 @@ def test_default_out_of_bounds(): c = interactive(f) check_widgets(c, f=dict( - cls=widgets.FloatSliderWidget, + cls=widgets.FloatSlider, value=5., ), h=dict( - cls=widgets.DropdownWidget, + cls=widgets.Dropdown, values={'a': 1}, value=1, ), j=dict( - cls=widgets.DropdownWidget, + cls=widgets.Dropdown, values={'hi':'hi', 'there':'there'}, value='hi', ), ) def test_annotations(): - @annotate(n=10, f=widgets.FloatTextWidget()) + @annotate(n=10, f=widgets.FloatText()) def f(n, f): pass c = interactive(f) check_widgets(c, n=dict( - cls=widgets.IntSliderWidget, + cls=widgets.IntSlider, value=10, ), f=dict( - cls=widgets.FloatTextWidget, + cls=widgets.FloatText, ), ) @@ -344,11 +344,11 @@ def test_priority(): c = interactive(f, kwarg='kwarg') check_widgets(c, kwarg=dict( - cls=widgets.TextWidget, + cls=widgets.Text, value='kwarg', ), annotate=dict( - cls=widgets.TextWidget, + cls=widgets.Text, value='annotate', ), ) @@ -362,7 +362,7 @@ def test_decorator_kwarg(): nt.assert_equal(len(displayed), 1) w = displayed[0].children[0] check_widget(w, - cls=widgets.IntSliderWidget, + cls=widgets.IntSlider, value=5, ) @@ -375,7 +375,7 @@ def test_decorator_no_call(): nt.assert_equal(len(displayed), 1) w = displayed[0].children[0] check_widget(w, - cls=widgets.TextWidget, + cls=widgets.Text, value='default', ) @@ -388,7 +388,7 @@ def test_call_interact(): nt.assert_equal(len(displayed), 1) w = displayed[0].children[0] check_widget(w, - cls=widgets.TextWidget, + cls=widgets.Text, value='default', ) @@ -401,7 +401,7 @@ def test_call_interact_kwargs(): nt.assert_equal(len(displayed), 1) w = displayed[0].children[0] check_widget(w, - cls=widgets.IntSliderWidget, + cls=widgets.IntSlider, value=10, ) @@ -417,7 +417,7 @@ def test_call_decorated_on_trait_change(): nt.assert_equal(len(displayed), 1) w = displayed[0].children[0] check_widget(w, - cls=widgets.TextWidget, + cls=widgets.Text, value='default', ) # test calling the function directly @@ -441,7 +441,7 @@ def test_call_decorated_kwargs_on_trait_change(): nt.assert_equal(len(displayed), 1) w = displayed[0].children[0] check_widget(w, - cls=widgets.TextWidget, + cls=widgets.Text, value='kwarg', ) # test calling the function directly @@ -458,7 +458,7 @@ def test_fixed(): nt.assert_equal(len(c.children), 1) w = c.children[0] check_widget(w, - cls=widgets.TextWidget, + cls=widgets.Text, value='text', description='b', ) @@ -467,22 +467,22 @@ def test_default_description(): c = interactive(f, b='text') w = c.children[0] check_widget(w, - cls=widgets.TextWidget, + cls=widgets.Text, value='text', description='b', ) def test_custom_description(): - c = interactive(f, b=widgets.TextWidget(value='text', description='foo')) + c = interactive(f, b=widgets.Text(value='text', description='foo')) w = c.children[0] check_widget(w, - cls=widgets.TextWidget, + cls=widgets.Text, value='text', description='foo', ) def test_int_range_logic(): - irsw = widgets.IntRangeSliderWidget + irsw = widgets.IntRangeSlider w = irsw(value=(2, 4), min=0, max=6) check_widget(w, cls=irsw, value=(2, 4), min=0, max=6) w.value = (4, 2) @@ -537,7 +537,7 @@ def test_int_range_logic(): def test_float_range_logic(): - frsw = widgets.FloatRangeSliderWidget + frsw = widgets.FloatRangeSlider w = frsw(value=(.2, .4), min=0., max=.6) check_widget(w, cls=frsw, value=(.2, .4), min=0., max=.6) w.value = (.4, .2) @@ -588,4 +588,4 @@ def test_float_range_logic(): with nt.assert_raises(ValueError): frsw(value=(2, 4), lower=3, upper=3) with nt.assert_raises(ValueError): - frsw(min=.2, max=.1) \ No newline at end of file + frsw(min=.2, max=.1) diff --git a/IPython/html/widgets/widget_bool.py b/IPython/html/widgets/widget_bool.py index ac07d7f..b7bbb1d 100644 --- a/IPython/html/widgets/widget_bool.py +++ b/IPython/html/widgets/widget_bool.py @@ -1,4 +1,4 @@ -"""BoolWidget class. +"""Bool class. Represents a boolean using a widget. """ @@ -15,20 +15,29 @@ Represents a boolean using a widget. #----------------------------------------------------------------------------- from .widget import DOMWidget from IPython.utils.traitlets import Unicode, Bool +from IPython.utils.warn import DeprecatedClass #----------------------------------------------------------------------------- # Classes #----------------------------------------------------------------------------- -class _BoolWidget(DOMWidget): +class _Bool(DOMWidget): + """A base class for creating widgets that represent booleans.""" value = Bool(False, help="Bool value", sync=True) - description = Unicode('', help="Description of the boolean (label).", sync=True) + description = Unicode('', help="Description of the boolean (label).", sync=True) disabled = Bool(False, help="Enable or disable user changes.", sync=True) -class CheckboxWidget(_BoolWidget): +class Checkbox(_Bool): + """Displays a boolean `value`.""" _view_name = Unicode('CheckboxView', sync=True) -class ToggleButtonWidget(_BoolWidget): - _view_name = Unicode('ToggleButtonView', sync=True) +class ToggleButton(_Bool): + """Displays a boolean `value`.""" + _view_name = Unicode('ToggleButtonView', sync=True) + + +# Remove in IPython 4.0 +CheckboxWidget = DeprecatedClass(Checkbox, 'CheckboxWidget') +ToggleButtonWidget = DeprecatedClass(ToggleButton, 'ToggleButtonWidget') diff --git a/IPython/html/widgets/widget_box.py b/IPython/html/widgets/widget_box.py new file mode 100644 index 0000000..bd204bd --- /dev/null +++ b/IPython/html/widgets/widget_box.py @@ -0,0 +1,73 @@ +"""Box class. + +Represents a container that can be used to group other widgets. +""" + +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. + +from .widget import DOMWidget +from IPython.utils.traitlets import Unicode, Tuple, TraitError, Int, CaselessStrEnum +from IPython.utils.warn import DeprecatedClass + +class Box(DOMWidget): + """Displays multiple widgets in a group.""" + _view_name = Unicode('BoxView', sync=True) + + # Child widgets in the container. + # Using a tuple here to force reassignment to update the list. + # When a proper notifying-list trait exists, that is what should be used here. + children = Tuple(sync=True, allow_none=False) + + def __init__(self, children = (), **kwargs): + kwargs['children'] = children + super(Box, self).__init__(**kwargs) + self.on_displayed(Box._fire_children_displayed) + + def _fire_children_displayed(self): + for child in self.children: + child._handle_displayed() + + +class Popup(Box): + """Displays multiple widgets in an in page popup div.""" + _view_name = Unicode('PopupView', sync=True) + + description = Unicode(sync=True) + button_text = Unicode(sync=True) + + +class FlexBox(Box): + """Displays multiple widgets using the flexible box model.""" + _view_name = Unicode('FlexBoxView', sync=True) + orientation = CaselessStrEnum(values=['vertical', 'horizontal'], default_value='vertical', sync=True) + flex = Int(0, sync=True, help="""Specify the flexible-ness of the model.""") + def _flex_changed(self, name, old, new): + new = min(max(0, new), 2) + if self.flex != new: + self.flex = new + + _locations = ['start', 'center', 'end', 'baseline', 'stretch'] + pack = CaselessStrEnum( + values=_locations, + default_value='start', allow_none=False, sync=True) + align = CaselessStrEnum( + values=_locations, + default_value='start', allow_none=False, sync=True) + + +def VBox(*pargs, **kwargs): + """Displays multiple widgets vertically using the flexible box model.""" + kwargs['orientation'] = 'vertical' + return FlexBox(*pargs, **kwargs) + +def HBox(*pargs, **kwargs): + """Displays multiple widgets horizontally using the flexible box model.""" + kwargs['orientation'] = 'horizontal' + return FlexBox(*pargs, **kwargs) + + +# Remove in IPython 4.0 +ContainerWidget = DeprecatedClass(Box, 'ContainerWidget') +PopupWidget = DeprecatedClass(Popup, 'PopupWidget') + diff --git a/IPython/html/widgets/widget_button.py b/IPython/html/widgets/widget_button.py index 3fdfe72..17774cc 100644 --- a/IPython/html/widgets/widget_button.py +++ b/IPython/html/widgets/widget_button.py @@ -1,4 +1,4 @@ -"""ButtonWidget class. +"""Button class. Represents a button in the frontend using a widget. Allows user to listen for click events on the button and trigger backend code when the clicks are fired. @@ -16,11 +16,16 @@ click events on the button and trigger backend code when the clicks are fired. #----------------------------------------------------------------------------- from .widget import DOMWidget, CallbackDispatcher from IPython.utils.traitlets import Unicode, Bool +from IPython.utils.warn import DeprecatedClass #----------------------------------------------------------------------------- # Classes #----------------------------------------------------------------------------- -class ButtonWidget(DOMWidget): +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.""" _view_name = Unicode('ButtonView', sync=True) # Keys @@ -29,7 +34,7 @@ class ButtonWidget(DOMWidget): def __init__(self, **kwargs): """Constructor""" - super(ButtonWidget, self).__init__(**kwargs) + super(Button, self).__init__(**kwargs) self._click_handlers = CallbackDispatcher() self.on_msg(self._handle_button_msg) @@ -54,3 +59,7 @@ class ButtonWidget(DOMWidget): Content of the msg.""" if content.get('event', '') == 'click': self._click_handlers(self) + + +# Remove in IPython 4.0 +ButtonWidget = DeprecatedClass(Button, 'ButtonWidget') diff --git a/IPython/html/widgets/widget_container.py b/IPython/html/widgets/widget_container.py deleted file mode 100644 index 6ba8206..0000000 --- a/IPython/html/widgets/widget_container.py +++ /dev/null @@ -1,34 +0,0 @@ -"""ContainerWidget class. - -Represents a container that can be used to group other widgets. -""" - -# Copyright (c) IPython Development Team. -# Distributed under the terms of the Modified BSD License. - -from .widget import DOMWidget -from IPython.utils.traitlets import Unicode, Tuple, TraitError - -class ContainerWidget(DOMWidget): - _view_name = Unicode('ContainerView', sync=True) - - # Child widgets in the container. - # Using a tuple here to force reassignment to update the list. - # When a proper notifying-list trait exists, that is what should be used here. - children = Tuple(sync=True, allow_none=False) - - def __init__(self, children = (), **kwargs): - kwargs['children'] = children - super(ContainerWidget, self).__init__(**kwargs) - self.on_displayed(ContainerWidget._fire_children_displayed) - - def _fire_children_displayed(self): - for child in self.children: - child._handle_displayed() - - -class PopupWidget(ContainerWidget): - _view_name = Unicode('PopupView', sync=True) - - description = Unicode(sync=True) - button_text = Unicode(sync=True) diff --git a/IPython/html/widgets/widget_float.py b/IPython/html/widgets/widget_float.py index db7b350..e5d5aac 100644 --- a/IPython/html/widgets/widget_float.py +++ b/IPython/html/widgets/widget_float.py @@ -1,4 +1,4 @@ -"""FloatWidget class. +"""Float class. Represents an unbounded float using a widget. """ @@ -15,17 +15,18 @@ Represents an unbounded float using a widget. #----------------------------------------------------------------------------- from .widget import DOMWidget from IPython.utils.traitlets import Unicode, CFloat, Bool, Enum, Tuple +from IPython.utils.warn import DeprecatedClass #----------------------------------------------------------------------------- # Classes #----------------------------------------------------------------------------- -class _FloatWidget(DOMWidget): - value = CFloat(0.0, help="Float value", sync=True) +class _Float(DOMWidget): + value = CFloat(0.0, help="Float value", sync=True) disabled = Bool(False, help="Enable or disable user changes", sync=True) description = Unicode(help="Description of the value this widget represents", sync=True) -class _BoundedFloatWidget(_FloatWidget): +class _BoundedFloat(_Float): max = CFloat(100.0, help="Max value", sync=True) min = CFloat(0.0, help="Min value", sync=True) step = CFloat(0.1, help="Minimum step that the value can take (ignored by some views)", sync=True) @@ -42,26 +43,26 @@ class _BoundedFloatWidget(_FloatWidget): self.value = min(max(new, self.min), self.max) -class FloatTextWidget(_FloatWidget): +class FloatText(_Float): _view_name = Unicode('FloatTextView', sync=True) -class BoundedFloatTextWidget(_BoundedFloatWidget): +class BoundedFloatText(_BoundedFloat): _view_name = Unicode('FloatTextView', sync=True) -class FloatSliderWidget(_BoundedFloatWidget): +class FloatSlider(_BoundedFloat): _view_name = Unicode('FloatSliderView', sync=True) - orientation = Enum([u'horizontal', u'vertical'], u'horizontal', + orientation = Enum([u'horizontal', u'vertical'], u'horizontal', help="Vertical or horizontal.", sync=True) range = Bool(False, help="Display a range selector", sync=True) readout = Bool(True, help="Display the current value of the slider next to it.", sync=True) -class FloatProgressWidget(_BoundedFloatWidget): +class FloatProgress(_BoundedFloat): _view_name = Unicode('ProgressView', sync=True) -class _FloatRangeWidget(_FloatWidget): +class _FloatRange(_Float): value = Tuple(CFloat, CFloat, default_value=(0.0, 1.0), help="Tuple of (lower, upper) bounds", sync=True) lower = CFloat(0.0, help="Lower bound", sync=False) upper = CFloat(1.0, help="Upper bound", sync=False) @@ -90,14 +91,14 @@ class _FloatRangeWidget(_FloatWidget): elif name == 'upper': self.value = (self.value[0], new) -class _BoundedFloatRangeWidget(_FloatRangeWidget): +class _BoundedFloatRange(_FloatRange): step = CFloat(1.0, help="Minimum step that the value can take (ignored by some views)", sync=True) max = CFloat(100.0, help="Max value", sync=True) min = CFloat(0.0, help="Min value", sync=True) def __init__(self, *pargs, **kwargs): any_value_given = 'value' in kwargs or 'upper' in kwargs or 'lower' in kwargs - _FloatRangeWidget.__init__(self, *pargs, **kwargs) + _FloatRange.__init__(self, *pargs, **kwargs) # ensure a minimal amount of sanity if self.min > self.max: @@ -156,9 +157,15 @@ class _BoundedFloatRangeWidget(_FloatRangeWidget): self.lower = low -class FloatRangeSliderWidget(_BoundedFloatRangeWidget): +class FloatRangeSlider(_BoundedFloatRange): _view_name = Unicode('FloatSliderView', sync=True) orientation = Enum([u'horizontal', u'vertical'], u'horizontal', help="Vertical or horizontal.", sync=True) range = Bool(True, help="Display a range selector", sync=True) readout = Bool(True, help="Display the current value of the slider next to it.", sync=True) + +# Remove in IPython 4.0 +FloatTextWidget = DeprecatedClass(FloatText, 'FloatTextWidget') +BoundedFloatTextWidget = DeprecatedClass(BoundedFloatText, 'BoundedFloatTextWidget') +FloatSliderWidget = DeprecatedClass(FloatSlider, 'FloatSliderWidget') +FloatProgressWidget = DeprecatedClass(FloatProgress, 'FloatProgressWidget') diff --git a/IPython/html/widgets/widget_image.py b/IPython/html/widgets/widget_image.py index 0e18bde..f8ff8d4 100644 --- a/IPython/html/widgets/widget_image.py +++ b/IPython/html/widgets/widget_image.py @@ -1,4 +1,4 @@ -"""ImageWidget class. +"""Image class. Represents an image in the frontend using a widget. """ @@ -17,11 +17,18 @@ import base64 from .widget import DOMWidget from IPython.utils.traitlets import Unicode, CUnicode, Bytes +from IPython.utils.warn import DeprecatedClass #----------------------------------------------------------------------------- # Classes #----------------------------------------------------------------------------- -class ImageWidget(DOMWidget): +class Image(DOMWidget): + """Displays an image as a widget. + + The `value` of this widget accepts a byte string. The byte string is the raw + image data that you want the browser to display. You can explicitly define + the format of the byte string using the `format` trait (which defaults to + "png").""" _view_name = Unicode('ImageView', sync=True) # Define the custom state properties to sync with the front-end @@ -33,3 +40,7 @@ class ImageWidget(DOMWidget): value = Bytes() def _value_changed(self, name, old, new): self._b64value = base64.b64encode(new) + + +# Remove in IPython 4.0 +ImageWidget = DeprecatedClass(Image, 'ImageWidget') diff --git a/IPython/html/widgets/widget_int.py b/IPython/html/widgets/widget_int.py index f2f4f92..4497990 100644 --- a/IPython/html/widgets/widget_int.py +++ b/IPython/html/widgets/widget_int.py @@ -1,4 +1,4 @@ -"""IntWidget class. +"""Int class. Represents an unbounded int using a widget. """ @@ -15,17 +15,21 @@ Represents an unbounded int using a widget. #----------------------------------------------------------------------------- from .widget import DOMWidget from IPython.utils.traitlets import Unicode, CInt, Bool, Enum, Tuple +from IPython.utils.warn import DeprecatedClass #----------------------------------------------------------------------------- # Classes #----------------------------------------------------------------------------- -class _IntWidget(DOMWidget): - value = CInt(0, help="Int value", sync=True) +class _Int(DOMWidget): + """Base class used to create widgets that represent an int.""" + value = CInt(0, help="Int value", sync=True) disabled = Bool(False, help="Enable or disable user changes", sync=True) description = Unicode(help="Description of the value this widget represents", sync=True) -class _BoundedIntWidget(_IntWidget): +class _BoundedInt(_Int): + """Base class used to create widgets that represent a int that is bounded + by a minium and maximum.""" step = CInt(1, help="Minimum step that the value can take (ignored by some views)", sync=True) max = CInt(100, help="Max value", sync=True) min = CInt(0, help="Min value", sync=True) @@ -41,26 +45,30 @@ class _BoundedIntWidget(_IntWidget): self.value = min(max(new, self.min), self.max) -class IntTextWidget(_IntWidget): +class IntText(_Int): + """Textbox widget that represents a int.""" _view_name = Unicode('IntTextView', sync=True) -class BoundedIntTextWidget(_BoundedIntWidget): +class BoundedIntText(_BoundedInt): + """Textbox widget that represents a int bounded by a minimum and maximum value.""" _view_name = Unicode('IntTextView', sync=True) -class IntSliderWidget(_BoundedIntWidget): +class IntSlider(_BoundedInt): + """Slider widget that represents a int bounded by a minimum and maximum value.""" _view_name = Unicode('IntSliderView', sync=True) - orientation = Enum([u'horizontal', u'vertical'], u'horizontal', + orientation = Enum([u'horizontal', u'vertical'], u'horizontal', help="Vertical or horizontal.", sync=True) range = Bool(False, help="Display a range selector", sync=True) readout = Bool(True, help="Display the current value of the slider next to it.", sync=True) -class IntProgressWidget(_BoundedIntWidget): +class IntProgress(_BoundedInt): + """Progress bar that represents a int bounded by a minimum and maximum value.""" _view_name = Unicode('ProgressView', sync=True) -class _IntRangeWidget(_IntWidget): +class _IntRange(_Int): value = Tuple(CInt, CInt, default_value=(0, 1), help="Tuple of (lower, upper) bounds", sync=True) lower = CInt(0, help="Lower bound", sync=False) upper = CInt(1, help="Upper bound", sync=False) @@ -89,14 +97,14 @@ class _IntRangeWidget(_IntWidget): elif name == 'upper': self.value = (self.value[0], new) -class _BoundedIntRangeWidget(_IntRangeWidget): +class _BoundedIntRange(_IntRange): step = CInt(1, help="Minimum step that the value can take (ignored by some views)", sync=True) max = CInt(100, help="Max value", sync=True) min = CInt(0, help="Min value", sync=True) def __init__(self, *pargs, **kwargs): any_value_given = 'value' in kwargs or 'upper' in kwargs or 'lower' in kwargs - _IntRangeWidget.__init__(self, *pargs, **kwargs) + _IntRange.__init__(self, *pargs, **kwargs) # ensure a minimal amount of sanity if self.min > self.max: @@ -153,9 +161,15 @@ class _BoundedIntRangeWidget(_IntRangeWidget): self.upper = high self.lower = low -class IntRangeSliderWidget(_BoundedIntRangeWidget): +class IntRangeSlider(_BoundedIntRange): _view_name = Unicode('IntSliderView', sync=True) orientation = Enum([u'horizontal', u'vertical'], u'horizontal', help="Vertical or horizontal.", sync=True) range = Bool(True, help="Display a range selector", sync=True) readout = Bool(True, help="Display the current value of the slider next to it.", sync=True) + +# Remove in IPython 4.0 +IntTextWidget = DeprecatedClass(IntText, 'IntTextWidget') +BoundedIntTextWidget = DeprecatedClass(BoundedIntText, 'BoundedIntTextWidget') +IntSliderWidget = DeprecatedClass(IntSlider, 'IntSliderWidget') +IntProgressWidget = DeprecatedClass(IntProgress, 'IntProgressWidget') diff --git a/IPython/html/widgets/widget_selection.py b/IPython/html/widgets/widget_selection.py index e9706d1..7c4f2f1 100644 --- a/IPython/html/widgets/widget_selection.py +++ b/IPython/html/widgets/widget_selection.py @@ -1,4 +1,4 @@ -"""SelectionWidget classes. +"""Selection classes. Represents an enumeration using a widget. """ @@ -20,11 +20,12 @@ from threading import Lock from .widget import DOMWidget from IPython.utils.traitlets import Unicode, List, Bool, Any, Dict, TraitError from IPython.utils.py3compat import unicode_type +from IPython.utils.warn import DeprecatedClass #----------------------------------------------------------------------------- # SelectionWidget #----------------------------------------------------------------------------- -class _SelectionWidget(DOMWidget): +class _Selection(DOMWidget): """Base class for Selection widgets ``values`` can be specified as a list or dict. If given as a list, @@ -109,17 +110,30 @@ class _SelectionWidget(DOMWidget): self.value_lock.release() -class ToggleButtonsWidget(_SelectionWidget): +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) -class DropdownWidget(_SelectionWidget): +class Dropdown(_Selection): + """Allows you to select a single item from a dropdown.""" _view_name = Unicode('DropdownView', sync=True) -class RadioButtonsWidget(_SelectionWidget): +class RadioButtons(_Selection): + """Group of radio buttons that represent an enumeration. Only one radio + button can be toggled at any point in time.""" _view_name = Unicode('RadioButtonsView', sync=True) -class SelectWidget(_SelectionWidget): +class Select(_Selection): + """Listbox that only allows one item to be selected at any given time.""" _view_name = Unicode('SelectView', sync=True) + + +# Remove in IPython 4.0 +ToggleButtonsWidget = DeprecatedClass(ToggleButtons, 'ToggleButtonsWidget') +DropdownWidget = DeprecatedClass(Dropdown, 'DropdownWidget') +RadioButtonsWidget = DeprecatedClass(RadioButtons, 'RadioButtonsWidget') +SelectWidget = DeprecatedClass(Select, 'SelectWidget') diff --git a/IPython/html/widgets/widget_selectioncontainer.py b/IPython/html/widgets/widget_selectioncontainer.py index ef91559..729090b 100644 --- a/IPython/html/widgets/widget_selectioncontainer.py +++ b/IPython/html/widgets/widget_selectioncontainer.py @@ -1,4 +1,4 @@ -"""SelectionContainerWidget class. +"""SelectionContainer class. Represents a multipage container that can be used to group other widgets into pages. @@ -14,13 +14,15 @@ pages. #----------------------------------------------------------------------------- # Imports #----------------------------------------------------------------------------- -from .widget_container import ContainerWidget +from .widget_box import Box from IPython.utils.traitlets import Unicode, Dict, CInt +from IPython.utils.warn import DeprecatedClass #----------------------------------------------------------------------------- # Classes #----------------------------------------------------------------------------- -class _SelectionContainerWidget(ContainerWidget): +class _SelectionContainer(Box): + """Base class used to display multiple child widgets.""" _titles = Dict(help="Titles of the pages", sync=True) selected_index = CInt(0, sync=True) @@ -50,9 +52,16 @@ class _SelectionContainerWidget(ContainerWidget): return None -class AccordionWidget(_SelectionContainerWidget): +class Accordion(_SelectionContainer): + """Displays children each on a separate accordion page.""" _view_name = Unicode('AccordionView', sync=True) -class TabWidget(_SelectionContainerWidget): +class Tab(_SelectionContainer): + """Displays children each on a separate accordion tab.""" _view_name = Unicode('TabView', sync=True) + + +# Remove in IPython 4.0 +AccordionWidget = DeprecatedClass(Accordion, 'AccordionWidget') +TabWidget = DeprecatedClass(Tab, 'TabWidget') diff --git a/IPython/html/widgets/widget_string.py b/IPython/html/widgets/widget_string.py index 9f6aa30..26b06cc 100644 --- a/IPython/html/widgets/widget_string.py +++ b/IPython/html/widgets/widget_string.py @@ -1,4 +1,4 @@ -"""StringWidget class. +"""String class. Represents a unicode string using a widget. """ @@ -15,37 +15,44 @@ Represents a unicode string using a widget. #----------------------------------------------------------------------------- from .widget import DOMWidget, CallbackDispatcher from IPython.utils.traitlets import Unicode, Bool +from IPython.utils.warn import DeprecatedClass #----------------------------------------------------------------------------- # Classes #----------------------------------------------------------------------------- -class _StringWidget(DOMWidget): +class _String(DOMWidget): + """Base class used to create widgets that represent a string.""" value = Unicode(help="String value", sync=True) disabled = Bool(False, help="Enable or disable user changes", sync=True) description = Unicode(help="Description of the value this widget represents", sync=True) placeholder = Unicode("", help="Placeholder text to display when nothing has been typed", sync=True) -class HTMLWidget(_StringWidget): +class HTML(_String): + """Renders the string `value` as HTML.""" _view_name = Unicode('HTMLView', sync=True) -class LatexWidget(_StringWidget): +class Latex(_String): + """Renders math inside the string `value` as Latex (requires $ $ or $$ $$ + and similar latex tags).""" _view_name = Unicode('LatexView', sync=True) -class TextareaWidget(_StringWidget): +class Textarea(_String): + """Multiline text area widget.""" _view_name = Unicode('TextareaView', sync=True) def scroll_to_bottom(self): self.send({"method": "scroll_to_bottom"}) -class TextWidget(_StringWidget): +class Text(_String): + """Single line textbox widget.""" _view_name = Unicode('TextView', sync=True) def __init__(self, **kwargs): - super(TextWidget, self).__init__(**kwargs) + super(Text, self).__init__(**kwargs) self._submission_callbacks = CallbackDispatcher() self.on_msg(self._handle_string_msg) @@ -71,3 +78,10 @@ class TextWidget(_StringWidget): remove: bool (optional) Whether to unregister the callback""" self._submission_callbacks.register_callback(callback, remove=remove) + + +# Remove in IPython 4.0 +HTMLWidget = DeprecatedClass(HTML, 'HTMLWidget') +LatexWidget = DeprecatedClass(Latex, 'LatexWidget') +TextareaWidget = DeprecatedClass(Textarea, 'TextareaWidget') +TextWidget = DeprecatedClass(Text, 'TextWidget') diff --git a/IPython/kernel/kernelspec.py b/IPython/kernel/kernelspec.py index c708a8c..f129886 100644 --- a/IPython/kernel/kernelspec.py +++ b/IPython/kernel/kernelspec.py @@ -8,7 +8,7 @@ pjoin = os.path.join from IPython.utils.path import get_ipython_dir from IPython.utils.py3compat import PY3 -from IPython.utils.traitlets import HasTraits, List, Unicode, Dict +from IPython.utils.traitlets import HasTraits, List, Unicode, Dict, Any if os.name == 'nt': programdata = os.environ.get('PROGRAMDATA', None) @@ -36,18 +36,12 @@ class KernelSpec(HasTraits): argv = List() display_name = Unicode() language = Unicode() - codemirror_mode = None + codemirror_mode = Any() # can be unicode or dict env = Dict() - resource_dir = Unicode() - def __init__(self, resource_dir, argv, display_name, language, - codemirror_mode=None): - super(KernelSpec, self).__init__(resource_dir=resource_dir, argv=argv, - display_name=display_name, language=language, - codemirror_mode=codemirror_mode) - if not self.codemirror_mode: - self.codemirror_mode = self.language + def _codemirror_mode_default(self): + return self.language @classmethod def from_resource_dir(cls, resource_dir): diff --git a/IPython/utils/_process_cli.py b/IPython/utils/_process_cli.py index eb69d3f..a7b7b90 100644 --- a/IPython/utils/_process_cli.py +++ b/IPython/utils/_process_cli.py @@ -61,3 +61,18 @@ def getoutput(cmd): myError = reg.StandardError error = myError.ReadToEnd() return output + +def check_pid(pid): + """ + Check if a process with the given PID (pid) exists + """ + try: + System.Diagnostics.Process.GetProcessById(pid) + # process with given pid is running + return True + except System.InvalidOperationException: + # process wasn't started by this object (but is running) + return True + except System.ArgumentException: + # process with given pid isn't running + return False diff --git a/IPython/utils/process.py b/IPython/utils/process.py index 2a19945..64b7254 100644 --- a/IPython/utils/process.py +++ b/IPython/utils/process.py @@ -23,7 +23,7 @@ import sys if sys.platform == 'win32': from ._process_win32 import _find_cmd, system, getoutput, arg_split, check_pid elif sys.platform == 'cli': - from ._process_cli import _find_cmd, system, getoutput, arg_split + from ._process_cli import _find_cmd, system, getoutput, arg_split, check_pid else: from ._process_posix import _find_cmd, system, getoutput, arg_split, check_pid diff --git a/IPython/utils/tests/test_traitlets.py b/IPython/utils/tests/test_traitlets.py index fcb3ca2..9251ae8 100644 --- a/IPython/utils/tests/test_traitlets.py +++ b/IPython/utils/tests/test_traitlets.py @@ -406,6 +406,13 @@ class TestHasTraits(TestCase): a = A() self.assertEqual(a.trait_metadata('i','config_key'), 'MY_VALUE') + def test_trait_metadata_default(self): + class A(HasTraits): + i = Int() + a = A() + self.assertEqual(a.trait_metadata('i', 'config_key'), None) + self.assertEqual(a.trait_metadata('i', 'config_key', 'default'), 'default') + def test_traits(self): class A(HasTraits): i = Int diff --git a/IPython/utils/traitlets.py b/IPython/utils/traitlets.py index 7b65196..c81f68a 100644 --- a/IPython/utils/traitlets.py +++ b/IPython/utils/traitlets.py @@ -458,8 +458,8 @@ class TraitType(object): % (self.name, self.info(), repr_type(value)) raise TraitError(e) - def get_metadata(self, key): - return getattr(self, '_metadata', {}).get(key, None) + def get_metadata(self, key, default=None): + return getattr(self, '_metadata', {}).get(key, default) def set_metadata(self, key, value): getattr(self, '_metadata', {})[key] = value @@ -728,7 +728,7 @@ class HasTraits(py3compat.with_metaclass(MetaHasTraits, object)): return result - def trait_metadata(self, traitname, key): + def trait_metadata(self, traitname, key, default=None): """Get metadata values for trait by key.""" try: trait = getattr(self.__class__, traitname) @@ -736,7 +736,7 @@ class HasTraits(py3compat.with_metaclass(MetaHasTraits, object)): raise TraitError("Class %s does not have a trait named %s" % (self.__class__.__name__, traitname)) else: - return trait.get_metadata(key) + return trait.get_metadata(key, default) #----------------------------------------------------------------------------- # Actual TraitTypes implementations/subclasses diff --git a/IPython/utils/warn.py b/IPython/utils/warn.py index 693eeb3..6ad6acc 100644 --- a/IPython/utils/warn.py +++ b/IPython/utils/warn.py @@ -16,6 +16,7 @@ Utilities for warnings. Shoudn't we just use the built in warnings module. from __future__ import print_function import sys +import warnings from IPython.utils import io @@ -65,3 +66,16 @@ def fatal(msg,exit_val=1): warn(msg,exit_val=exit_val,level=4) + +def DeprecatedClass(base, class_name): + # Hook the init method of the base class. + def init_hook(self, *pargs, **kwargs): + base.__init__(self, *pargs, **kwargs) + + # Warn once per class. + if base not in DeprecatedClass._warned_classes: + DeprecatedClass._warned_classes.append(base) + warn('"{}" is deprecated, please use "{}" instead.'.format( + class_name, base.__name__)) + return type(class_name, (base,), {'__init__': init_hook}) +DeprecatedClass._warned_classes = [] diff --git a/docs/source/whatsnew/pr/incompat-deprecated-widget-names.rst b/docs/source/whatsnew/pr/incompat-deprecated-widget-names.rst new file mode 100644 index 0000000..6ccd7aa --- /dev/null +++ b/docs/source/whatsnew/pr/incompat-deprecated-widget-names.rst @@ -0,0 +1,6 @@ +* The widget classes have been renamed from `*Widget` to `*`. The old names are + still functional, but are deprecated. i.e. `IntSliderWidget` has been renamed + to `IntSlider`. +* The ContainerWidget was renamed to Box and no longer defaults as a flexible + box in the web browser. A new FlexBox widget was added, which allows you to + use the flexible box model. diff --git a/tox.ini b/tox.ini index 5852233..dae0b87 100644 --- a/tox.ini +++ b/tox.ini @@ -3,23 +3,44 @@ # test suite on all supported python versions. To use it, "pip install tox" # and then run "tox" from this directory. +# Building the source distribution requires both fabric's fab binary +# (http://www.fabfile.org/) and the lessc binary of the css preprocessor +# less (http://lesscss.org/) in the PATH. +# "pip install fabric" will install fabric. Less can be installed by +# node.js' (http://nodejs.org/) package manager npm: +# "npm install -g less". + +# Javascript tests need additional dependencies that can be installed +# using node.js' package manager npm: +# [*] casperjs: "npm install -g casperjs" +# [*] slimerjs: "npm install -g slimerjs" +# [*] phantomjs: "npm install -g phantomjs" + +# Note: qt4 versions break some tests with tornado versions >=4.0. + [tox] -envlist = py27, py33 +envlist = py27, py33, py34 [testenv] -deps = +deps = + pyzmq nose - mock - tornado + tornado<4.0 jinja2 sphinx pygments + jsonpointer + jsonschema + mistune + # To avoid loading IPython module in the current directory, change # current directory to ".tox/py*/tmp" before running test. changedir = {envtmpdir} commands = - # As pip does not treat egg, use easy_install to install PyZMQ. - # See also: https://github.com/zeromq/pyzmq - easy_install -q pyzmq - iptest --all + iptest --all + +[testenv:py27] +deps= + mock + {[testenv]deps}