# HG changeset patch # User William Stein # Date 1204447690 21600 # Node ID 48f2d5de9355c29c0d5f477db1d137f8def9bca4 # Parent 9ebe670b72be0ab93433932e9b70cb1b6b9faec1 Trac #1322 -- manipulate -- new version (still first prototype) diff -r 9ebe670b72be -r 48f2d5de9355 sage/server/notebook/all.py --- a/sage/server/notebook/all.py Tue Jan 08 00:05:53 2008 -0800 +++ b/sage/server/notebook/all.py Sun Mar 02 02:48:10 2008 -0600 @@ -14,4 +14,4 @@ from notebook_object import notebook, in from sagetex import sagetex -from manipulate import manipulate +from manipulate import manipulate, text_box, slider diff -r 9ebe670b72be -r 48f2d5de9355 sage/server/notebook/jquery.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sage/server/notebook/jquery.py Sun Mar 02 02:48:10 2008 -0600 @@ -0,0 +1,22 @@ +import manipulate + +def javascript(s): + print ''%s + +def cell_id(): + return manipulate.SAGE_CELL_ID + +def draggable(option=''): + """ + INPUT: + option -- string + "" (default) -- Creates new draggables on the current cell. + "enable" -- Enable the draggable functionality. + "disable" -- Temporarily disable the draggable functionality. + """ + s = '$("#cell_outer_%s").draggable("%s")'%(cell_id(), option) + javascript(s) + +def resizable(): + s = '$("#cell_outer_%s").resizable()'%cell_id() + javascript(s) diff -r 9ebe670b72be -r 48f2d5de9355 sage/server/notebook/js.py --- a/sage/server/notebook/js.py Tue Jan 08 00:05:53 2008 -0800 +++ b/sage/server/notebook/js.py Sun Mar 02 02:48:10 2008 -0600 @@ -2497,10 +2497,10 @@ function show_help_window(worksheet) { /////////////////////////////////////////////////////////////////// -// Dynamic / Manipulate +// Manipulate /////////////////////////////////////////////////////////////////// -function dynamic(id, input) { +function manipulate(id, input) { active_cell_list = active_cell_list.concat([id]); async_request(worksheet_command('eval'), evaluate_cell_callback, 'newcell=0' + '&id=' + id + '&input='+escape0('%manipulate\n' + input)); diff -r 9ebe670b72be -r 48f2d5de9355 sage/server/notebook/manipulate.py --- a/sage/server/notebook/manipulate.py Tue Jan 08 00:05:53 2008 -0800 +++ b/sage/server/notebook/manipulate.py Sun Mar 02 02:48:10 2008 -0600 @@ -1,23 +1,301 @@ SAGE_CELL_ID=0 -SAGE_CELL_ID=0 +r""" +Manipulate Sage functions in the notebook -def _text_box(f, var): - print """ - -
- - -
-
- - """%(var, SAGE_CELL_ID, var, f.__name__) +This module implements a manipulate decorator for function in the Sage +notebook. +The controls are: +\begin{itemize} + \item TextBox -- a text box + \item Slider -- a slider +\end{itemize} + +AUTHOR: + -- William Stein (2008-03-02): initial version at Sage/Enthought + Days 8 in Texas + -- Jason Grout (2008-03): collaborators substantially on the + design and prototypes. + +TODO/PLAN: + [ ] sliders + [ ] default value + [ ] more widgets + [ ] better widget layout controls + [ ] 100% doctest coverage +""" + +import inspect + +SAGE_CELL_ID = 0 +vars = {} + +def html(s): + """ + Render the input string s in a form that tells the notebook + to display it in the HTML portion of the output. + + INPUT: + s -- a string + + OUTPUT: + string -- html format + """ + print "%s"%s + +class ManipulateControl: + """ + Base class for manipulate controls. + """ + def __init__(self, f, var): + """ + Create a new manipulate control. + + INPUT: + f -- a Python function (that's being decorated) + var -- name of variable that this control manipulates + SAGE_CELL_ID -- uses this global variable + """ + self.__var = var + self.__cell_id = SAGE_CELL_ID + self.__f = f + + def __repr__(self): + return "A ManipulateControl (abstract base class)" + + def manipulate(self): + """ + Return a string that when evaluated in Javascript calls the + javascript manipulate function with appropriate inputs for + this control. + + OUTPUT: + string -- that is meant to be evaluated in Javascript + """ + return 'manipulate(%s, "sage.server.notebook.manipulate.vars[%s][\\"%s\\"]=sage_eval(r\\"\\"\\""+%s+"\\"\\"\\", globals())\\n%s()");'%( + self.cell_id(), self.cell_id(), self.var(), self.value(), self.function_name()) + + def function_name(self): + """ + Returns the name of the function that this control manipulates. + + OUTPUT: + string -- name of a function as a string + """ + return self.__f.__name__ + + def var(self): + """ + Return the name of the variable that this control manipulates. + + OUTPUT: + string -- name of a variable as a string. + """ + return self.__var + + def cell_id(self): + """ + Return the id of the cell that contains this manipulate control. + + OUTPUT: + integer -- id of cell that this control manipulates + """ + return self.__cell_id + +class TextBox(ManipulateControl): + """ + A text box manipulate control. + """ + def __repr__(self): + return "A TextBox manipulate control" + + def value(self): + """ + Return javascript string that will give the + value of this control element. + + OUTPUT: + string -- javascript + """ + return "this.value" + + def render(self): + """ + Render this control as a string. + + OUTPUT: + string -- html format + """ + return """ + %s: + """%(self.var(), self.var(), self.manipulate()) + +class Slider(ManipulateControl): + """ + A slider manipulate control. + """ + def __init__(self, f, var, values): + """ + Create a slider manipulate control that takes on the given + list of values. + """ + ManipulateControl.__init__(self, f, var) + self.__values = values + + def __repr__(self): + return "A Slider manipulate control" + + def value(self): + """ + Return javascript string that will give the + value of this control element. + + OUTPUT: + string -- javascript + """ + return "this.value" + + def render(self): + """ + Render this control as a string. + + OUTPUT: + string -- html format + """ + return """ + SLIDER %s: + """%(self.var(), self.var(), self.manipulate()) + + +class ManipulateCanvas: + """ + Base class for manipulate canvases. This is where all the controls + along with the output of the manipulated function are layed out + and rendered. + """ + def __init__(self, controls): + """ + Create a manipulate canvas. + + INPUT: + controls -- a list of ManipulateControl instances. + """ + self.__controls = controls + + def render_output(self): + """ + Render in text (html) form the output portion of the manipulate canvas. + + The output contains two special tags, and , + which get replaced at runtime by the text and html parts + of the output of running the function. + + OUTPUT: + string -- html + """ + + return """ +
+ + + +
+
+ """ + + def render_controls(self): + """ + Render in text (html) form all the input controls. + + OUTPUT: + string -- html + """ + # This will need some sophisticated layout querying of the c's, maybe. + return ''.join([c.render() for c in self.__controls]) + + def render(self): + """ + Render in text (html) the entire manipulate canvas. + + OUTPUT: + string -- html + """ + return "%s%s"%(self.render_controls(), self.render_output()) + + def manipulate(f): """ - Decorate a function f to make a manipulatable version of f. + Decorate a function f to make a manipulate version of f. + @manipulate + def foo(n,m): + ... """ - import inspect - vars = inspect.getargspec(f)[0] - print _text_box(f, vars[0]) + + (args, varargs, varkw, defaults) = inspect.getargspec(f) + + if defaults is None: + defaults = [] + + n = len(args) - len(defaults) + controls = [TextBox(f, v) for v in args[:n]] + \ + [defaults[i].render(f, args[i+n]) for i in range(len(defaults))] + + C = ManipulateCanvas(controls) + + vars[SAGE_CELL_ID] = {} + d = vars[SAGE_CELL_ID] + for v in args: + d[v] = '' + + html(C.render()) + def g(): - return f(eval(vars[0])) + return f(*[d[args[i]] for i in range(len(args))]) return g + + +###################################################### +# Actual control objects that the user passes in +###################################################### +class control: + pass + +class text_box(control): + def __init__(self, default): + """ + INPUT: + default -- string (the default value) + """ + self.__default = default + + def __repr__(self): + return "A manipulate text box control with default value '%s'"%self.__default + + def render(self, f, var): + """ + INPUT: + f -- a Python function + var -- a string (variable; one of the variable names input to f) + """ + return TextBox(f, var) + +class slider(control): + def __init__(self, vmin, vmax=None, steps=30): + if isinstance(vmin, list): + self.__values = vmin + else: + if vmax is None: + vmax = vmin + vmin = 0 + steps = int(steps) + if steps <= 0: + self.__values = [vmin, vmax] + else: + step = (vmax-vmin)/steps # hard coded + self.__values = [vmin + i*step for i in range(steps)] + [vmax] + + def __repr__(self): + return "A manipulate slider control [%s - %s]."%(min(self.__values), max(self.__values)) + + def render(self, f, var): + return Slider(f, var, self.__values) +