Check-in [4eb3029eeb]
Not logged in

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

Overview
Comment:improve twv demo
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: 4eb3029eeb74d93dbff6f98fe59f45d91bf802c1
User & Date: chw 2019-06-14 15:09:06
Context
2019-06-15
06:09
minor fix in plotchart check-in: d34dee0c49 user: chw tags: trunk
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:31
improve twv demo check-in: 4520d299f7 user: chw tags: trunk
Changes

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

    14     14        string.</dd><br>
    15     15    <dt><code><b>h1</b> string</code>, <code><b>h2..h5</b></code></dt>
    16     16    <dd>Format a HTML header after the correspondig input field, auto-hide
    17     17        the input field.</dd><br>
    18     18    <dt><code><b>hr</b></code></dt>
    19     19    <dd>Format a horizontal ruler, auto-hide the input field.</dd><br>
    20     20    <dt><code><b>htmlraw</b> html ?hidden?</code></dt>
    21         - <dd>Output raw HTML after the corresponding input field, if <code>hidden</code>
    22         -     is true, auto-hide the input field.</dd><br>
           21  + <dd>Output raw HTML after the corresponding input field, if 
           22  +     <code>hidden</code> is true, auto-hide the input field.</dd><br>
    23     23    <dt><code><b>img</b> ?filename import mime?</code></dt>
    24     24    <dd>Format a HTML IMG given <code>filename</code>, if file name omitted,
    25     25        present file selection. If <code>import</code> is true, the image is
    26     26        inlined. If the mime type of the image is unknown, it can be specified
    27     27        with the <code>mime</code> parameter. The corresponding input field
    28     28        is auto-hidden.</dd><br>
    29     29    <dt><code><b>img_from_binary</b> data mime ?hidden?</code></dt>
    30     30    <dd>Format a HTML IMG given the byte array <code>data</code> and mime
    31         -     type <code>mime</code>. If <code>hidden</code> is false, the corresponding
    32         -     input field is not auto-hidden.</dd><br>
           31  +     type <code>mime</code>. If <code>hidden</code> is false, the
           32  +     corresponding input field is not auto-hidden.</dd><br>
    33     33    <dt><code><b>parray</b> arrayname ?pattern?</code></dt>
    34     34    <dd>Pretty print an array using <code><b>puts</b></code> and thus outputs
    35     35        after the corresponding input field.</dd><br>
    36     36    <dt><code><b>puts</b></code></dt>
    37         - <dd>Overriden, writing to <code>stdout</code>/<code>stderr</code> channels
    38         -     outputs after the corresponding input field.</dd><br>
           37  + <dd>Overriden, writing to <code>stdout</code>/<code>stderr</code>
           38  +     channels outputs after the corresponding input field.</dd><br>
    39     39    <dt><code><b>table</b> colinfo data</code></dt>
    40         - <dd>Format a HTML table, <code>colinfo</code> gives the number of columns, if it's
    41         -     an integer; if negative, the first <code>-colinfo</code> items are used in the
    42         -     header row. If <code>colinfo</code> is a list, it specifies the column headers
    43         -     directly.</dd>
           40  + <dd>Format a HTML table, <code>colinfo</code> gives the number of
           41  +     columns, if it's an integer; if negative, the first
           42  +     <code>-colinfo</code> items are used in the header row.
           43  +     If <code>colinfo</code> is a list, it specifies the column
           44  +     headers directly.</dd>
    44     45   </dl>} 9 {h4 "Useful procs in the tsb namespace"} 10 {lsort [info proc tsb::*]} 11 {#HTML
    45     46   <dl>
    46     47    <dt><code><b>tsb::canvas</b> ?-width w -height h?</code></dt>
    47         - <dd>Creates a <code>canvas</code> emulation implementing enough methods
    48         -     for Plotchart, returns a widget command.</dd><br>
           48  + <dd>Creates a <code>canvas</code> emulation implementing enough
           49  +     methods for Plotchart, returns a widget command.</dd><br>
    49     50    <dt><code><b>tsb::canvascmd</b> cmd ...</code></dt>
    50     51    <dd>Implementation of the canvas widget command, supports
    51     52        <code>create</code>, <code>delete</code> and other methods plus
    52     53        method <code>svg</code> which renders the canvas as SVG after
    53     54        the corresponding input field.</dd><br>
    54     55    <dt><code><b>tsb::clear</b></code></dt>
    55     56    <dd>Clears the entire page.</dd><br>
................................................................................
    56     57    <dt><code><b>tsb::eval</b> id</code></dt>
    57     58    <dd>Re-evaluates field with number <code>id</code>.</dd><br>
    58     59    <dt><code><b>tsb::load</b> ?filename?</code></dt>
    59     60    <dd>Load page from given <code>filename</code>, if file name omitted,
    60     61        present file selection.</dd><br>
    61     62    <dt><code><b>tsb::print</b></code></dt>
    62     63    <dd>Opens the print dialog to print the page. Platform dependent,
    63         -     on Windows use <code>&lt;Control-p&gt;</code> from keyboard instead.</dd><br>
           64  +     on Windows use <code>&lt;Control-p&gt;</code> from keyboard
           65  +     instead.</dd><br>
    64     66    <dt><code><b>tsb::save</b> ?filename?</code></dt>
    65     67    <dd>Save page to given <code>filename</code>, if file name omitted,
    66         -     present file selection.</dd>
           68  +     present file selection.</dd><br>
           69  + <dt><code><b>tsb::wrhtml</b> ?filename?</code></dt>
           70  + <dd>Write page as HTML to given <code>filename</code>, if file name
           71  +     omitted, present file selection.</dd>
    67     72   </dl>} 12 {h4 "Input Fields"} 13 {#HTML
    68         -<p>Input fields have a label of the form <code>in(&lt;number&gt;)</code> where the
    69         -number is the index in the history array. Arbitraty text can be entered which
    70         -however should be valid Tcl code, except the very first line is <code>#HTML</code>
    71         -or <code>#MARKDOWN</code>, in which case the following lines should be valid HTML
    72         -or Markdown text, respectively. The input field is evaluated when
    73         -<code>&lt;Shift-Return&gt;</code> is pressed.</p>
           73  +<p>Input fields have a label of the form <code>in(&lt;number&gt;)</code>
           74  +where the number is the index in the history array. Arbitrary text can be
           75  +entered which however should be valid Tcl code, except the very first line
           76  +is <code>#HTML</code> or <code>#MARKDOWN</code>, in which case the
           77  +following lines should be valid HTML or Markdown text, respectively.
           78  +The input field is evaluated when <code>&lt;Shift-Return&gt;</code>
           79  +is pressed.</p>
    74     80   
    75     81   <p>Depending on the command evaluation the input field is sometimes hidden.
    76         -A double click on the corresponding output field(s) makes the input field visible
    77         -and thus editable, again.</p>} 14 {h4 "Output Fields"} 15 {#HTML
    78         -<p>Each input field has two corresponding output fields which are visible/non-empty
    79         -depending on context. One output field receives preformatted text, either the result
    80         -or the error message of a command evaluation. The other output field receives HTML,
    81         -e.g. for displaying an image or for the <code>#HTML</code> and <code>#MARKDOWN</code>
    82         -formats from the input field.</p>} 16 {tsb::save cheatsheet.tsb}
           82  +A double click on the corresponding output field(s) makes the input field
           83  +visible and thus editable, again.</p>} 14 {h4 "Output Fields"} 15 {#HTML
           84  +<p>Each input field has two corresponding output fields which are
           85  +visible/non-empty depending on context. One output field receives
           86  +preformatted text, either the result or the error message of a
           87  +command evaluation. The other output field receives HTML, e.g.
           88  +for displaying an image or for the <code>#HTML</code> and
           89  +<code>#MARKDOWN</code> formats from the input field.</p>} 16 {tsb::save cheatsheet.tsb}

Changes to undroid/tsb/tsb.tcl.

    16     16       [winfo exists .] && ([winfo children .] eq "")} {
    17     17       wm withdraw .
    18     18   }
    19     19   
    20     20   namespace eval ::tsb {
    21     21       variable inload 0
    22     22       variable ready 0
           23  +    variable dump ""
           24  +    variable file ""
    23     25   }
    24     26   
    25     27   # The history array of input fields.
    26     28   
    27     29   array set H {1 ""}
    28     30   
    29     31   # This is invoked from the JS window.external.invoke function.
    30     32   # The first non-blank characters are assumed to be the integer id
    31     33   # of the (input) field.
    32     34   
    33     35   proc ::tsb::call_from_js {str} {
           36  +    variable dump
           37  +    if {[string first "<" $str] == 0} {
           38  +	set dump $str
           39  +	return
           40  +    }
    34     41       set n [string first " " $str]
    35     42       if {$n < 0} {
    36     43   	return
    37     44       }
    38     45       set id [string trim [string range $str 0 $n]]
    39     46       incr n
    40     47       set str [string trim [string range $str $n end]]
................................................................................
   268    275       set ::tsb::file $name
   269    276       $::W title "$::tsb::title - [file tail $name]"
   270    277       return -code 4 ;# continue
   271    278   }
   272    279   
   273    280   proc ::tsb::loadh {data} {
   274    281       variable inload
          282  +    if {$inload} {
          283  +	return
          284  +    }
   275    285       set inload 1
   276    286       unset -nocomplain ::H
   277    287       $::W call ClearFields
   278    288       foreach {key value} $data {
   279    289   	if {[string is integer $key]} {
   280    290   	    set h($key) $value
   281    291   	}
................................................................................
   289    299   	incr next
   290    300   	set ::H($next) ""
   291    301   	::tsb::call_from_js "$key $h($key)"
   292    302       }
   293    303       if {[info exists next]} {
   294    304   	unset ::H($next)
   295    305       }
          306  +    after cancel ::tsb::reload0
   296    307       set inload 0
   297    308   }
   298    309   
   299    310   proc ::tsb::save {{name {}}} {
   300    311       variable inload
   301    312       if {$inload} {
   302    313   	return
................................................................................
   329    340       # Nothing to see here ...
   330    341   }
   331    342   
   332    343   # Reload document related functions; used for initial load, too.
   333    344   
   334    345   proc ::tsb::reload0 {} {
   335    346       variable ready
          347  +    variable inload
          348  +    if {$inload} {
          349  +	return
          350  +    }
   336    351       if {![info exists ::W]} {
   337    352   	# Can happen during init of webview.
   338    353   	return
   339    354       }
   340    355       set ready 1
   341    356       $::W call document.write $::tsb::D
          357  +    $::W call document.close
   342    358       set title $::tsb::title
   343    359       if {$::tsb::file ne ""} {
   344    360   	append title " - [file tail $::tsb::file]"
   345    361       }
   346    362       $::W title $title
   347    363       ::tsb::loadh [array get ::H]
   348    364   }
   349    365   
   350    366   proc ::tsb::reload {args} {
          367  +    variable inload
          368  +    if {$inload} {
          369  +	return
          370  +    }
   351    371       after cancel ::tsb::reload0
   352    372       after idle ::tsb::reload0
          373  +    return -code 4 ;# continue
   353    374   }
   354    375   
   355    376   # Clear page.
   356    377   
   357    378   proc ::tsb::clear {} {
   358    379       variable inload
   359    380       if {$inload} {
................................................................................
   378    399       tailcall $::W call Feval $id
   379    400   }
   380    401   
   381    402   # Print page; on Windows seems not to work, but pressing
   382    403   # <Control-p> opens the printer dialog at least.
   383    404   
   384    405   proc ::tsb::print {} {
   385         -    tailcall $::W call window.print()
          406  +    tailcall $::W call window.print
          407  +}
          408  +
          409  +# Dump to HTML; the weird vwait/loop construct needs to be revised.
          410  +
          411  +proc ::tsb::dump {} {
          412  +    variable dump
          413  +    set dump ""
          414  +    $::W call Dump
          415  +    set count 10
          416  +    while {$count > 0} {
          417  +	incr count -1
          418  +	$::W loop 0
          419  +	if {$dump ne ""} {
          420  +	    break
          421  +	}
          422  +	after 10 [set ::tsb::dump $::tsb::dump]
          423  +	vwait [namespace current]::dump
          424  +    }
          425  +    return $dump
          426  +}
          427  +
          428  +# Write HTML to file.
          429  +
          430  +proc ::tsb::wrhtml {{name {}}} {
          431  +    variable inload
          432  +    variable D_head
          433  +    variable D_style
          434  +    variable title
          435  +    variable file
          436  +    set selname 0
          437  +    if {$name eq ""} {
          438  +	set selname 1
          439  +	set name [$::W dialog open "Write HTML To File"]
          440  +    }
          441  +    if {$name eq ""} {
          442  +	return
          443  +    }
          444  +    set t $title
          445  +    if {$file ne ""} {
          446  +	append t " - [file tail $file]"
          447  +    }
          448  +    set t [htmlquote $t]
          449  +    set f [open $name w]
          450  +    puts $f $D_head
          451  +    puts $f "<title>$t</title>"
          452  +    puts $f $D_style
          453  +    puts $f "</head><body>\n"
          454  +    puts $f [::tsb::dump]
          455  +    puts $f "</body></html>"
          456  +    close $f
          457  +    if {$selname} {
          458  +	set cmd [dict get [info frame -1] cmd]
          459  +	set newcmd $cmd
          460  +	lappend newcmd $name
          461  +	::tsb::change_field $cmd $newcmd
          462  +    }
          463  +    return -code 4 ;# continue
   386    464   }
   387    465   
   388    466   # A minimal canvas emulation for plotchart. The svg method
   389    467   # produces SVG into the current (output) field.
   390    468   #
   391    469   #  set C [::tsb::canvas ...]
   392    470   #  $C create line 10 10 20 20 -fill black ...
................................................................................
   701    779   	return -code error $msg
   702    780       }
   703    781       return $msg
   704    782   }
   705    783   
   706    784   namespace eval ::tsb {
   707    785   
   708         -    # JS core functions assembled from various pieces into a big string.
          786  +    # CSS and JavaScript assembled from various pieces into a big string.
   709    787   
          788  +    variable D_head
          789  +    variable D_style
          790  +    variable D_script
   710    791       variable D
   711    792   
   712    793       # HEAD
   713         -    set D {<!DOCTYPE html><html lang="en"><head>}
   714         -    append D {<meta charset="utf-8">}
   715         -    append D {<meta http-equiv="X-UA-Compatible" content="IE=edge">}
          794  +    set D_head {<!DOCTYPE html><html lang="en"><head>}
          795  +    append D_head {<meta charset="utf-8">}
   716    796   
   717    797       # STYLE, CSS
   718         -    append D {<style>}
   719         -    if {$::tcl_platform(platform) eq "windows"} {
   720         -	append D {
   721         -	    body {
   722         -		font-family: Arial, Tahoma, Helvetica, sans-serif; font-size: 90%;
   723         -	    }
   724         -	    pre, code {
   725         -		font-family: Consolas, monospace; font-size: 90%;
   726         -	    }
   727         -	    textarea {
   728         -		font-size: 100%;
   729         -	    }
          798  +    set D_style {
          799  +	<style>
          800  +	body {
          801  +	    font-family: sans-serif, Arial, Tahoma, Helvetica;
          802  +	    font-size: 90%;
          803  +	}
          804  +	textarea {
          805  +	    font-family: Consolas, monospace; font-size: 100%;
          806  +	}
          807  +	label {
          808  +	    font-family: Consolas, monospace;
          809  +	    margin: 1px;
          810  +	    padding: 1px;
          811  +	}
          812  +	pre {
          813  +	    /* overflow-x: auto; */
          814  +	    white-space: pre-wrap;
          815  +	    white-space: -moz-pre-wrap;
          816  +	    white-space: -pre-wrap;
          817  +	    white-space: -o-pre-wrap;
          818  +	    word-wrap: break-word;
          819  +	}
          820  +	pre, code {
          821  +	    font-family: Consolas, monospace; font-size: 100%;
          822  +	}
          823  +	.infield * {
          824  +	    vertical-align: middle;
          825  +	}
          826  +	.tin {
          827  +	    width: 85%;
          828  +	    resize: none;
          829  +	    overflow: auto;
   730    830   	}
   731         -    } else {
   732         -	append D {
   733         -	    body {
   734         -		font-family: sans-serif, Helvetica; font-size: 90%;
   735         -	    }
   736         -	}
   737         -    }
   738         -
   739         -    append D {
   740         -	textarea {
          831  +	.tin, .tin:disabled {
   741    832   	    border: 1px solid #AAAAAA;
          833  +	    background: transparent;
          834  +	    color: inherit;
   742    835   	    margin: 4px;
   743    836   	    outline: none;
   744    837   	    padding: 4px;
   745    838   	}
   746         -	textarea:focus {
          839  +	.tin:focus {
   747    840   	    border: 3px solid rgba(81, 203, 238, 1);
   748    841   	    margin: 2px;
   749    842   	    padding: 4px;
   750    843   	}
          844  +	</style>
   751    845       }
   752    846   
   753         -    append D {</style>}
   754         -
   755    847       # SCRIPT
   756         -    append D {
          848  +    set D_script {
   757    849   	<script type="text/javascript">
   758    850   
   759    851   	var msLike = /MSIE|Trident|Edge/i.test(navigator.userAgent);
   760    852   
   761    853   	if (!msLike && !window.external) {
   762    854   	    /* See also webview.h */
   763    855   	    window.external = {
................................................................................
   765    857   		    window.webkit.messageHandlers.external.postMessage(str);
   766    858   		}
   767    859   	    };
   768    860   	}
   769    861   
   770    862   	var needsClear = new Array();
   771    863   
   772         -	var RunTcl = function(str) { window.external.invoke(str); };
          864  +	var RunTcl = function(str) {
          865  +	    window.external.invoke(str);
          866  +	};
   773    867   
   774    868   	/* The 2nd sin: this drives the Tcl event loop. */
   775    869   	var Gtimer = window.setInterval(function() {
   776         -	    window.external.invoke("0 ::tsb::ping\n");
          870  +	    window.external.invoke("0 ::tsb::ping");
   777    871   	}, 20);
   778    872   
   779    873   	var Wclear = function(id) {
   780    874   	    if (!needsClear[id]) {
   781    875   		return;
   782    876   	    }
   783    877   	    var output =
................................................................................
   872    966   	    var input = document.getElementById('code' + id);
   873    967   	    needsClear[id] = true;
   874    968   	    RunTcl("" + id + " " + input.value);
   875    969   	};
   876    970   
   877    971   	var Field = function(id) {
   878    972   	    var div = document.createElement('div');
   879         -	    div.classname = 'field';
   880         -	    var html = '<div class="field" id="in' + id + '">';
   881         -	    html += '<label for="code' + id + '"';
   882         -	    if (msLike) {
   883         -		html += ' style="font-family: Consolas, sans-serif;"';
   884         -	    } else {
   885         -		html += ' style="vertical-align: 10px; font-family: monospace;"';
   886         -	    }
   887         -	    html += '>  in(' + id + ')  </label>';
   888         -	    html += '<textarea id="code' + id + '" rows="1"';
   889         -	    if (msLike) {
   890         -		html += ' style="width: 90%; resize: none;';
   891         -		html += ' font-family: Consolas, monospace;';
   892         -		html += ' overflow: hidden"';
   893         -	    } else {
   894         -		html += ' style="width: 90%; resize: none;';
   895         -		html += ' font-family: monospace; overflow: hidden"';
   896         -	    }
   897         -	    html += '></textarea></div><div class="field"';
   898         -	    html += ' id="out' + id + '-pre"';
   899         -	    if (msLike) {
   900         -		html += ' style="word-wrap: break-word;"';
   901         -	    } else {
   902         -		html += ' style="overflow-wrap: break-word;"';
   903         -	    }
   904         -	    html += '><pre></pre></div>';
   905         -	    html += '<div class="field" id="out' + id + '-raw"></div>';
          973  +	    div.className = 'field';
          974  +	    var html = '\n <div class="infield" id="in' + id + '">';
          975  +	    html += '\n  <label for="code' + id + '">in(' + id + ')</label>';
          976  +	    html += '\n  <textarea class="tin" id="code' + id + '" rows="1"';
          977  +	    html += '></textarea>\n </div>\n <div';
          978  +	    html += ' id="out' + id + '-pre">';
          979  +	    html += '<pre></pre></div>';
          980  +	    html += '\n <div id="out' + id + '-raw"></div>\n';
   906    981   	    div.innerHTML = html;
   907    982   	    document.body.appendChild(div);
          983  +	    /* For better readability only. */
          984  +	    document.body.appendChild(document.createTextNode('\n'));
   908    985   	    needsClear[id] = null;
   909    986   	    var input = document.getElementById('code' + id);
   910    987   	    input.addEventListener('keydown', function(event) {
   911    988   		var code;
   912    989   		var shift = false;
   913    990   		if (window.event) {
   914    991   		    code = window.event.keyCode;
................................................................................
   954   1031   		if (fields.length > 0) {
   955   1032   		    fields[0].parentNode.removeChild(fields[0]);
   956   1033   		    continue;
   957   1034   		}
   958   1035   		break;
   959   1036   	    }
   960   1037   	};
         1038  +
         1039  +	var Dump = function() {
         1040  +	    var i;
         1041  +	    var values = new Array();
         1042  +	    var fields = document.getElementsByClassName('tin');
         1043  +	    for (i = 0; i < fields.length; i++) {
         1044  +		values[i] = fields[i].value;
         1045  +		fields[i].innerHTML = values[i];
         1046  +		fields[i].disabled = true;
         1047  +	    }
         1048  +	    window.external.invoke(document.body.innerHTML);
         1049  +	    fields = document.getElementsByClassName('tin');
         1050  +	    for (i = 0; i < fields.length; i++) {
         1051  +		fields[i].disabled = false;
         1052  +		fields[i].innerHTML = "";
         1053  +		fields[i].value = values[i];
         1054  +	    }
         1055  +	};
   961   1056   
   962   1057   	/* Is this really needed? */
   963   1058   	window.addEventListener('unload', function(event) {
   964   1059   	    window.clearInterval(Gtimer);
   965   1060   	    Gtimer = null;
   966   1061   	});
   967   1062   
   968   1063   	</script>
   969   1064       }
   970   1065   
   971         -    # BODY (empty), END
   972         -    append D {</head><body></body></html>}
         1066  +    # Assemble pieces with empty BODY.
         1067  +    set D $D_head
         1068  +    append D $D_style $D_script {</head><body></body></html>}
   973   1069   
   974   1070       # The URL to be displayed.
   975   1071       # Windows:   data:text/html,<html><head><script>window.external.invoke("0 ::tsb::reload");</script></head></html>
   976   1072       # MacOS:     data:text/html,<html><head><script>window.external.invoke("0 ::tsb::reload");</script></head></html>
   977   1073       # WebkitGtk: data:text/html,<html><head><script>window.webkit.messageHandlers.extern.postMessage("0 ::tsb::reload");</script></head></html>
   978   1074   
   979   1075       variable U
................................................................................
  1014   1110   
  1015   1111   set W [::twv::new -width 800 -height 600 -title $::tsb::title \
  1016   1112   	   -url $::tsb::U -resizable 1 -debug 0 \
  1017   1113   	   -callback ::tsb::call_from_js]
  1018   1114   
  1019   1115   # On Windows this seems the way to load, maybe timing?
  1020   1116   
  1021         -if {!$::tsb::ready} {
         1117  +if {($tcl_platform(platform) eq "windows") && !$::tsb::ready} {
         1118  +    after cancel ::tsb::reload0
  1022   1119       ::tsb::reload0
  1023   1120   }
  1024   1121   
  1025   1122   # Enter the webview event loop.
  1026   1123   
  1027   1124   $W run
  1028   1125   
  1029   1126   # Done.
  1030   1127   
  1031   1128   exit 0