Check-in [dd29686ee3]
Not logged in

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:merge with trunk
Timelines: family | ancestors | descendants | both | wtf-8-experiment
Files: files | file ages | folders
SHA1: dd29686ee304f2b582f361453675ffa099585e09
User & Date: chw 2019-06-14 15:10:10
Context
2019-06-15
06:11
merge with trunk check-in: 9c76ebe46f user: chw tags: wtf-8-experiment
2019-06-14
15:10
merge with trunk check-in: dd29686ee3 user: chw tags: wtf-8-experiment
15:09
improve twv demo check-in: 4eb3029eeb user: chw tags: trunk
2019-06-13
14:32
merge with trunk check-in: 8e21d22583 user: chw tags: wtf-8-experiment
Changes

Changes to undroid/tsb/examples/cheatsheet.tsb.

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
     string.</dd><br>
 <dt><code><b>h1</b> string</code>, <code><b>h2..h5</b></code></dt>
 <dd>Format a HTML header after the correspondig input field, auto-hide
     the input field.</dd><br>
 <dt><code><b>hr</b></code></dt>
 <dd>Format a horizontal ruler, auto-hide the input field.</dd><br>
 <dt><code><b>htmlraw</b> html ?hidden?</code></dt>
 <dd>Output raw HTML after the corresponding input field, if <code>hidden</code>
     is true, auto-hide the input field.</dd><br>
 <dt><code><b>img</b> ?filename import mime?</code></dt>
 <dd>Format a HTML IMG given <code>filename</code>, if file name omitted,
     present file selection. If <code>import</code> is true, the image is
     inlined. If the mime type of the image is unknown, it can be specified
     with the <code>mime</code> parameter. The corresponding input field
     is auto-hidden.</dd><br>
 <dt><code><b>img_from_binary</b> data mime ?hidden?</code></dt>
 <dd>Format a HTML IMG given the byte array <code>data</code> and mime
     type <code>mime</code>. If <code>hidden</code> is false, the corresponding
     input field is not auto-hidden.</dd><br>
 <dt><code><b>parray</b> arrayname ?pattern?</code></dt>
 <dd>Pretty print an array using <code><b>puts</b></code> and thus outputs
     after the corresponding input field.</dd><br>
 <dt><code><b>puts</b></code></dt>
 <dd>Overriden, writing to <code>stdout</code>/<code>stderr</code> channels
     outputs after the corresponding input field.</dd><br>
 <dt><code><b>table</b> colinfo data</code></dt>
 <dd>Format a HTML table, <code>colinfo</code> gives the number of columns, if it's

     an integer; if negative, the first <code>-colinfo</code> items are used in the
     header row. If <code>colinfo</code> is a list, it specifies the column headers
     directly.</dd>
</dl>} 9 {h4 "Useful procs in the tsb namespace"} 10 {lsort [info proc tsb::*]} 11 {#HTML
<dl>
 <dt><code><b>tsb::canvas</b> ?-width w -height h?</code></dt>
 <dd>Creates a <code>canvas</code> emulation implementing enough methods
     for Plotchart, returns a widget command.</dd><br>
 <dt><code><b>tsb::canvascmd</b> cmd ...</code></dt>
 <dd>Implementation of the canvas widget command, supports
     <code>create</code>, <code>delete</code> and other methods plus
     method <code>svg</code> which renders the canvas as SVG after
     the corresponding input field.</dd><br>
 <dt><code><b>tsb::clear</b></code></dt>
 <dd>Clears the entire page.</dd><br>
................................................................................
 <dt><code><b>tsb::eval</b> id</code></dt>
 <dd>Re-evaluates field with number <code>id</code>.</dd><br>
 <dt><code><b>tsb::load</b> ?filename?</code></dt>
 <dd>Load page from given <code>filename</code>, if file name omitted,
     present file selection.</dd><br>
 <dt><code><b>tsb::print</b></code></dt>
 <dd>Opens the print dialog to print the page. Platform dependent,
     on Windows use <code>&lt;Control-p&gt;</code> from keyboard instead.</dd><br>

 <dt><code><b>tsb::save</b> ?filename?</code></dt>
 <dd>Save page to given <code>filename</code>, if file name omitted,
     present file selection.</dd>



</dl>} 12 {h4 "Input Fields"} 13 {#HTML
<p>Input fields have a label of the form <code>in(&lt;number&gt;)</code> where the
number is the index in the history array. Arbitraty text can be entered which
however should be valid Tcl code, except the very first line is <code>#HTML</code>
or <code>#MARKDOWN</code>, in which case the following lines should be valid HTML
or Markdown text, respectively. The input field is evaluated when
<code>&lt;Shift-Return&gt;</code> is pressed.</p>


<p>Depending on the command evaluation the input field is sometimes hidden.
A double click on the corresponding output field(s) makes the input field visible
and thus editable, again.</p>} 14 {h4 "Output Fields"} 15 {#HTML
<p>Each input field has two corresponding output fields which are visible/non-empty
depending on context. One output field receives preformatted text, either the result

or the error message of a command evaluation. The other output field receives HTML,
e.g. for displaying an image or for the <code>#HTML</code> and <code>#MARKDOWN</code>
formats from the input field.</p>} 16 {tsb::save cheatsheet.tsb}







|
|








|
|




|
|

|
>
|
|
|



|
|







 







|
>


|
>
>
>

|
|
|
|
|
|
>


|
|
|
|
>
|
|
|
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
     string.</dd><br>
 <dt><code><b>h1</b> string</code>, <code><b>h2..h5</b></code></dt>
 <dd>Format a HTML header after the correspondig input field, auto-hide
     the input field.</dd><br>
 <dt><code><b>hr</b></code></dt>
 <dd>Format a horizontal ruler, auto-hide the input field.</dd><br>
 <dt><code><b>htmlraw</b> html ?hidden?</code></dt>
 <dd>Output raw HTML after the corresponding input field, if 
     <code>hidden</code> is true, auto-hide the input field.</dd><br>
 <dt><code><b>img</b> ?filename import mime?</code></dt>
 <dd>Format a HTML IMG given <code>filename</code>, if file name omitted,
     present file selection. If <code>import</code> is true, the image is
     inlined. If the mime type of the image is unknown, it can be specified
     with the <code>mime</code> parameter. The corresponding input field
     is auto-hidden.</dd><br>
 <dt><code><b>img_from_binary</b> data mime ?hidden?</code></dt>
 <dd>Format a HTML IMG given the byte array <code>data</code> and mime
     type <code>mime</code>. If <code>hidden</code> is false, the
     corresponding input field is not auto-hidden.</dd><br>
 <dt><code><b>parray</b> arrayname ?pattern?</code></dt>
 <dd>Pretty print an array using <code><b>puts</b></code> and thus outputs
     after the corresponding input field.</dd><br>
 <dt><code><b>puts</b></code></dt>
 <dd>Overriden, writing to <code>stdout</code>/<code>stderr</code>
     channels outputs after the corresponding input field.</dd><br>
 <dt><code><b>table</b> colinfo data</code></dt>
 <dd>Format a HTML table, <code>colinfo</code> gives the number of
     columns, if it's an integer; if negative, the first
     <code>-colinfo</code> items are used in the header row.
     If <code>colinfo</code> is a list, it specifies the column
     headers directly.</dd>
</dl>} 9 {h4 "Useful procs in the tsb namespace"} 10 {lsort [info proc tsb::*]} 11 {#HTML
<dl>
 <dt><code><b>tsb::canvas</b> ?-width w -height h?</code></dt>
 <dd>Creates a <code>canvas</code> emulation implementing enough
     methods for Plotchart, returns a widget command.</dd><br>
 <dt><code><b>tsb::canvascmd</b> cmd ...</code></dt>
 <dd>Implementation of the canvas widget command, supports
     <code>create</code>, <code>delete</code> and other methods plus
     method <code>svg</code> which renders the canvas as SVG after
     the corresponding input field.</dd><br>
 <dt><code><b>tsb::clear</b></code></dt>
 <dd>Clears the entire page.</dd><br>
................................................................................
 <dt><code><b>tsb::eval</b> id</code></dt>
 <dd>Re-evaluates field with number <code>id</code>.</dd><br>
 <dt><code><b>tsb::load</b> ?filename?</code></dt>
 <dd>Load page from given <code>filename</code>, if file name omitted,
     present file selection.</dd><br>
 <dt><code><b>tsb::print</b></code></dt>
 <dd>Opens the print dialog to print the page. Platform dependent,
     on Windows use <code>&lt;Control-p&gt;</code> from keyboard
     instead.</dd><br>
 <dt><code><b>tsb::save</b> ?filename?</code></dt>
 <dd>Save page to given <code>filename</code>, if file name omitted,
     present file selection.</dd><br>
 <dt><code><b>tsb::wrhtml</b> ?filename?</code></dt>
 <dd>Write page as HTML to given <code>filename</code>, if file name
     omitted, present file selection.</dd>
</dl>} 12 {h4 "Input Fields"} 13 {#HTML
<p>Input fields have a label of the form <code>in(&lt;number&gt;)</code>
where the number is the index in the history array. Arbitrary text can be
entered which however should be valid Tcl code, except the very first line
is <code>#HTML</code> or <code>#MARKDOWN</code>, in which case the
following lines should be valid HTML or Markdown text, respectively.
The input field is evaluated when <code>&lt;Shift-Return&gt;</code>
is pressed.</p>

<p>Depending on the command evaluation the input field is sometimes hidden.
A double click on the corresponding output field(s) makes the input field
visible and thus editable, again.</p>} 14 {h4 "Output Fields"} 15 {#HTML
<p>Each input field has two corresponding output fields which are
visible/non-empty depending on context. One output field receives
preformatted text, either the result or the error message of a
command evaluation. The other output field receives HTML, e.g.
for displaying an image or for the <code>#HTML</code> and
<code>#MARKDOWN</code> formats from the input field.</p>} 16 {tsb::save cheatsheet.tsb}

Changes to undroid/tsb/tsb.tcl.

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
...
268
269
270
271
272
273
274



275
276
277
278
279
280
281
...
289
290
291
292
293
294
295

296
297
298
299
300
301
302
...
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
...
378
379
380
381
382
383
384
385

























































386
387
388
389
390
391
392
...
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
...
765
766
767
768
769
770
771
772


773
774
775
776
777
778
779
780
781
782
783
...
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
...
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
....
1014
1015
1016
1017
1018
1019
1020
1021

1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
    [winfo exists .] && ([winfo children .] eq "")} {
    wm withdraw .
}

namespace eval ::tsb {
    variable inload 0
    variable ready 0


}

# The history array of input fields.

array set H {1 ""}

# This is invoked from the JS window.external.invoke function.
# The first non-blank characters are assumed to be the integer id
# of the (input) field.

proc ::tsb::call_from_js {str} {





    set n [string first " " $str]
    if {$n < 0} {
	return
    }
    set id [string trim [string range $str 0 $n]]
    incr n
    set str [string trim [string range $str $n end]]
................................................................................
    set ::tsb::file $name
    $::W title "$::tsb::title - [file tail $name]"
    return -code 4 ;# continue
}

proc ::tsb::loadh {data} {
    variable inload



    set inload 1
    unset -nocomplain ::H
    $::W call ClearFields
    foreach {key value} $data {
	if {[string is integer $key]} {
	    set h($key) $value
	}
................................................................................
	incr next
	set ::H($next) ""
	::tsb::call_from_js "$key $h($key)"
    }
    if {[info exists next]} {
	unset ::H($next)
    }

    set inload 0
}

proc ::tsb::save {{name {}}} {
    variable inload
    if {$inload} {
	return
................................................................................
    # Nothing to see here ...
}

# Reload document related functions; used for initial load, too.

proc ::tsb::reload0 {} {
    variable ready




    if {![info exists ::W]} {
	# Can happen during init of webview.
	return
    }
    set ready 1
    $::W call document.write $::tsb::D

    set title $::tsb::title
    if {$::tsb::file ne ""} {
	append title " - [file tail $::tsb::file]"
    }
    $::W title $title
    ::tsb::loadh [array get ::H]
}

proc ::tsb::reload {args} {




    after cancel ::tsb::reload0
    after idle ::tsb::reload0

}

# Clear page.

proc ::tsb::clear {} {
    variable inload
    if {$inload} {
................................................................................
    tailcall $::W call Feval $id
}

# Print page; on Windows seems not to work, but pressing
# <Control-p> opens the printer dialog at least.

proc ::tsb::print {} {
    tailcall $::W call window.print()

























































}

# A minimal canvas emulation for plotchart. The svg method
# produces SVG into the current (output) field.
#
#  set C [::tsb::canvas ...]
#  $C create line 10 10 20 20 -fill black ...
................................................................................
	return -code error $msg
    }
    return $msg
}

namespace eval ::tsb {

    # JS core functions assembled from various pieces into a big string.




    variable D

    # HEAD
    set D {<!DOCTYPE html><html lang="en"><head>}
    append D {<meta charset="utf-8">}
    append D {<meta http-equiv="X-UA-Compatible" content="IE=edge">}

    # STYLE, CSS

    append D {<style>}
    if {$::tcl_platform(platform) eq "windows"} {
	append D {
	    body {
		font-family: Arial, Tahoma, Helvetica, sans-serif; font-size: 90%;
	    }
	    pre, code {
		font-family: Consolas, monospace; font-size: 90%;
	    }
	    textarea {
		font-size: 100%;

	    }




	}
    } else {
	append D {
	    body {
		font-family: sans-serif, Helvetica; font-size: 90%;



	    }


	}


    }





    append D {
	textarea {

	    border: 1px solid #AAAAAA;


	    margin: 4px;
	    outline: none;
	    padding: 4px;
	}
	textarea:focus {
	    border: 3px solid rgba(81, 203, 238, 1);
	    margin: 2px;
	    padding: 4px;
	}

    }

    append D {</style>}

    # SCRIPT
    append D {

	<script type="text/javascript">

	var msLike = /MSIE|Trident|Edge/i.test(navigator.userAgent);

	if (!msLike && !window.external) {
	    /* See also webview.h */
	    window.external = {
................................................................................
		    window.webkit.messageHandlers.external.postMessage(str);
		}
	    };
	}

	var needsClear = new Array();

	var RunTcl = function(str) { window.external.invoke(str); };



	/* The 2nd sin: this drives the Tcl event loop. */
	var Gtimer = window.setInterval(function() {
	    window.external.invoke("0 ::tsb::ping\n");
	}, 20);

	var Wclear = function(id) {
	    if (!needsClear[id]) {
		return;
	    }
	    var output =
................................................................................
	    var input = document.getElementById('code' + id);
	    needsClear[id] = true;
	    RunTcl("" + id + " " + input.value);
	};

	var Field = function(id) {
	    var div = document.createElement('div');
	    div.classname = 'field';
	    var html = '<div class="field" id="in' + id + '">';
	    html += '<label for="code' + id + '"';
	    if (msLike) {
		html += ' style="font-family: Consolas, sans-serif;"';
	    } else {
		html += ' style="vertical-align: 10px; font-family: monospace;"';
	    }
	    html += '>  in(' + id + ')  </label>';
	    html += '<textarea id="code' + id + '" rows="1"';
	    if (msLike) {
		html += ' style="width: 90%; resize: none;';
		html += ' font-family: Consolas, monospace;';
		html += ' overflow: hidden"';
	    } else {
		html += ' style="width: 90%; resize: none;';
		html += ' font-family: monospace; overflow: hidden"';
	    }
	    html += '></textarea></div><div class="field"';
	    html += ' id="out' + id + '-pre"';
	    if (msLike) {
		html += ' style="word-wrap: break-word;"';
	    } else {
		html += ' style="overflow-wrap: break-word;"';
	    }
	    html += '><pre></pre></div>';
	    html += '<div class="field" id="out' + id + '-raw"></div>';
	    div.innerHTML = html;
	    document.body.appendChild(div);


	    needsClear[id] = null;
	    var input = document.getElementById('code' + id);
	    input.addEventListener('keydown', function(event) {
		var code;
		var shift = false;
		if (window.event) {
		    code = window.event.keyCode;
................................................................................
		if (fields.length > 0) {
		    fields[0].parentNode.removeChild(fields[0]);
		    continue;
		}
		break;
	    }
	};



















	/* Is this really needed? */
	window.addEventListener('unload', function(event) {
	    window.clearInterval(Gtimer);
	    Gtimer = null;
	});

	</script>
    }

    # BODY (empty), END

    append D {</head><body></body></html>}

    # The URL to be displayed.
    # Windows:   data:text/html,<html><head><script>window.external.invoke("0 ::tsb::reload");</script></head></html>
    # MacOS:     data:text/html,<html><head><script>window.external.invoke("0 ::tsb::reload");</script></head></html>
    # WebkitGtk: data:text/html,<html><head><script>window.webkit.messageHandlers.extern.postMessage("0 ::tsb::reload");</script></head></html>

    variable U
................................................................................

set W [::twv::new -width 800 -height 600 -title $::tsb::title \
	   -url $::tsb::U -resizable 1 -debug 0 \
	   -callback ::tsb::call_from_js]

# On Windows this seems the way to load, maybe timing?

if {!$::tsb::ready} {

    ::tsb::reload0
}

# Enter the webview event loop.

$W run

# Done.

exit 0







>
>











>
>
>
>
>







 







>
>
>







 







>







 







>
>
>
>






>









>
>
>
>


>







 







|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







 







|

>
>
>



|
|
<


>
|
<
<
|
|
<
<
|
|
|
<
>
|
>
>
>
>

|
|
|
|
>
>
>
|
>
>

>
>
|
>
>
>
>
|
<
<
>

>
>




|




>


<
<

<
>







 







|
>
>



|







 







|
|
|
<
<
<
<
<
<
|
<
<
<
<
<
<
<
<
|
|
<
<
<
<
<
|
|


>
>







 







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>










|
>
|







 







|
>










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
...
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
...
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
...
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
...
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
...
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
...
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
...
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
....
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
....
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
    [winfo exists .] && ([winfo children .] eq "")} {
    wm withdraw .
}

namespace eval ::tsb {
    variable inload 0
    variable ready 0
    variable dump ""
    variable file ""
}

# The history array of input fields.

array set H {1 ""}

# This is invoked from the JS window.external.invoke function.
# The first non-blank characters are assumed to be the integer id
# of the (input) field.

proc ::tsb::call_from_js {str} {
    variable dump
    if {[string first "<" $str] == 0} {
	set dump $str
	return
    }
    set n [string first " " $str]
    if {$n < 0} {
	return
    }
    set id [string trim [string range $str 0 $n]]
    incr n
    set str [string trim [string range $str $n end]]
................................................................................
    set ::tsb::file $name
    $::W title "$::tsb::title - [file tail $name]"
    return -code 4 ;# continue
}

proc ::tsb::loadh {data} {
    variable inload
    if {$inload} {
	return
    }
    set inload 1
    unset -nocomplain ::H
    $::W call ClearFields
    foreach {key value} $data {
	if {[string is integer $key]} {
	    set h($key) $value
	}
................................................................................
	incr next
	set ::H($next) ""
	::tsb::call_from_js "$key $h($key)"
    }
    if {[info exists next]} {
	unset ::H($next)
    }
    after cancel ::tsb::reload0
    set inload 0
}

proc ::tsb::save {{name {}}} {
    variable inload
    if {$inload} {
	return
................................................................................
    # Nothing to see here ...
}

# Reload document related functions; used for initial load, too.

proc ::tsb::reload0 {} {
    variable ready
    variable inload
    if {$inload} {
	return
    }
    if {![info exists ::W]} {
	# Can happen during init of webview.
	return
    }
    set ready 1
    $::W call document.write $::tsb::D
    $::W call document.close
    set title $::tsb::title
    if {$::tsb::file ne ""} {
	append title " - [file tail $::tsb::file]"
    }
    $::W title $title
    ::tsb::loadh [array get ::H]
}

proc ::tsb::reload {args} {
    variable inload
    if {$inload} {
	return
    }
    after cancel ::tsb::reload0
    after idle ::tsb::reload0
    return -code 4 ;# continue
}

# Clear page.

proc ::tsb::clear {} {
    variable inload
    if {$inload} {
................................................................................
    tailcall $::W call Feval $id
}

# Print page; on Windows seems not to work, but pressing
# <Control-p> opens the printer dialog at least.

proc ::tsb::print {} {
    tailcall $::W call window.print
}

# Dump to HTML; the weird vwait/loop construct needs to be revised.

proc ::tsb::dump {} {
    variable dump
    set dump ""
    $::W call Dump
    set count 10
    while {$count > 0} {
	incr count -1
	$::W loop 0
	if {$dump ne ""} {
	    break
	}
	after 10 [set ::tsb::dump $::tsb::dump]
	vwait [namespace current]::dump
    }
    return $dump
}

# Write HTML to file.

proc ::tsb::wrhtml {{name {}}} {
    variable inload
    variable D_head
    variable D_style
    variable title
    variable file
    set selname 0
    if {$name eq ""} {
	set selname 1
	set name [$::W dialog open "Write HTML To File"]
    }
    if {$name eq ""} {
	return
    }
    set t $title
    if {$file ne ""} {
	append t " - [file tail $file]"
    }
    set t [htmlquote $t]
    set f [open $name w]
    puts $f $D_head
    puts $f "<title>$t</title>"
    puts $f $D_style
    puts $f "</head><body>\n"
    puts $f [::tsb::dump]
    puts $f "</body></html>"
    close $f
    if {$selname} {
	set cmd [dict get [info frame -1] cmd]
	set newcmd $cmd
	lappend newcmd $name
	::tsb::change_field $cmd $newcmd
    }
    return -code 4 ;# continue
}

# A minimal canvas emulation for plotchart. The svg method
# produces SVG into the current (output) field.
#
#  set C [::tsb::canvas ...]
#  $C create line 10 10 20 20 -fill black ...
................................................................................
	return -code error $msg
    }
    return $msg
}

namespace eval ::tsb {

    # CSS and JavaScript assembled from various pieces into a big string.

    variable D_head
    variable D_style
    variable D_script
    variable D

    # HEAD
    set D_head {<!DOCTYPE html><html lang="en"><head>}
    append D_head {<meta charset="utf-8">}


    # STYLE, CSS
    set D_style {
	<style>


	body {
	    font-family: sans-serif, Arial, Tahoma, Helvetica;


	    font-size: 90%;
	}
	textarea {

	    font-family: Consolas, monospace; font-size: 100%;
	}
	label {
	    font-family: Consolas, monospace;
	    margin: 1px;
	    padding: 1px;
	}
	pre {
	    /* overflow-x: auto; */
	    white-space: pre-wrap;
	    white-space: -moz-pre-wrap;
	    white-space: -pre-wrap;
	    white-space: -o-pre-wrap;
	    word-wrap: break-word;
	}
	pre, code {
	    font-family: Consolas, monospace; font-size: 100%;
	}
	.infield * {
	    vertical-align: middle;
	}
	.tin {
	    width: 85%;
	    resize: none;
	    overflow: auto;
	}


	.tin, .tin:disabled {
	    border: 1px solid #AAAAAA;
	    background: transparent;
	    color: inherit;
	    margin: 4px;
	    outline: none;
	    padding: 4px;
	}
	.tin:focus {
	    border: 3px solid rgba(81, 203, 238, 1);
	    margin: 2px;
	    padding: 4px;
	}
	</style>
    }



    # SCRIPT

    set D_script {
	<script type="text/javascript">

	var msLike = /MSIE|Trident|Edge/i.test(navigator.userAgent);

	if (!msLike && !window.external) {
	    /* See also webview.h */
	    window.external = {
................................................................................
		    window.webkit.messageHandlers.external.postMessage(str);
		}
	    };
	}

	var needsClear = new Array();

	var RunTcl = function(str) {
	    window.external.invoke(str);
	};

	/* The 2nd sin: this drives the Tcl event loop. */
	var Gtimer = window.setInterval(function() {
	    window.external.invoke("0 ::tsb::ping");
	}, 20);

	var Wclear = function(id) {
	    if (!needsClear[id]) {
		return;
	    }
	    var output =
................................................................................
	    var input = document.getElementById('code' + id);
	    needsClear[id] = true;
	    RunTcl("" + id + " " + input.value);
	};

	var Field = function(id) {
	    var div = document.createElement('div');
	    div.className = 'field';
	    var html = '\n <div class="infield" id="in' + id + '">';
	    html += '\n  <label for="code' + id + '">in(' + id + ')</label>';






	    html += '\n  <textarea class="tin" id="code' + id + '" rows="1"';








	    html += '></textarea>\n </div>\n <div';
	    html += ' id="out' + id + '-pre">';





	    html += '<pre></pre></div>';
	    html += '\n <div id="out' + id + '-raw"></div>\n';
	    div.innerHTML = html;
	    document.body.appendChild(div);
	    /* For better readability only. */
	    document.body.appendChild(document.createTextNode('\n'));
	    needsClear[id] = null;
	    var input = document.getElementById('code' + id);
	    input.addEventListener('keydown', function(event) {
		var code;
		var shift = false;
		if (window.event) {
		    code = window.event.keyCode;
................................................................................
		if (fields.length > 0) {
		    fields[0].parentNode.removeChild(fields[0]);
		    continue;
		}
		break;
	    }
	};

	var Dump = function() {
	    var i;
	    var values = new Array();
	    var fields = document.getElementsByClassName('tin');
	    for (i = 0; i < fields.length; i++) {
		values[i] = fields[i].value;
		fields[i].innerHTML = values[i];
		fields[i].disabled = true;
	    }
	    window.external.invoke(document.body.innerHTML);
	    fields = document.getElementsByClassName('tin');
	    for (i = 0; i < fields.length; i++) {
		fields[i].disabled = false;
		fields[i].innerHTML = "";
		fields[i].value = values[i];
	    }
	};

	/* Is this really needed? */
	window.addEventListener('unload', function(event) {
	    window.clearInterval(Gtimer);
	    Gtimer = null;
	});

	</script>
    }

    # Assemble pieces with empty BODY.
    set D $D_head
    append D $D_style $D_script {</head><body></body></html>}

    # The URL to be displayed.
    # Windows:   data:text/html,<html><head><script>window.external.invoke("0 ::tsb::reload");</script></head></html>
    # MacOS:     data:text/html,<html><head><script>window.external.invoke("0 ::tsb::reload");</script></head></html>
    # WebkitGtk: data:text/html,<html><head><script>window.webkit.messageHandlers.extern.postMessage("0 ::tsb::reload");</script></head></html>

    variable U
................................................................................

set W [::twv::new -width 800 -height 600 -title $::tsb::title \
	   -url $::tsb::U -resizable 1 -debug 0 \
	   -callback ::tsb::call_from_js]

# On Windows this seems the way to load, maybe timing?

if {($tcl_platform(platform) eq "windows") && !$::tsb::ready} {
    after cancel ::tsb::reload0
    ::tsb::reload0
}

# Enter the webview event loop.

$W run

# Done.

exit 0