# HG changeset patch # User William Stein # Date 1204629781 21600 # Node ID 2c559ef52454cb440bdf42bcb5fd3bffe4972a12 # Parent 6f73ec3c47727f583703041009c39a6b12f1a27a #1322 -- sage manipulate command -- finally got something pretty usable (!) PHG: user: William Stein diff -r 6f73ec3c4772 -r 2c559ef52454 sage/server/notebook/cell.py --- a/sage/server/notebook/cell.py Tue Mar 04 01:23:37 2008 -0600 +++ b/sage/server/notebook/cell.py Tue Mar 04 05:23:01 2008 -0600 @@ -305,7 +305,11 @@ class Cell(Cell_generic): self.__version = 1+self.version() return elif self.is_manipulating(): - del self.manipulate + try: + del self.manipulate + del self._manipulate_output + except AttributeError: + pass self.__version = 1+self.version() self.__in = input @@ -408,7 +412,8 @@ class Cell(Cell_generic): try: # Fill in the output template output,html = self._manipulate_output - z = z.replace('', output.replace('<','<')) + output = self.parse_html(output, ncols) + z = z.replace('', output) z = z.replace('', html) return z except (ValueError, AttributeError), msg: @@ -429,37 +434,41 @@ class Cell(Cell_generic): return s if html: - def format(x): - return word_wrap(x.replace('<','<'), ncols=ncols) + s = self.parse_html(s, ncols) - def format_html(x): - x = self.process_cell_urls(x) - return x + if not is_manipulate and not self.is_html() and len(s.strip()) > 0: + s = '
' + s.strip('\n') + '
' + return s.strip('\n') + + def parse_html(self, s, ncols): + def format(x): + return word_wrap(x.replace('<','<'), ncols=ncols) + + def format_html(x): + return self.process_cell_urls(x) + + # if there is an error in the output, + # specially format it. + s = format_exception(s, ncols) + + # Everything not wrapped in ... + # should have the <'s replaced by <'s + # and be word wrapped. + t = '' + while len(s) > 0: + i = s.find('') + if i == -1: + t += format(s) + break + j = s.find('') + if j == -1: + t += format(s[:i]) + break + t += format(s[:i]) + format_html(s[i+6:j]) + s = s[j+7:] + t = t.replace('','') + return t - # if there is an error in the output, - # specially format it. - s = format_exception(s, ncols) - - # Everything not wrapped in ... - # should have the <'s replaced by <'s - # and be word wrapped. - t = '' - while len(s) > 0: - i = s.find('') - if i == -1: - t += format(s) - break - j = s.find('') - if j == -1: - t += format(s[:i]) - break - t += format(s[:i]) + format_html(s[i+6:j]) - s = s[j+7:] - s = t - if not is_manipulate and not self.is_html() and len(s.strip()) > 0: - s = '
' + s.strip('\n') + '
' - - return s.strip('\n') def has_output(self): return len(self.__out.strip()) > 0 @@ -552,17 +561,7 @@ class Cell(Cell_generic): wrap = 68 div_wrap = 68 key = (wrap,div_wrap,do_print) - #try: - # return self._html_cache[key] - #except KeyError: - # pass - #except AttributeError: - # self._html_cache = {} - if self.__in.lstrip()[:8] == '%hideall': - #self._html_cache[key] = '' - return '' - if wrap is None: wrap = self.notebook().conf()['word_wrap_cols'] if self.worksheet().compute_process_has_been_started(): @@ -577,7 +576,11 @@ class Cell(Cell_generic): html_in = self.html_in(do_print=do_print) introspect = "
"%self.id() html_out = self.html_out(wrap, do_print=do_print) - s = html_in + introspect + html_out + + if self.__in.lstrip()[:8] == '%hideall': + s = html_out + else: + s = html_in + introspect + html_out if div_wrap: s = '\n\n
'%(self.id(), self.id(), cls) + s + '
' diff -r 6f73ec3c4772 -r 2c559ef52454 sage/server/notebook/js.py --- a/sage/server/notebook/js.py Tue Mar 04 01:23:37 2008 -0600 +++ b/sage/server/notebook/js.py Tue Mar 04 05:23:01 2008 -0600 @@ -1757,6 +1757,18 @@ function evaluate_cell_callback(status, } function cell_output_set_type(id, typ, do_async) { + /* We do this specifically because manipulate cells do not work at all when + displayed in nowrap mode, which is VERY BAD. So instead for manipulates + one gets a toggle to and from hidden. + */ + if (typ=="nowrap" && get_element("cell-manipulate-" + id)) { + /* if the type is nowrap and the cell-manipulate-[id] div exists (i.e., we are manipulating) + then just make the thing hidden. */ + typ = "hidden"; + } + + /* OK, now set the sell output type. */ + set_class('cell_div_output_' + id, 'cell_div_output_' + typ) set_class('cell_output_' + id, 'cell_output_' + typ) set_class('cell_output_nowrap_' + id, 'cell_output_nowrap_' + typ) @@ -1839,6 +1851,11 @@ function cancel_update_check() { document.title = original_title; } +function contains_jsmath(text) { + // TODO: should make this not case sensitive!! how to .lower() in javascript? + return (text.indexOf('class="math"') != -1 || text.indexOf("class='math'") != -1); +} + function set_output_text(id, text, wrapped_text, output_html, status, introspect_html, no_manip) { var cell_manip = get_element("cell-manipulate-" + id); if (!no_manip && cell_manip) { @@ -1849,7 +1866,17 @@ function set_output_text(id, text, wrapp return; } var new_manip_output = wrapped_text.slice(i+8,j); - cell_manip.innerHTML = new_manip_output; + + /* An error occured accessing the data for this cell. Just force reload + of the cell, which will certainly define that data. */ + if (new_manip_output.indexOf('__SAGE_MANIPULATE_RESTART__') != -1) { + evaluate_cell(id, 0); + } else { + cell_manip.innerHTML = new_manip_output; + if (contains_jsmath(new_manip_output)) { + jsMath.ProcessBeforeShowing(cell_manip); + } + } } else { /* fill in output text got so far */ var cell_output = get_element('cell_output_' + id); @@ -1859,12 +1886,19 @@ function set_output_text(id, text, wrapp cell_output.innerHTML = wrapped_text; cell_output_nowrap.innerHTML = text; cell_output_html.innerHTML = output_html; + + /* Did we just create or evaluate a new manipulate cell? */ + var cell_manip = get_element("cell-manipulate-" + id); + /* If so, trigger it so that we see the evaluated version + of the manipulate cell. */ + if (cell_manip) { + manipulate(id, 'sage.server.notebook.manipulate.state[' + id + ']["function"]()'); + } } if (status == 'd') { cell_set_done(id); - // TODO: should make this not case sensitive!! how to .lower() in javascript? - if (text.indexOf('class="math"') != -1 || text.indexOf("class='math'") != -1) { + if (contains_jsmath(text)) { try { /* jsMath.Process(cell_output); */ /* jsMath.ProcessBeforeShowing(cell_output_nowrap); */ @@ -2058,6 +2092,7 @@ function slide_mode() { slide_show(); } + function cell_mode() { in_slide_mode = false; set_class('left_pane', 'pane'); diff -r 6f73ec3c4772 -r 2c559ef52454 sage/server/notebook/manipulate.py --- a/sage/server/notebook/manipulate.py Tue Mar 04 01:23:37 2008 -0600 +++ b/sage/server/notebook/manipulate.py Tue Mar 04 05:23:01 2008 -0600 @@ -20,16 +20,30 @@ TODO: [X] get sliders to work; values after move slider [x] default values - [ ] get everything in the current version to work 100% bug free (including some style). post bundle. + [x] get everything in the current version to work 100% bug free (including some style). post bundle. BUGS: [x] have default values set from the get go [x] spacing around sliders; also need to have labels [x] when re-evaluate input, make sure to clear output so cell-manipulate-id div is gone. - [ ] if you use a manipulate control after restarting, doesn't work. Need to reset it. How? [x] two manipulates in one cell -- what to do? - [ ] display html parts of output as html + [x] draw initial state + [x] make manipulate canvas resizable + [x] if you use a manipulate control after restarting, doesn't work. Need to reset it. How? + (to finish -- fix the error message in js.py: + /* TODO: Make error message more distinct! */ + if (new_manip_output.indexOf('KeyError: '+id) != -1) { + evaluate_cell(id, 0); + new_manip_output = ""; + } + [x] display html parts of output as html - [ ] implement in non-word wrap mode too. + [x] NO -- autoswitch to 1-cell mode: + put slide_mode(); jump_to_slide(%s); in wrap_in_outside_frame + but feals all wrong. + + [ ] completely get rid of left clicking to switch wrap mode for + manipulate objects: always in word wrap mode! + [ ] implement a color object [ ] cool looking sliders: http://jqueryfordesigners.com/demo/slider-gallery.html @@ -38,7 +52,7 @@ PLANS and IDEAS: [ ] automagically determine the type of control from the default value of the variable. Here is how this will work: * u blank input field - * u = (umin,umax) slider; umin must not be a string + * u = (umin,umax) slider; umin must not be a sequence * u = (umin,umax,du) discrete slider * u = [1,2,3,4] setter bar: automatically when there are are most 5 elements; otherwise a drop down menu @@ -105,9 +119,18 @@ def foo(x,y): ... """ +from sage.misc.all import srange, sage_eval + import inspect + +# Module scope variable that is always set equal to +# the current cell id (of the executing cell). + SAGE_CELL_ID = 0 + +# Dictionary that stores the state of all evaluated +# manipulate cells. state = {} _k = 0 @@ -175,16 +198,27 @@ class ManipulateControl: def default_value(self): return self.__default_value - def adapt_user_input(self): + def adapt_number(self): """ Return string representation of function that is called to adapt the values of this control to Python. """ - state[self.cell_id()]['adapt'][self.__adapt_number] = self._adapt_user_input - return 'sage.server.notebook.manipulate.state[%s][\\"adapt\\"][%s]'%(self.cell_id(), self.__adapt_number) + return self.__adapt_number - def _adapt_user_input(self, x): - return x + def _adaptor(self, value, globs): + """ + Adapt a user input, which is a string, to be an element selected + by this control. + + INPUT: + value -- the string the user typed in + globs -- the globals interpreter variables, e.g., + globals(), which is useful for evaling value. + + OUTPUT: + object + """ + return sage_eval(value, globs) def manipulate(self): """ @@ -195,18 +229,12 @@ class ManipulateControl: OUTPUT: string -- that is meant to be evaluated in Javascript """ - s = 'manipulate(%s, "sage.server.notebook.manipulate.state[%s][\\"variables\\"][\\"%s\\"]=sage_eval(r\\"\\"\\"%s("+%s+")\\"\\"\\", globals())\\n%s()");'%( - self.cell_id(), self.cell_id(), self.var(), self.adapt_user_input(), self.value(), self.function_name()) + # The following is a crazy line to read because of all the backslashes and try/except. + # All it does is run the manipulate function once after setting exactly one + # dynamic variable. If setting the dynamic variable fails, due to a KeyError + s = 'manipulate(%s, "sage.server.notebook.manipulate.update(%s, \\"%s\\", %s, \\""+%s+"\\", globals())")'%( + self.cell_id(), self.cell_id(), self.var(), self.adapt_number(), self.value_js()) return s - - def function_name(self): - """ - Returns the name of the function that this control manipulates. - - OUTPUT: - string -- name of a function as a string - """ - return 'sage.server.notebook.manipulate.state[%s][\\"function\\"]'%(self.cell_id()) def var(self): """ @@ -233,7 +261,7 @@ class InputBox(ManipulateControl): def __repr__(self): return "A InputBox manipulate control" - def value(self): + def value_js(self): """ Return javascript string that will give the value of this control element. @@ -251,7 +279,7 @@ class InputBox(ManipulateControl): string -- html format """ return """ - %s: + %s: """%(self.var(), self.default_value(), self.manipulate()) class Slider(ManipulateControl): @@ -276,7 +304,7 @@ class Slider(ManipulateControl): """ return self.__default_position - def value(self): + def value_js(self): """ Return javascript string that will give the value of this control element. @@ -286,10 +314,21 @@ class Slider(ManipulateControl): """ return "position" - def _adapt_user_input(self, position): - # Input position is a string that evals to a float between 0 and 100. - # we translate it into an index into self.__values + def _adaptor(self, position, globs): + """ + Adapt a user input, which is the slider position, to be an + element selected by this control. + + INPUT: + position -- position of the slider + globs -- the globals interpreter variables (not used here). + + OUTPUT: + object + """ v = self.__values + # We have to cast to int, since it comes back as a float that + # is too big. return v[int(position)] def render(self): @@ -336,11 +375,11 @@ class ManipulateCanvas: """ return """
- -
+ + - +
"""%self.cell_id() @@ -356,8 +395,15 @@ class ManipulateCanvas: return ''.join([c.render() for c in self.__controls]) def wrap_in_outside_frame(self, inside): - #return "
%s
"%inside - return "
%s
"%inside + return """
+
%s
+ """%(self.cell_id(), inside) +## def render(self): """ @@ -389,16 +435,16 @@ def manipulate(f): n = len(args) - len(defaults) controls = [automatic_control(f, args[i], defaults[i-n] if i >= n else None) for i in range(len(args))] - #controls = [automatic_control(f, v) for v in args[:n]] + \ - # [defaults[i].render(f, args[i+n]) for i in range(len(defaults))] C = ManipulateCanvas(controls, SAGE_CELL_ID) d = {} - state[SAGE_CELL_ID] = {'variables':d, 'adapt':{}} + ad = {} + state[SAGE_CELL_ID] = {'variables':d, 'adapt':ad} for con in controls: d[con.var()] = con.default_value() + ad[con.adapt_number()] = con._adaptor html(C.render()) @@ -406,7 +452,7 @@ def manipulate(f): return f(*[d[args[i]] for i in range(len(args))]) state[SAGE_CELL_ID]['function'] = g - + return g @@ -480,6 +526,36 @@ def automatic_control(f, v, default): C = default elif isinstance(default, list): C = slider(default) + elif isinstance(default, tuple): + if len(default) == 2: + if isinstance(default[0], list): + C = slider(default[0], default=default[1]) + else: + C = slider(range(default[0], default[1])) + elif len(default) == 3: + C = slider(srange(default[0], default[1], default[2])) + else: + C = slider(list(default)) else: C = input_box(default) return C.render(f, v) + + +def update(cell_id, var, adapt, value, globs): + """ + INPUT: + cell_id -- the id of a manipulate cell + var -- a variable associated to that cell + adapt -- the number of the adapt function + """ + try: + S = state[cell_id] + except KeyError: + print "__SAGE_MANIPULATE_RESTART__" + else: + # Look up the function that adapts inputs to have the right type + adapt_function = S["adapt"][adapt] + # Apply that function and save the result in the appropriate variables dictionary. + S["variables"][var] = adapt_function(value, globs) + # Finally call the manipulatable function, which will use the above variables. + S['function']()