Ticket #1322: manipulate_take3_part5.patch

File manipulate_take3_part5.patch, 18.9 KB (added by William Stein, 15 years ago)
  • sage/server/notebook/cell.py

    # HG changeset patch
    # User William Stein <wstein@gmail.com>
    # Date 1204629781 21600
    # Node ID 2c559ef52454cb440bdf42bcb5fd3bffe4972a12
    # Parent  6f73ec3c47727f583703041009c39a6b12f1a27a
    #1322 -- sage manipulate command -- finally got something pretty usable (!)
    PHG: user: William Stein <wstein@gmail.com>
    
    diff -r 6f73ec3c4772 -r 2c559ef52454 sage/server/notebook/cell.py
    a b class Cell(Cell_generic): 
    305305            self.__version = 1+self.version()
    306306            return
    307307        elif self.is_manipulating():
    308             del self.manipulate
     308            try:
     309                del self.manipulate
     310                del self._manipulate_output
     311            except AttributeError:
     312                pass
    309313           
    310314        self.__version = 1+self.version()
    311315        self.__in = input
    class Cell(Cell_generic): 
    408412                try:
    409413                    # Fill in the output template
    410414                    output,html = self._manipulate_output
    411                     z = z.replace('<?TEXT>', output.replace('<','&lt;'))
     415                    output = self.parse_html(output, ncols)
     416                    z = z.replace('<?TEXT>', output)
    412417                    z = z.replace('<?HTML>', html)
    413418                    return z
    414419                except (ValueError, AttributeError), msg:
    class Cell(Cell_generic): 
    429434            return s
    430435
    431436        if html:
    432             def format(x):
    433                 return word_wrap(x.replace('<','&lt;'), ncols=ncols)
     437            s = self.parse_html(s, ncols)
    434438
    435             def format_html(x):
    436                 x = self.process_cell_urls(x)
    437                 return x
     439        if not is_manipulate and not self.is_html() and len(s.strip()) > 0:
     440            s = '<pre class="shrunk">' + s.strip('\n') + '</pre>'
     441        return s.strip('\n')
     442
     443    def parse_html(self, s, ncols):
     444        def format(x):
     445            return word_wrap(x.replace('<','&lt;'), ncols=ncols)
     446
     447        def format_html(x):
     448            return self.process_cell_urls(x)
     449
     450        # if there is an error in the output,
     451        # specially format it.
     452        s = format_exception(s, ncols)
     453
     454        # Everything not wrapped in <html> ... </html>
     455        # should have the <'s replaced by &lt;'s
     456        # and be word wrapped.
     457        t = ''
     458        while len(s) > 0:
     459            i = s.find('<html>')
     460            if i == -1:
     461                t += format(s)
     462                break
     463            j = s.find('</html>')
     464            if j == -1:
     465                t += format(s[:i])
     466                break
     467            t += format(s[:i]) + format_html(s[i+6:j])
     468            s = s[j+7:]
     469        t = t.replace('</html>','')
     470        return t
    438471       
    439             # if there is an error in the output,
    440             # specially format it.
    441             s = format_exception(s, ncols)
    442 
    443             # Everything not wrapped in <html> ... </html>
    444             # should have the <'s replaced by &lt;'s
    445             # and be word wrapped.
    446             t = ''
    447             while len(s) > 0:
    448                 i = s.find('<html>')
    449                 if i == -1:
    450                     t += format(s)
    451                     break
    452                 j = s.find('</html>')
    453                 if j == -1:
    454                     t += format(s[:i])
    455                     break
    456                 t += format(s[:i]) + format_html(s[i+6:j])
    457                 s = s[j+7:]
    458             s = t
    459             if not is_manipulate and not self.is_html() and len(s.strip()) > 0:
    460                 s = '<pre class="shrunk">' + s.strip('\n') + '</pre>'
    461 
    462         return s.strip('\n')
    463472
    464473    def has_output(self):
    465474        return len(self.__out.strip()) > 0
    class Cell(Cell_generic): 
    552561            wrap = 68
    553562            div_wrap = 68
    554563        key = (wrap,div_wrap,do_print)
    555         #try:
    556         #    return self._html_cache[key]
    557         #except KeyError:
    558         #    pass
    559         #except AttributeError:
    560         #    self._html_cache = {}
    561564
    562         if self.__in.lstrip()[:8] == '%hideall':
    563             #self._html_cache[key] = ''
    564             return ''
    565        
    566565        if wrap is None:
    567566            wrap = self.notebook().conf()['word_wrap_cols']
    568567        if self.worksheet().compute_process_has_been_started():
    class Cell(Cell_generic): 
    577576        html_in  = self.html_in(do_print=do_print)
    578577        introspect = "<div id='introspect_div_%s' class='introspection'></div>"%self.id()
    579578        html_out = self.html_out(wrap, do_print=do_print)
    580         s = html_in  + introspect + html_out
     579
     580        if self.__in.lstrip()[:8] == '%hideall':
     581            s = html_out
     582        else:
     583            s = html_in  + introspect + html_out
    581584
    582585        if div_wrap:
    583586            s = '\n\n<div id="cell_outer_%s" class="cell_visible"><div id="cell_%s" class="%s">'%(self.id(), self.id(), cls) + s + '</div></div>'
  • sage/server/notebook/js.py

    diff -r 6f73ec3c4772 -r 2c559ef52454 sage/server/notebook/js.py
    a b function evaluate_cell_callback(status,  
    17571757}
    17581758
    17591759function cell_output_set_type(id, typ, do_async) {
     1760    /* We do this specifically because manipulate cells do not work at all when
     1761       displayed in nowrap mode, which is VERY BAD.  So instead for manipulates
     1762       one gets a toggle to and from hidden.
     1763    */
     1764    if (typ=="nowrap" && get_element("cell-manipulate-" + id)) {
     1765        /* if the type is nowrap and the cell-manipulate-[id] div exists (i.e., we are manipulating)
     1766           then just make the thing hidden. */
     1767        typ = "hidden";
     1768    }
     1769
     1770    /* OK, now set the sell output type.  */
     1771   
    17601772    set_class('cell_div_output_' + id,    'cell_div_output_' + typ)
    17611773    set_class('cell_output_' + id,        'cell_output_' + typ)
    17621774    set_class('cell_output_nowrap_' + id, 'cell_output_nowrap_' + typ)
    function cancel_update_check() { 
    18391851    document.title = original_title;
    18401852}
    18411853
     1854function contains_jsmath(text) {
     1855    // TODO: should make this not case sensitive!!  how to .lower() in javascript?
     1856    return (text.indexOf('class="math"') != -1 || text.indexOf("class='math'") != -1);
     1857}
     1858
    18421859function set_output_text(id, text, wrapped_text, output_html, status, introspect_html, no_manip) {
    18431860    var cell_manip = get_element("cell-manipulate-" + id);
    18441861    if (!no_manip && cell_manip) {
    function set_output_text(id, text, wrapp 
    18491866            return;
    18501867        }
    18511868        var new_manip_output = wrapped_text.slice(i+8,j);
    1852         cell_manip.innerHTML = new_manip_output;
     1869       
     1870        /* An error occured accessing the data for this cell.  Just force reload
     1871           of the cell, which will certainly define that data. */
     1872        if (new_manip_output.indexOf('__SAGE_MANIPULATE_RESTART__') != -1) {
     1873            evaluate_cell(id, 0);
     1874        } else {
     1875            cell_manip.innerHTML = new_manip_output;
     1876            if (contains_jsmath(new_manip_output)) {       
     1877                jsMath.ProcessBeforeShowing(cell_manip);
     1878            }
     1879        }
    18531880    } else {
    18541881        /* fill in output text got so far */
    18551882        var cell_output = get_element('cell_output_' + id);
    function set_output_text(id, text, wrapp 
    18591886        cell_output.innerHTML = wrapped_text;
    18601887        cell_output_nowrap.innerHTML = text;
    18611888        cell_output_html.innerHTML = output_html;
     1889
     1890        /* Did we just create or evaluate a new manipulate cell? */
     1891        var cell_manip = get_element("cell-manipulate-" + id);
     1892        /* If so, trigger it so that we see the evaluated version
     1893           of the manipulate cell. */
     1894        if (cell_manip) {
     1895            manipulate(id, 'sage.server.notebook.manipulate.state[' + id + ']["function"]()');
     1896        }
    18621897    }
    18631898
    18641899    if (status == 'd') {
    18651900         cell_set_done(id);
    1866          // TODO: should make this not case sensitive!!  how to .lower() in javascript?
    1867          if (text.indexOf('class="math"') != -1 || text.indexOf("class='math'") != -1) {
     1901         if (contains_jsmath(text)) {
    18681902             try {
    18691903                 /* jsMath.Process(cell_output); */
    18701904                 /* jsMath.ProcessBeforeShowing(cell_output_nowrap); */
    function slide_mode() { 
    20582092    slide_show();
    20592093}
    20602094
     2095
    20612096function cell_mode() {
    20622097    in_slide_mode = false;
    20632098    set_class('left_pane', 'pane');
  • sage/server/notebook/manipulate.py

    diff -r 6f73ec3c4772 -r 2c559ef52454 sage/server/notebook/manipulate.py
    a b TODO: 
    2020   [X] get sliders to work; values after move slider
    2121
    2222   [x] default values
    23    [ ] get everything in the current version to work 100% bug free (including some style). post bundle.
     23   [x] get everything in the current version to work 100% bug free (including some style). post bundle.
    2424       BUGS:
    2525          [x] have default values set from the get go
    2626          [x] spacing around sliders; also need to have labels 
    2727          [x] when re-evaluate input, make sure to clear output so cell-manipulate-id div is gone.
    28           [ ] if you  use a manipulate control after restarting, doesn't work.   Need to reset it.  How?
    2928          [x] two manipulates in one cell -- what to do?
    30           [ ] display html parts of output as html
     29          [x] draw initial state
     30          [x] make manipulate canvas resizable
     31          [x] if you  use a manipulate control after restarting, doesn't work.   Need to reset it.  How?
     32                (to finish -- fix the error message in js.py:
     33                    /* TODO: Make error message more distinct! */
     34                    if (new_manip_output.indexOf('KeyError: '+id) != -1) {
     35                        evaluate_cell(id, 0);
     36                        new_manip_output = "";
     37                    }
     38          [x] display html parts of output as html
    3139
    32    [ ] implement in non-word wrap mode too.
     40   [x] NO -- autoswitch to 1-cell mode:
     41           put                  slide_mode(); jump_to_slide(%s);   in wrap_in_outside_frame
     42        but feals all wrong.     
     43
     44   [ ] completely get rid of left clicking to switch wrap mode for
     45           manipulate objects: always in word wrap mode!
     46           
    3347   [ ] implement a color object
    3448   [ ] cool looking sliders:
    3549        http://jqueryfordesigners.com/demo/slider-gallery.html
    PLANS and IDEAS: 
    3852   [ ] automagically determine the type of control from the default
    3953       value of the variable.  Here is how this will work:
    4054        * u                      blank input field
    41         * u = (umin,umax)        slider; umin must not be a string
     55        * u = (umin,umax)        slider; umin must not be a sequence
    4256        * u = (umin,umax,du)     discrete slider
    4357        * u = [1,2,3,4]          setter bar: automatically when there are are most 5
    4458                                 elements; otherwise a drop down menu
    def foo(x,y): 
    105119    ...
    106120"""
    107121
     122from sage.misc.all import srange, sage_eval
     123
    108124import inspect
    109125
     126
     127# Module scope variable that is always set equal to
     128# the current cell id (of the executing cell).
     129
    110130SAGE_CELL_ID = 0
     131
     132# Dictionary that stores the state of all evaluated
     133# manipulate cells.
    111134state = {}
    112135
    113136_k = 0
    class ManipulateControl: 
    175198    def default_value(self):
    176199        return self.__default_value
    177200
    178     def adapt_user_input(self):
     201    def adapt_number(self):
    179202        """
    180203        Return string representation of function that is called to
    181204        adapt the values of this control to Python.
    182205        """
    183         state[self.cell_id()]['adapt'][self.__adapt_number] = self._adapt_user_input
    184         return 'sage.server.notebook.manipulate.state[%s][\\"adapt\\"][%s]'%(self.cell_id(), self.__adapt_number)
     206        return self.__adapt_number
    185207
    186     def _adapt_user_input(self, x):
    187         return x
     208    def _adaptor(self, value, globs):
     209        """
     210        Adapt a user input, which is a string, to be an element selected
     211        by this control.
     212
     213        INPUT:
     214            value -- the string the user typed in
     215            globs -- the globals interpreter variables, e.g.,
     216                     globals(), which is useful for evaling value.
     217
     218        OUTPUT:
     219            object
     220        """
     221        return sage_eval(value, globs)
    188222       
    189223    def manipulate(self):
    190224        """
    class ManipulateControl: 
    195229        OUTPUT:
    196230            string -- that is meant to be evaluated in Javascript
    197231        """
    198         s = 'manipulate(%s, "sage.server.notebook.manipulate.state[%s][\\"variables\\"][\\"%s\\"]=sage_eval(r\\"\\"\\"%s("+%s+")\\"\\"\\", globals())\\n%s()");'%(
    199             self.cell_id(), self.cell_id(), self.var(), self.adapt_user_input(), self.value(), self.function_name())
     232        # The following is a crazy line to read because of all the backslashes and try/except.
     233        # All it does is run the manipulate function once after setting exactly one
     234        # dynamic variable.    If setting the dynamic variable fails, due to a KeyError
     235        s = 'manipulate(%s, "sage.server.notebook.manipulate.update(%s, \\"%s\\", %s, \\""+%s+"\\", globals())")'%(
     236            self.cell_id(), self.cell_id(), self.var(), self.adapt_number(), self.value_js())
    200237        return s
    201 
    202     def function_name(self):
    203         """
    204         Returns the name of the function that this control manipulates.
    205 
    206         OUTPUT:
    207             string -- name of a function as a string
    208         """
    209         return 'sage.server.notebook.manipulate.state[%s][\\"function\\"]'%(self.cell_id())
    210238
    211239    def var(self):
    212240        """
    class InputBox(ManipulateControl): 
    233261    def __repr__(self):
    234262        return "A InputBox manipulate control"
    235263
    236     def value(self):
     264    def value_js(self):
    237265        """
    238266        Return javascript string that will give the
    239267        value of this control element.
    class InputBox(ManipulateControl): 
    251279             string -- html format
    252280        """
    253281        return """
    254         %s: <input type='text' value='%r' width=100%% onchange='%s'></input>
     282        %s: <input type='text' value='%r' width=200px onchange='%s'></input>
    255283        """%(self.var(), self.default_value(),  self.manipulate())
    256284
    257285class Slider(ManipulateControl):
    class Slider(ManipulateControl): 
    276304        """
    277305        return self.__default_position
    278306
    279     def value(self):
     307    def value_js(self):
    280308        """
    281309        Return javascript string that will give the
    282310        value of this control element.
    class Slider(ManipulateControl): 
    286314        """
    287315        return "position"
    288316   
    289     def _adapt_user_input(self, position):
    290         # Input position is a string that evals to a float between 0 and 100.
    291         # we translate it into an index into self.__values
     317    def _adaptor(self, position, globs):
     318        """
     319        Adapt a user input, which is the slider position, to be an
     320        element selected by this control.
     321
     322        INPUT:
     323            position -- position of the slider
     324            globs -- the globals interpreter variables (not used here).
     325
     326        OUTPUT:
     327            object
     328        """
    292329        v = self.__values
     330        # We have to cast to int, since it comes back as a float that
     331        # is too big.
    293332        return v[int(position)]
    294333
    295334    def render(self):
    class ManipulateCanvas: 
    336375        """
    337376        return """
    338377        <div id='cell-manipulate-%s'><?START>
    339         <table border=1 bgcolor='#dcdcdc' cellpadding=3 width=100%% height=100%%>
    340         <tr><td bgcolor=white>
     378        <table border=0 bgcolor='#white' width=100%% height=100%%>
     379        <tr><td bgcolor=white align=center valign=top>
    341380          <?TEXT>
    342381        </td></tr>
    343         <tr><td><?HTML></td></tr>
     382        <tr><td  align=center valign=top><?HTML></td></tr>
    344383        </table>
    345384        </td></tr></table><?END></div>
    346385        """%self.cell_id()
    class ManipulateCanvas: 
    356395        return ''.join([c.render() for c in self.__controls])
    357396
    358397    def wrap_in_outside_frame(self, inside):
    359         #return "<table bgcolor='#c5c5c5' width=100%% height=100%% cellpadding=5><tr><td bgcolor='#f9f9f9'>%s</td></tr></table>"%inside
    360         return "<table bgcolor='#c5c5c5' width=800px height=400px cellpadding=5><tr><td bgcolor='#f9f9f9'>%s</td></tr></table>"%inside
     398        return """<div padding=6 id='div-manipulate-%s'> <table bgcolor='#c5c5c5'
     399                 width=600px height=400px cellpadding=15><tr><td bgcolor='#f9f9f9' valign=top align=center>%s</td>
     400                 </tr></table></div>
     401                 """%(self.cell_id(), inside)
     402##                  <script>
     403##                  setTimeout(function() {
     404##                  $('#div-manipulate-%s').resizable();
     405##                  $('#div-manipulate-%s').draggable();
     406##                  }, 1);</script>
    361407
    362408    def render(self):
    363409        """
    def manipulate(f): 
    389435
    390436    n = len(args) - len(defaults)
    391437    controls = [automatic_control(f, args[i], defaults[i-n] if i >= n else None) for i in range(len(args))]
    392     #controls = [automatic_control(f, v) for v in args[:n]] + \
    393     #           [defaults[i].render(f, args[i+n]) for i in range(len(defaults))]
    394438
    395439    C = ManipulateCanvas(controls, SAGE_CELL_ID)
    396440
    397441    d = {}
    398     state[SAGE_CELL_ID] = {'variables':d, 'adapt':{}}
     442    ad = {}
     443    state[SAGE_CELL_ID] = {'variables':d, 'adapt':ad}
    399444
    400445    for con in controls:
    401446        d[con.var()] = con.default_value()
     447        ad[con.adapt_number()] = con._adaptor
    402448
    403449    html(C.render())
    404450
    def manipulate(f): 
    406452        return f(*[d[args[i]] for i in range(len(args))])
    407453
    408454    state[SAGE_CELL_ID]['function'] = g
    409    
     455
    410456    return g   
    411457
    412458
    def automatic_control(f, v, default): 
    480526        C = default
    481527    elif isinstance(default, list):
    482528        C = slider(default)
     529    elif isinstance(default, tuple):
     530        if len(default) == 2:
     531            if isinstance(default[0], list):
     532                C = slider(default[0], default=default[1])
     533            else:
     534                C = slider(range(default[0], default[1]))
     535        elif len(default) == 3:
     536            C = slider(srange(default[0], default[1], default[2]))
     537        else:
     538            C = slider(list(default))
    483539    else:
    484540        C = input_box(default)
    485541    return C.render(f, v)
     542
     543
     544def update(cell_id, var, adapt, value, globs):
     545    """
     546    INPUT:
     547        cell_id -- the id of a manipulate cell
     548        var -- a variable associated to that cell
     549        adapt -- the number of the adapt function
     550    """
     551    try:
     552        S = state[cell_id]
     553    except KeyError:
     554        print "__SAGE_MANIPULATE_RESTART__"
     555    else:
     556        # Look up the function that adapts inputs to have the right type
     557        adapt_function = S["adapt"][adapt]
     558        # Apply that function and save the result in the appropriate variables dictionary.
     559        S["variables"][var] = adapt_function(value, globs)
     560        # Finally call the manipulatable function, which will use the above variables.
     561        S['function']()