1919
2020try :
2121 from ipywidgets import (IntSlider , FloatSlider , ToggleButtons , Box , DOMWidget )
22+ from IPython .core .formatters import IPythonDisplayFormatter
2223 _ipywidgets = True
2324except ImportError :
2425 # fallback to non-Manipulate-enabled build if we don't have ipywidgets installed.
@@ -57,6 +58,12 @@ def __init__(self, var):
5758 self .var = var
5859
5960
61+ class JupyterWidgetError (Exception ):
62+ def __init__ (self , err ):
63+ super (JupyterWidgetError , self ).__init__ ()
64+ self .err = err
65+
66+
6067class ManipulateParameter (Builtin ): # parses one Manipulate[] parameter spec, e.g. {x, 1, 2}, see _WidgetInstantiator
6168 context = 'System`Private`'
6269
@@ -88,6 +95,13 @@ def _manipulate_label(x): # gets the label that is displayed for a symbol or na
8895 return str (x )
8996
9097
98+ def _create_widget (widget , ** kwargs ):
99+ try :
100+ return widget (** kwargs )
101+ except Exception as e :
102+ raise JupyterWidgetError (str (e ))
103+
104+
91105class _WidgetInstantiator ():
92106 # we do not want to have widget instances (like FloatSlider) get into the evaluation pipeline (e.g. via Expression
93107 # or Atom), since there might be all kinds of problems with serialization of these widget classes. therefore, the
@@ -132,7 +146,7 @@ def _add_continuous_widget(self, symbol, label, default, minimum, maximum, evalu
132146 raise IllegalWidgetArguments (symbol )
133147 else :
134148 defval = min (max (default .to_python (), minimum_value ), maximum_value )
135- widget = FloatSlider ( value = defval , min = minimum_value , max = maximum_value )
149+ widget = _create_widget ( FloatSlider , value = defval , min = minimum_value , max = maximum_value )
136150 self ._add_widget (widget , symbol .get_name (), lambda x : from_python (x ), label )
137151
138152 def _add_discrete_widget (self , symbol , label , default , minimum , maximum , step , evaluation ):
@@ -144,10 +158,10 @@ def _add_discrete_widget(self, symbol, label, default, minimum, maximum, step, e
144158 else :
145159 default_value = min (max (default .to_python (), minimum_value ), maximum_value )
146160 if all (isinstance (x , Integer ) for x in [minimum , maximum , default , step ]):
147- widget = IntSlider ( value = default_value , min = minimum_value , max = maximum_value ,
161+ widget = _create_widget ( IntSlider , value = default_value , min = minimum_value , max = maximum_value ,
148162 step = step_value )
149163 else :
150- widget = FloatSlider ( value = default_value , min = minimum_value , max = maximum_value ,
164+ widget = _create_widget ( FloatSlider , value = default_value , min = minimum_value , max = maximum_value ,
151165 step = step_value )
152166 self ._add_widget (widget , symbol .get_name (), lambda x : from_python (x ), label )
153167
@@ -162,7 +176,7 @@ def _add_options_widget(self, symbol, options, default, label, evaluation):
162176 if option .same (default ):
163177 default_index = i
164178
165- widget = ToggleButtons ( options = formatted_options , value = default_index )
179+ widget = _create_widget ( ToggleButtons , options = formatted_options , value = default_index )
166180 self ._add_widget (widget , symbol .get_name (), lambda j : options .leaves [j ], label )
167181
168182 def _add_widget (self , widget , name , parse , label ):
@@ -226,8 +240,11 @@ class Manipulate(Builtin):
226240 messages = {
227241 'jupyter' : 'Manipulate[] only works inside a Jupyter notebook.' ,
228242 'noipywidget' : 'Manipulate[] needs the ipywidgets module to work.' ,
229- 'generr' : 'Widget creation failed: ``' ,
230- 'widgetargs' : 'Illegal variable range or step parameters for ``.'
243+ 'imathics' : 'Your IMathics kernel does not seem to support all necessary operations. ' +
244+ 'Please check that you have the latest version installed.' ,
245+ 'widgetmake' : 'Jupyter widget construction failed with "``".' ,
246+ 'widgetargs' : 'Illegal variable range or step parameters for ``.' ,
247+ 'widgetdisp' : 'Jupyter failed to display the widget.' ,
231248 }
232249
233250 def apply (self , expr , args , evaluation ):
@@ -245,14 +262,18 @@ def apply(self, expr, args, evaluation):
245262 if not instantiator .add (arg , evaluation ): # not a valid argument pattern?
246263 return
247264 except IllegalWidgetArguments as e :
248- evaluation .message ('Manipulate' , 'widgetargs' , strip_context (str (e .var )))
249- except Exception as e :
250- evaluation .message ('Manipulate' , 'generr ' , str ( e )) # usually some Jupyter error
265+ evaluation .error ('Manipulate' , 'widgetargs' , strip_context (str (e .var )))
266+ except JupyterWidgetError as e :
267+ evaluation .error ('Manipulate' , 'widgetmake ' , e . err )
251268 return Symbol ('$Aborted' )
252269
253270 clear_output_callback = evaluation .clear_output_callback
254271 display_data_callback = evaluation .display_data_callback # for pushing updates
255272
273+ if clear_output_callback is None or display_data_callback is None :
274+ evaluation .error ('Manipulate' , 'imathics' )
275+ return Symbol ('$Aborted' )
276+
256277 def callback (** kwargs ):
257278 clear_output_callback (wait = True )
258279
@@ -270,12 +291,9 @@ def callback(**kwargs):
270291 widgets = instantiator .get_widgets ()
271292 if len (widgets ) > 0 :
272293 box = _interactive (instantiator .build_callback (callback ), widgets ) # create the widget
273-
274- # the following code is a boiled down version from IPython.core.formatters.IPythonDisplayFormatter.
275- # note that '_ipython_display_' is a magic constant defined in print_method of IPythonDisplayFormatter.
276-
277- method = getattr (box , '_ipython_display_' )
278- if method is not None :
279- method () # make the widget appear on the Jupyter notebook
294+ formatter = IPythonDisplayFormatter ()
295+ if not formatter (box ): # make the widget appear on the Jupyter notebook
296+ evaluation .error ('Manipulate' , 'widgetdisp' )
297+ return Symbol ('$Aborted' )
280298
281299 return Symbol ('Null' ) # the interactive output is pushed via kernel.display_data_callback (see above)
0 commit comments