forked from sagemath/sagecell
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathinteract_old_sagenb.py
3773 lines (2839 loc) · 122 KB
/
interact_old_sagenb.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
# -*- coding: utf-8 -*
#############################################################################
# Copyright (C) 2008 William Stein <wstein@gmail.com>
# Distributed under the terms of the new BSD license
# See http://www.opensource.org/licenses/BSD-3-Clause for details
#############################################################################
# This file originally came from the Sage notebook. William said in a
# personal email dated 4 July 2011 that he agreed to relicense the
# file as new BSD, so I've changed the license above accordingly.
r"""
Interact Functions in the Notebook
This module implements an :func:`interact` function decorator for the Sage
notebook.
AUTHORS:
- William Stein (2008-03-02): version 1.0 at Sage/Enthought Days
8 in Texas
- Jason Grout (2008-03): discussion and first few prototypes
- Jason Grout (2008-05): :class:`input_grid` control
"""
"""
** PLANNING **
NOTES:
* There is no testing of pickling anywhere in this file. This is
because there is no reason one would ever pickle anything in this
file, since everything is associated with particular state
information of a notebook.
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-interact-id div is gone.
[x] two interacts in one cell -- what to do?
[x] draw initial state
[x] make interact canvas resizable
[x] if you use a interact control after restarting, doesn't work. Need to reset it. How?
[x] display html parts of output as html
[x] default slider pos doesn't work, eg. def _(q1=(-1,(-3,3)), q2=(1,(-3,3))):
[x] change from interact to interact everywhere.
[x] edit/save breaks interact mode
* picking up images that shouldn't.
* controls completely stop working.
[x] problems with html/pre/text formatting, e.g., in TEXT mode and in interact cells
[x] tab completion in interact broken formatting
[x] error exception reporting broken
[x] replace special %interact by something very obfuscated to keep from having
really weird mistakes that are hard for people to debug.
[x] cell order corruption
[x] cross-platform testing (good enough -- it's jquery)
[x] can't enter "foo" in input_box now because of how strings are
passed back and forth using single quotes.
[x] possible issue with page title being undefined; don't know why
or if that is connected with interactives
[x] autorunning interact cells on load is being injected into the
i/o pexpect stream way too early.
[x] what do published worksheets do??
VERSION 1:
[X] get sliders to work; values after move slider
[x] default values
[x] NO -- autoswitch to 1-cell mode:
put slide_mode(); jump_to_slide(%s); in wrap_in_outside_frame
but feels all wrong.
[x] completely get rid of left clicking to switch wrap mode for
interact objects: always in word wrap mode or hide.
[x] shortcut ('label', v)
[x] test saving and loading whole notebook to a file
[x] collection of about 20 good examples of how to use interact (doctested)
[x] interact(f) should also work; i.e., no need to use decorators -- done; won't be advertised, but
at least fixing this improves code quality.
[x] obfuscate ?START and ?END much more.
[x] type checked input box
[x] line up all the control in a single table so all labels and all
controls exactly match up
[x] button bar
[x] drop down menu
[x] checkbox
[x] color selector
[x] something to avoid server flood, e.g., any %interact request removes all other
such requests from the queue in worksheet.py
DOCS:
[x] 100% documentation and doctest coverage
[ ] put the docs for this in the reference manual
[ ] put summary doc in notebook help page
VERSION 2:
[ ] vertical scroll bars (maybe very easy via jsquery)
[ ] small version of color selector
[ ] button -- block of code gets run when it is clicked
[ ] when click a button in a button bar it is highlighted and
other buttons are not (via some sort of javascript)
[ ] much more fine control over style of all controls
[ ] safe/secure evaluation mode
[ ] slider is too narrow -- need to expand to window width?
[ ] fix the flicker resize during update (hard???)
[ ] make option for text input that correctly gives something of
the same type as the default input.
[ ] matrix input control (method of matrix space) -- a spreadsheet like thing
[ ] a 2-D slider:
u = ((xmin,xmax),(ymin,ymax)) 2-D slider -- NOT IMPLEMENTED
[ ] locator in a 2-D graphic
[ ] tab_view -- represents an object in which clicking the tab
with label lbl[i] displays expr[i]
[ ] controls: make them easy to customize as below --
location -- where to put the slider (?)
align -- left, right, top, texttop, middle, absmiddle, baseline, bottom, absbottom
background -- the color of the background for the cell
frame -- draw a frame around
disabled -- disables the input element when it first loads
so that the user can not write text in it, or
select it.
editable -- bool
font_size -- integer
maxlength -- the maximum number of characters allowed in a text field.
name -- defines a unique name for the input element
size -- the size of the input element
type -- button, checkbox, file, password, radio, slider, text, setter_bar
VERSION 3:
[ ] protocol for objects to have their own interact function; make
it so for any object obj in sage, one can do
{{{
interact(obj)
}}}
and get something useful as a result.
[ ] flot -- some pretty but very simple javascript only graphics (maybe don't do... unclear)
[ ] zorn -- similar (maybe don't do... unclear)
[ ] color sliders (?):
u = color(...) a color slider
http://interface.eyecon.ro/demos/slider_colorpicker.html
http://jqueryfordesigners.com/demo/slider-gallery.html
[ ] tag_cell('foo') -- makes it so one can refer to the current cell
from elsewhere using the tag foo instead of the cell id number
This involves doing something with SAGE_CELL_ID and the cell_id() method.
[ ] framed -- put a frame around an object
"""
# Standard system libraries
from base64 import standard_b64encode, standard_b64decode
import inspect
import math
import types
# Sage libraries
from jsmath import math_parse
from sagenb.misc.misc import srange, sage_eval, Color, is_Matrix
# SAGE_CELL_ID is a module scope variable that is always set equal to
# the current cell id (of the executing cell). Code that sets this is
# inserted by the notebook server each time a worksheet cell is
# evaluated.
SAGE_CELL_ID = 0
# Dictionary that stores the state of all active interact cells.
state = {}
def reset_state():
"""
Reset the :func:`interact` state of this sage process.
EXAMPLES::
sage: sagenb.notebook.interact.state # random output
{1: {'function': <function g at 0x72aaab0>, 'variables': {'m': 3, 'n': 5}, 'adapt': {1: <bound method Slider._adaptor of Slider Interact Control: n [1--|1|---10].>, 2: <bound method Slider._adaptor of Slider Interact Control: m [1--|1|---10].>}}}
sage: from sagenb.notebook.interact import reset_state
sage: reset_state()
sage: sagenb.notebook.interact.state
{}
"""
global state
state = {}
_k = 0
def new_adapt_number():
"""
Return an integer, always counting up, and starting with 0. This
is used for saving the adapt methods for controls. An adapt
method is just a function that coerces data into some object,
e.g., makes sure the control always produces int's.
OUTPUT:
- an integer
EXAMPLES::
sage: sagenb.notebook.interact.new_adapt_number() # random output -- depends on when called
1
"""
global _k
_k += 1
return _k
def html(s):
"""
Print the input string ``s`` in a form that tells the notebook to
display it in the HTML portion of the output. This function has
no return value.
INPUT:
- ``s`` - a string
EXAMPLES::
sage: sagenb.notebook.interact.html('hello')
<html>hello</html>
"""
print "<html>%s</html>"%s
def html_slider(id, values, callback, steps, default=0, margin=0):
"""
Return the HTML representation of a jQuery slider.
INPUT:
- ``id`` - a string; the DOM ID of the slider (better be unique)
- ``values`` - a string; 'null' or JavaScript string containing
array of values on slider
- ``callback`` - a string; JavaScript that is executed whenever
the slider is done moving
- ``steps`` - an integer; number of steps from minimum to maximum
value
- ``default`` - an integer (default: 0); the default position of
the slider
- ``margin`` - an integer (default: 0); size of margin to insert
around the slider
OUTPUT:
- a string - HTML format
EXAMPLES:
We create a jQuery HTML slider. If you do the following in the
notebook you should obtain a slider that when moved pops up a
window showing its current position::
sage: from sagenb.notebook.interact import html_slider, html
sage: html(html_slider('slider-007', 'null', 'alert(position)', steps=5, default=2, margin=5))
<html>...</html>
"""
s = """<table><tr><td>
<div id="%s" style="margin:%spx; margin-left: 1.0em; margin-right: 1.0em; width: 15.0em;"></div>
</td>"""%(id,int(margin))
if values != "null":
s += '<td><font color="black" id="%s-lbl"></font></td>' % id
s += "</tr></table>"
# We now generate javascript that gets run after the above div
# gets inserted. This happens because of the setTimeout function
# below which gets passed an anonymous function.
s += """<script>(function(){ var values = %(values)s; setTimeout(function() {
$('#%(id)s').slider({
step: 1, min: 0, max: %(maxvalue)s, value: %(startvalue)s,
change: function (e,ui) { var position = ui.value; if(values!=null) $('#%(id)s-lbl').text(values[position]); %(callback)s; },
slide: function(e,ui) { if(values!=null) $('#%(id)s-lbl').text(values[ui.value]); }
});
if(values != null) $('#%(id)s-lbl').text(values[$('#%(id)s').slider('value')]);
}, 1); })();</script>"""%{'values': values, 'id': id, 'maxvalue': steps-1, 'startvalue': default, 'callback': callback}
# change 'change' to 'slide' and it changes the slider every time it moves;
# needs much more work to actually work, since server gets flooded by
# requests.
return s
def html_rangeslider(id, values, callback, steps, default_l=0, default_r=1, margin=0):
"""
Return the HTML representation of a jQuery range slider.
INPUT:
- ``id`` - a string; the DOM ID of the slider (better be unique)
- ``values`` - a string; 'null' or JavaScript string containing
array of values on slider
- ``callback`` - a string; JavaScript that is executed whenever
the slider is done moving
- ``steps`` - an integer; number of steps from minimum to maximum
value
- ``default_l`` - an integer (default: 0); the default position of
the left edge of the slider
- ``default_r`` - an integer (default: 1); the default position of
the right edge of the slider
- ``margin`` - an integer (default: 0); size of margin to insert
around the slider
OUTPUT:
- a string - HTML format
EXAMPLES:
We create a jQuery range slider. If you do the following in the
notebook you should obtain a slider that when moved pops up a
window showing its current position::
sage: from sagenb.notebook.interact import html_rangeslider, html
sage: html(html_rangeslider('slider-007', 'null', 'alert(pos[0]+", "+pos[1])', steps=5, default_l=2, default_r=3, margin=5))
<html>...</html>
"""
s = """<table><tr><td>
<div id="%s" style="margin:%spx; margin-left: 1.0em; margin-right: 1.0em; width: 20.0em;"></div>
</td></tr>"""%(id,int(margin))
if values != "null":
s += '<tr><td><font color="black" id="%s-lbl"></font></td></tr>' % id
s += "</table>"
# We now generate javascript that gets run after the above div
# gets inserted. This happens because of the setTimeout function
# below which gets passed an anonymous function.
s += """<script>(function()
{
var values = %s;
var pos = [%s, %s];
var sel = '#%s';
var updatePos = function()
{
pos[0]=$(sel).slider('values', 0);
pos[1]=$(sel).slider('values', 1);
if(values!=null) $(sel+'-lbl').text('('+values[pos[0]]+', '+values[pos[1]]+')');
};
setTimeout(function()
{
$(sel).slider(
{
range: true,
step: 1,
min: 0,
max: %s,
values: [%s, %s],
change: function(e,ui){ updatePos(); %s; },
slide: updatePos
});
updatePos();
}, 1);
})();</script>"""%(values, default_l, default_r, id, steps-1, default_l, default_r, callback)
# change 'change' to 'slide' and it changes the slider every time it moves;
# needs much more work to actually work, since server gets flooded by
# requests.
return s
def html_color_selector(id, change, input_change, default='000000',
widget='jpicker', hide_box=False):
"""
Return HTML representation of a jQuery color selector.
INPUT:
- ``id`` - an integer or string; an identifier (e.g., cell ID) for
this selector
- ``change`` - a string; JavaScript code to execute when the color
selector changes.
- ``default`` - a string (default: ``'000000'``); default color as
a 6-character HTML hexadecimal string.
- ``widget`` - a string (default: 'jpicker'); the color
selector widget to use; choices are 'colorpicker', 'jpicker' and
'farbtastic'
- ``hide_box`` - a boolean (default: False); whether to hide the
input box associated with the color selector widget
OUTPUT:
- a string - HTML that creates the slider.
EXAMPLES::
sage: sagenb.notebook.interact.html_color_selector(0, 'alert("changed")', '', default='0afcac')
'...<table>...jpicker...'
sage: sagenb.notebook.interact.html_color_selector(99, 'console.log(color);', '', default='fedcba', widget='colorpicker', hide_box=True)
'...<table>...colorpicker...'
"""
input_style = ''
if hide_box:
input_style = 'display: none;'
if widget == 'farbtastic':
# HTML
s = """
<table>
<tr>
<td>
<div id="%s-picker"></div>
</td>
<td>
<input type="text"
id="%s"
name="color"
onchange="%s; $(this).css({backgroundColor: this.value, color: ''}); $.farbtastic('#%s-picker').setColor(this.value);"
value="%s"
style="%s"/>
</td>
</tr>
</table>""" % (id, id, input_change, id, default, input_style)
# We now generate javascript that gets run after the above div
# gets inserted. This happens because of the setTimeout
# function below which gets passed an anonymous function.
# Figuring out the below took understanding jQuery much
# better, and took me surprisingly long, especially the part
# involving linkTo which sets the callback.
s += """<script>
setTimeout(function () {
var input = $('#%s'), picker = $.farbtastic('#%s-picker');
picker.linkTo('#%s').linkTo(function (color) {
if (input.val() !== color) {
input.val(color);
input.css({
backgroundColor: color,
color: picker.hsl[2] > 0.5 ? '#000000' : '#ffffff'
});
%s;
}
})
}, 1);
</script>""" % (id, id, id, change)
elif widget == 'colorpicker':
# HTML
select = 'background: url(/javascript/jquery/plugins/colorpicker/images/select.png)'
style1 = 'position: relative; width: 36px; height: 36px; %s;' % select
style2 = 'position: absolute; top: 3px; left: 3px; width: 30px; height: 30px; %s center; background-color: %s' % (select, default)
s = """
<table>
<tr>
<td>
<input type="text"
id="%s"
name="color"
onchange="%s; $(this).css({backgroundColor: this.value}); $('#%s-picker').ColorPickerSetColor(this.value.slice(1)); $('#%s-picker div').css({backgroundColor: this.value});"
value="%s"
style="%s" />
</td>
<td>
<div id="%s-picker" style="%s">
<div style="%s"></div>
</div>
</td>
</tr>
</table>""" % (id, input_change, id, id, default, input_style,
id, style1, style2)
# JS
s +="""<script>
setTimeout(function () {
var def = '%s'.slice(1), div = $('#%s-picker div'),
input = $('#%s'), picker = $('#%s-picker');
input.css({
backgroundColor: '%s',
// Should be good enough:
color: (parseInt(def.slice(0, 2), 16) + parseInt(def.slice(2, 4), 16) + parseInt(def.slice(4, 6), 16)) / 3 > 127 ? '#000000' : '#ffffff'
});
picker.ColorPicker({
color : '%s',
onShow : function (pkr) {
$(pkr).css({zIndex: '10'}).show();
return false;
},
onChange : function (hsb, hex, rgb) {
color = '#' + hex;
if (input.val() !== color) {
input.val(color);
input.css({
backgroundColor: color,
color: hsb.b > 50 ? '#000000' : '#ffffff'
});
div.css({backgroundColor: color});
%s;
}
},
onSubmit : function (hsb, hex, rgb, el) {
$(el).ColorPickerHide();
}
});
}, 1);
</script>""" % (default, id, id, id, default, default, change)
elif widget == 'jpicker':
# HTML.
s = """
<table>
<tr>
<td>
<input type="text"
id="%s"
name="color"
onchange="%s; $(this).css({backgroundColor: this.value}); $('.jPicker_HexText', $('#%s-picker').next()).val(this.value.slice(1)).trigger('keyup');"
value="%s"
style="%s" />
</td>
<td>
<div id="%s-picker"></div>
</td>
</tr>
</table>
""" % (id, input_change, id, default[1:], input_style, id)
# JS. Note: jPicker uses, e.g., 'abcdef' instead of '#abcdef'
# for bound input fields. To maintain consistency with the
# Farbtastic and ColorPicker implementations above, we do not
# simply call $('#%s').jPicker({...}).
s +="""<script>
setTimeout(function () {
var input = $('#%(id)s'), picker = $('#%(id)s-picker');
picker.jPicker(
// Settings.
{
window: {
expandable: true,
position: { x: 20, y: 20 },
title: 'Select a color'
},
color: {
active: new $.jPicker.Color({hex: '%(default)s'})
},
images: {
clientPath: '/javascript/jquery/plugins/jpicker/images/'
}
},
// commitCallback
function (color) {},
// liveCallback
function (color_arg) {
color = '#' + color_arg.val('hex');
if (input.val() !== color) {
input.val(color);
input.css({
backgroundColor: color,
color: color_arg.val('v') > 50 ? '#000000' : '#ffffff'
});
%(change)s;
}
},
// cancelCallback
function (color) {}
);
// The 'change' event is still bound:
input.unbind('keyup');
}, 1);
</script>""" % dict(id=id, default=default, change=change)
return s
class InteractElement(object):
def label(self):
"""
Returns an empty label for this element. This should be
overridden for subclasses that need a label.
OUTPUT:
- a string
EXAMPLES::
sage: from sagenb.notebook.interact import UpdateButton, InteractElement
sage: b = UpdateButton(1, 'autoupdate')
sage: isinstance(b, InteractElement)
True
sage: b.label()
''
"""
return ""
def set_canvas(self, canvas):
"""
Sets the :class:`InteractCanvas` on which this element appears.
This method is primarily called in the constructor for
:class:`InteractCanvas`.
EXAMPLES::
sage: from sagenb.notebook.interact import InputBox, InteractCanvas
sage: B = InputBox('x',2)
sage: canvas1 = InteractCanvas([B], 3)
sage: canvas2 = InteractCanvas([B], 3)
sage: B.canvas() is canvas2
True
sage: B.set_canvas(canvas1)
sage: B.canvas() is canvas1
True
"""
self._canvas = canvas
def canvas(self):
"""
Returns the :class:`InteractCanvas` associated to this element. If
no canvas has been set (via the :meth:`set_canvas` method), then
raise a ValueError.
EXAMPLES::
sage: from sagenb.notebook.interact import InputBox, InteractCanvas
sage: B = InputBox('x',2)
sage: canvas1 = InteractCanvas([B], 3)
sage: canvas2 = InteractCanvas([B], 3)
sage: B.canvas() is canvas2
True
"""
if hasattr(self, '_canvas'):
return self._canvas
else:
raise ValueError, "this element does not have a canvas associated with it"
class InteractControl(InteractElement):
def __init__(self, var, default_value, label=None):
"""
Abstract base class for :func:`interact` controls. These are controls
that are used in a specific :func:`interact`. They have internal
state information about the specific function being interacted,
etc.
INPUT:
- ``var`` - a string; name of variable that this control
interacts
- ``default_value`` - the default value of the variable
corresponding to this control.
- ``label`` - a string (default: None); label for this
control; if None then defaults to ``var``.
EXAMPLES::
sage: from sagenb.notebook.interact import InteractControl
sage: InteractControl('x', default_value=5)
A InteractControl (abstract base class)
"""
self.__var = var
self.__cell_id = SAGE_CELL_ID
self.__default_value = default_value
self.__adapt_number = new_adapt_number()
if label is None:
self.__label = var
else:
self.__label = label
InteractElement.__init__(self)
def __repr__(self):
"""
String representation of :func:`interact` control.
OUTPUT:
- a string
EXAMPLES::
sage: from sagenb.notebook.interact import InteractControl
sage: InteractControl('x', default_value=5).__repr__()
'A InteractControl (abstract base class)'
"""
return "A InteractControl (abstract base class)"
def value_js(self):
"""
JavaScript that when evaluated gives the current value of this
control. This should be redefined in a derived class.
OUTPUT:
- a string - defaults to 'NULL' - this should be redefined.
EXAMPLES::
sage: sagenb.notebook.interact.InteractControl('x', default_value=5).value_js()
'NULL'
"""
return 'NULL'
def label(self):
"""
Return the text label of this :func:`interact` control.
OUTPUT:
- a string
EXAMPLES::
sage: from sagenb.notebook.interact import InteractControl
sage: InteractControl('x', default_value=5, label='the x value').label()
'the x value'
"""
return math_parse(self.__label)
def default_value(self):
"""
Return the default value of the variable corresponding to this
:func:`interact` control.
OUTPUT:
- an object
EXAMPLES::
sage: from sagenb.notebook.interact import InteractControl
sage: InteractControl('x', 19/3).default_value()
19/3
"""
return self.__default_value
def html_escaped_default_value(self):
"""
Returns the HTML escaped default value of the variable
corresponding to this :func:`interact` control. Note that any
HTML that uses quotes around this should use double quotes and
not single quotes.
OUTPUT:
- a string
EXAMPLES::
sage: from sagenb.notebook.interact import InteractControl
sage: InteractControl('x', '"cool"').html_escaped_default_value()
'"cool"'
sage: InteractControl('x',"'cool'").html_escaped_default_value()
"'cool'"
sage: x = var('x')
sage: InteractControl('x', x^2).html_escaped_default_value()
'x^2'
"""
s = self.default_value()
if not isinstance(s, str):
s = repr(s)
import cgi
return cgi.escape(s, quote=True)
def adapt_number(self):
"""
Return integer index into adapt dictionary of function that is
called to adapt the values of this control to Python.
OUTPUT:
- an integer
EXAMPLES::
sage: from sagenb.notebook.interact import InteractControl
sage: InteractControl('x', 19/3).adapt_number() # random -- depends on call order
2
"""
return self.__adapt_number
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`` - a string:object dictionary; the globals
interpreter variables, e.g., :func:`globals`, which is
useful for evaluating value.
OUTPUT:
- an object
EXAMPLES::
sage: sagenb.notebook.interact.InteractControl('x', 1)._adaptor('2/3', globals())
2/3
"""
return sage_eval(value, globs)
def interact(self, *args):
r"""
Return a string that when evaluated in JavaScript calls the
JavaScript :func:`interact` function with appropriate inputs for
this control.
This method will check to see if there is a canvas attached to
this control and whether or not controls should automatically
update the output when their values change. If no canvas is
associated with this control, then the control will
automatically update.
OUTPUT:
- a string - that is meant to be evaluated in JavaScript
EXAMPLES::
sage: sagenb.notebook.interact.InteractControl('x', 1).interact()
"interact(..., '_interact_.update(..., \\'x\\', ..., _interact_.standard_b64decode(\\''+encode64(NULL)+'\\'), globals()); _interact_.recompute(\\'0\\');')"
"""
# We have to do a try/except block here since the control may
# not have a canvas associated with it.
try:
auto_update = self.canvas().is_auto_update()
except ValueError:
auto_update = True
# The following is a crazy line to read because of all the
# backslashes and try/except. All it does is run the interact
# function once after setting exactly one dynamic variable.
# If setting the dynamic variable fails, due to a KeyError
python_string = """_interact_.update(\\'%s\\', \\'%s\\', %s, _interact_.standard_b64decode(\\''+encode64(%s)+'\\'), globals())""" % (
self.cell_id(), self.var(), self.adapt_number(), self.value_js(*args))
if auto_update:
python_string += """; _interact_.recompute(\\'%s\\');""" % self.cell_id()
s = """interact(%r, '%s')""" % (self.cell_id(), python_string)
return s
def var(self):
"""
Return the name of the variable that this control interacts.
OUTPUT:
- a string - name of a variable as a string.
EXAMPLES::
sage: sagenb.notebook.interact.InteractControl('theta', 1).var()
'theta'
"""
return self.__var
def cell_id(self):
"""
Return the ID of the cell that contains this :func:`interact` control.
OUTPUT:
- an integer or a string
EXAMPLES:
The output below should equal the ID of the current cell::
sage: sagenb.notebook.interact.InteractControl('theta', 1).cell_id()
0
"""
return self.__cell_id
class InputBox(InteractControl):
def __init__(self, var, default_value, label=None, type=None, width=80, height = 1, **kwargs):
"""
An input box :func:`interact` control.
INPUT:
- ``var`` - a string; name of variable that this control
interacts
- ``default_value`` - the default value of the variable
corresponding to this control
- ``label`` - a string (default: None); label for this
control
- ``type`` - a type (default: None); the type of this control,
e.g., the type 'bool'
- ``height`` - an integer (default: 1); the number of rows.
If greater than 1 a value won't be returned until something
outside the textarea is clicked.
- ``width`` - an integer (default: 80); the character width of
this control
- ``kwargs`` - a dictionary; additional keyword options
EXAMPLES::
sage: sagenb.notebook.interact.InputBox('theta', 1, 'theta')
An InputBox interactive control with theta=1 and label 'theta'
sage: sagenb.notebook.interact.InputBox('theta', 1, 'theta', int)
An InputBox interactive control with theta=1 and label 'theta'
"""
InteractControl.__init__(self, var, default_value, label)
self.__type = type
self.__width = width
self.__height = height
self._kwargs = kwargs
def __repr__(self):
"""
String representation of an :class:`InputBox` interactive control.
OUTPUT:
- a string
EXAMPLES::
sage: sagenb.notebook.interact.InputBox('theta', 1).__repr__()
"An InputBox interactive control with theta=1 and label 'theta'"
"""
return 'An InputBox interactive control with %s=%r and label %r'%(
self.var(), self.default_value(), self.label())
def _adaptor(self, value, globs):
"""
Adapt a user input, which is the text they enter, to be an
element selected by this control.
INPUT:
- ``value`` - text entered by user
- ``globs`` - a string:object dictionary; the :func:`globals`
interpreter variables (not used here).
OUTPUT:
- an object
EXAMPLES::
sage: sagenb.notebook.interact.InputBox('theta', Color('red'), type=Color)._adaptor('#aaaaaa',globals())
RGB color (0.66..., 0.66..., 0.66...)
"""
if self.__type is None:
return sage_eval(value, globs)
elif self.__type is str:
return value
elif self.__type is Color:
try:
return Color(value)
except ValueError:
try:
return Color('#' + value)
except ValueError:
print "Invalid color '%s', using default Color()" % value
return Color()
else:
return self.__type(sage_eval(value,globs))
def value_js(self):
"""
Return JavaScript string that will give the value of this
control element.
OUTPUT:
- a string - JavaScript
EXAMPLES::
sage: sagenb.notebook.interact.InputBox('theta', 1).value_js()
'this.value'
"""
if self.__type is bool:
return 'this.checked'
else:
return 'this.value'
def render(self):
r"""