From: cbbrown@io.org (Christopher B. Browne)
Newsgroups: comp.os.linux.development.apps,
comp.os.linux.misc,comp.os.linux.x
Subject: Re: An on-line presentation maker..
Date: 2 May 1995 14:44:39 -0400
Message-ID: <3o5uin$9lm@ionews.io.org>
References: <SATAM.95Apr26145121@saathi.ncst.ernet.in>
In article <SATAM.95Apr26145121@saathi.ncst.ernet.in>,
Kirtikumar G. Satam <satam@saathi.ncst.ernet.in> wrote:
>Hello!
> Does anyone knows a utility like Microsoft (sic!) Powerpoint that
>can run under Linux and X (or maybe SVGALIB). With the proliferation of
>Notebooks with Linux, it will be a good idea to have presentations created
>in Linux instead of Windows. Something which is tcl/tk based, maybe?
I've heard of a commercial product called something like "Onyx" that can
read/write Powerpoint, and runs under X. Not sure if it's compatible.
It would be nice to have the SVGALIB option; that probably precludes
using tcl/tk.
--
Christopher Browne - cbbrown@io.org
Microsoft Office - What you use when you don't have access to good software.
As it happens, I've done a presentation from a linux laptop, using vanilla tcl/tk wish as a presentation manager. Presumably that means that an SVGALIB version is not feasible, but you never know where the tk widgets will be ported next...
What I've done isn't anywhere near as powerful as Powerpoint in terms of creating the materials, but it was adequate for my purpose.
The scheme I used was very simple indeed. My normal editor is now a simple wish script, and I have (essentially) a "mode" where I can edit drawing commands for a lookaside canvas. A man page and code for the editor and the canvas extensions are all included below, as well as the code I whipped up to create the overhead slides on my linux laptop. I also have stored the resulting slides in GIF form, so those of you without wish running can see the results. And for luck, here's an xref to the news posting that points to this html page (this link may well not work, given the slings and arrows that news expiry and/or propogation is heir to, but as I say, just for luck).
Further, the approach is probably a good deal more YAFIYGI than the WYSIWYG which people tend to be more comfortable with nowadays. Nevertheless, the notion of having a "presentation slides" mode right in your editor is fairly reasonable. And having tcl/tk embedded means I can try out appearances, add arbitrary code or subprograms (via exec if necessary), send the slide to a postscript printer via
exec lpr << [.c.c postscript]
and in general apply the good-old text-oriented
YAFIYGI
Unix tools to about any presentation problem needed.
So, for my purposes, this was just about ideal: I didn't have to learn a new tool (I already know wish), I could display the slides right from the laptop through a VGA-to-overhead gadget, and (especially nice for this presentation) I could go right into the tcl/tk demo after I'd given my talk to the slides.
xe [-t title] [-l length] [-r filename] [-e commands] [filename]
( Note, wish will process its usual switches (eg, the "-geometry" switch, but only the screen positioning portion of its syntax seems useful at present). )
The xe editor provides a very simple editing capability. A single text buffer is displayed, standard tcl text widget edits can be made in this buffer, and a supplementary set of operations is provided on a button panel.
The basic buttons are
The extension buttons each add a small button panel and a text argument to the top of the window (growing the window to make room). A "delete" button removes the addition when it is no longer needed, and some operation buttons (described below). The text is used as the parameter for some action to be taken.
Note, since the parameters of these operations are stored as tk text widgets, they can be altered by script as well as by X point-and-click. Further, if you have an action defined in one edit process, and you want to apply it to another edit process, it's simple enough to create a new operation in the other editor, and then copy the text across with X. One could even coin an operation that would use the tcl/tk "send" capability to do this will a single keystroke, but that is left as an exersise for the reader, as I find the "tcl" button coupled with a stand-aside "palette" of common operations adequate.
The rationale for using button-based operations and a stand-aside palette instead of menus is ergonomic. People with different editing styles could easily find another selection of style more efficient.
In addition to the basic operations and extended operations available via buttons, these actions have been added to Text widgets, in addition to the basic activities built-in to the base tk widget:
bind Text <Left> {%W mark set insert "insert - 1 chars"}
bind Text <Right> {%W mark set insert "insert + 1 chars"}
bind Text <Up> {%W mark set insert "insert - 1 lines"}
bind Text <Down> {%W mark set insert "insert + 1 lines"}
bind Text <ButtonRelease-3> {
%W mark set insert @%x,%y
%W insert insert [selection get]
}
This is simply cursor key positioning (for fine-grain movements and such), and single-mouse-button X-selection insertion (on button 3 so as not to conflict with the tk/tcl dragging convention on button 2).
Since wish is the basic executable, note that settings for wish in the .Xdefaults file will be used for background colors and such. The -r startup file, -e commands, or the tcl or tclop buttons, can be used to further configure a running editor.
The display is organized into a panel for the pushbuttons, an ops area for additional filters and search strings, and the main text window. These are named .panel, .ops, and .text respectively. Thus, if you had some text you wanted to insert literally at the insertion cursor, you could
.text insert insert "hello world"
Or, for some other examples
.panel.quit configure -background red
.text configure -font times_roman10
.text configure -height 50
The individual operations are given numbers counted off from a global variable "opnum". Thus, to prepare a script to make available a common set of filtration commands, one might
newop filter spell
newop filter fmt
The global variable "commit" contains the text of the text buffer the last time it was committed.
The global variable "fn" regulates which file will be written when the "write" button is pushed.
Report anything gruesome to throopw@sheol.org (Wayne Throop).
Probably the most annoying "features" will be lack of prompting for write upon quit, the lack of a visible indication of whether the current buffer has been written, and the lack of safety across system crashes. I've gotten used to these "features" because they tend to exist on the simpler platforms I've used (eg: MessyDos), and so I save frequently and pray fervently, and it seems to work for me. If anybody has ideas on how the xe script I use can be improved in these regards, I'd be very interested in hearing about it.
$HOME/tmp
#!/usr/bin/env app-wish-f
#
# edit file argument on a text window
if [catch {
global title
global fn
global opnum
global commit
global height
global rfile
set rfile ""
set eexpr ""
set height 40
set title xe
while {[regexp {^-[tlre]$} [lindex $argv 0]]} {
if [regexp {^-t$} [lindex $argv 0]] { set title [lindex $argv 1] }
if [regexp {^-l$} [lindex $argv 0]] { set height [lindex $argv 1] }
if [regexp {^-r$} [lindex $argv 0]] { set rfile [lindex $argv 1] }
if [regexp {^-e$} [lindex $argv 0]] { set eexpr [lindex $argv 1] }
set argv [lrange $argv 2 end]
}
if [string length [lindex $argv 0]] {
set fn [lindex $argv 0]
} {
set fn [exec sh -c {echo $HOME/tmp/`date +%y%m%d%H%M%S`}]
if ![file isfile $fn] {exec touch $fn}
}
if [regexp {^xe$} $title] {
regsub {^.*/} $fn "" title
set title "xe $title"
}
set opnum 0
proc write-buffer {} {
global fn
set f [open $fn w]
puts $f [.text get 1.0 end] nonewline
close $f
}
proc commit-text {} {
global commit
set commit [.text get 1.0 end]
}
proc revert-text {} {
global commit
set x [.text get 1.0 end]
.text delete 1.0 end
.text insert 1.0 $commit
set commit $x
}
proc newtcl {} {
global opnum
set opnum [expr $opnum+1]
frame .ops.$opnum
button .ops.$opnum.delete -text del -command "destroy .ops.$opnum"
button .ops.$opnum.do -text tcl -command "dotcl $opnum"
text .ops.$opnum.text -font 6x13 -height 1 -width 60
foreach b {delete do} {pack .ops.$opnum.$b -side left}
pack .ops.$opnum.text -side left -fill x
pack .ops.$opnum -side top -fill x
}
proc newmark {} {
global opnum
set position [.text index @1,1]
newtcl
.ops.$opnum.text insert 1.0 ".text yview $position"
}
proc dotcl {n} {
eval [.ops.$n.text get 1.0 end]
}
proc newsearch {} {
global opnum
set opnum [expr $opnum+1]
frame .ops.$opnum
button .ops.$opnum.delete -text del -command "destroy .ops.$opnum"
button .ops.$opnum.next -text next -command "nextsearch $opnum"
button .ops.$opnum.prev -text prev -command "prevsearch $opnum"
text .ops.$opnum.text -font 6x13 -height 1 -width 60
foreach b {delete next prev} {pack .ops.$opnum.$b -side left}
pack .ops.$opnum.text -side left -fill x
pack .ops.$opnum -side top -fill x
}
proc nextsearch {n} {search next [.ops.$n.text get 1.0 end]}
proc prevsearch {n} {search prev [.ops.$n.text get 1.0 end]}
proc search {which re} {
if {$which=="next"} {
set incr {incr i}
set test {$i<=$iend}
set istart [expr int([.text index "insert linestart + 1 lines"])]
set iend [.text index "end linestart"]
} else {
set incr {incr i -1}
set test {$i>=$iend}
set istart [expr int([.text index "insert linestart - 1 lines"])]
set iend 1.0
}
if [regexp {^-i (.*)} $re re re] {set opt -nocase} {set opt --}
for {set i $istart} $test $incr {
if [regexp $opt $re [.text get "$i.0 linestart" "$i.0 lineend"]] {
.text yview $i.0
.text mark set insert $i.0
return
}
}
}
proc newfilter {} {
global opnum
set opnum [expr $opnum+1]
frame .ops.$opnum
button .ops.$opnum.delete -text del -command "destroy .ops.$opnum"
button .ops.$opnum.do -text do -command "dofilter $opnum"
text .ops.$opnum.text -font 6x13 -height 1 -width 60
foreach b {delete do} {pack .ops.$opnum.$b -side left}
pack .ops.$opnum.text -side left -fill x
pack .ops.$opnum -side top -fill x
}
proc dofilter {n} {
set text ""
if [catch {selection get} text] {set text ""}
.text insert insert \
[exec -keepnewline sh -c [.ops.$n.text get 1.0 end] \
<< $text]
}
proc newop {optype optext} {
global opnum
"new$optype"
.ops.$opnum.text insert 1.0 $optext
}
proc xd {args} {exec xd << [eval $args] &}
proc t {args} {
if [string length $args] {eval .text get $args} {.text get 1.0 end}
}
proc i {args} {eval .text insert $args}
proc s {args} {
if [string length $args] {eval selection $args} {selection get}
}
frame .panel
button .panel.quit -text quit -command "destroy ."
button .panel.write -text write -command write-buffer
button .panel.commit -text commit -command commit-text
button .panel.revert -text revert -command revert-text
button .panel.tcl -text tcl -command {eval [selection get]}
button .panel.tclop -text tclop -command "newtcl"
button .panel.mark -text mark -command "newmark"
button .panel.search -text search -command "newsearch"
button .panel.filter -text filter -command "newfilter"
frame .ops
frame .ops.nop
pack .ops.nop -side top
foreach b {quit write commit revert tcl tclop mark search filter} {
pack .panel.$b -side left
}
scrollbar .scroll -command ".text yview"
set f [open $fn r]
set commit [read $f]
text .text -yscroll ".scroll set" -wrap word -font 6x13 -height $height
close $f
pack .panel -side top -fill x
pack .ops -side top -fill x
pack .scroll -side left -fill y
pack .text -side left -fill x -fill y
bind Text <Left> {%W mark set insert "insert - 1 chars"}
bind Text <Right> {%W mark set insert "insert + 1 chars"}
bind Text <Up> {%W mark set insert "insert - 1 lines"}
bind Text <Down> {%W mark set insert "insert + 1 lines"}
bind Text <ButtonRelease-3> {
%W mark set insert @%x,%y
%W insert insert [selection get]
}
wm iconname . $title
wm title . $title
wm minsize . 100 100
if [string length $rfile] {source $rfile}
if [string length $eexpr] {eval $eexpr}
catch {source $env(HOME)/.xerc}
} err] {
tkerror $err
exit 2
}
#!/usr/bin/env app-wish-f
proc newop {type text} {
global opnum
"new$type"
.ops.$opnum.text insert 1.0 $text
}
source [exec hunt xe $env(PATH)]
toplevel .c
canvas .c.c -width 600 -height 800
pack .c.c
bind .c.c <Button-1> {.text insert insert "%x %y "
.c.c delete lastclick
.c.c create text %x %y -text X -fill red -tag lastclick}
bind .c.c <Button-2> {.text insert insert "[expr (%x+5)/10]0 [expr (%y+5)/10]0 "
.c.c delete lastclick
.c.c create text [expr (%x+5)/10]0 [expr (%y+5)/10]0 \
-text X -fill red -tag lastclick}
bind .c.c <Button-3> {.c.c delete lastclick
.c.c create text %x %y -text X -fill red -tag lastclick}
newop tcl {global fn;.c.c postscript -file "$fn.ps"}
newop tcl {.text insert insert [eval [selection get]]\n}
newop tcl {fresh-canvas}
newop tcl {draw-canvas}
newop tcl {fresh-text}
newop tcl {insert-canvas}
newop tcl {finditem}
newop tcl {deleteitem}
newop tcl {renewitem}
newop tcl {eval [selection get]}
proc creation-cmd {id} {
set cmd {drawitem}
set cmd "$cmd [lindex [.c.c gettags $id] 0]"
set cmd "$cmd [.c.c type $id]"
set cmd "$cmd [.c.c coords $id]"
foreach x [.c.c itemconfig $id] {
if [string length [lindex $x 4]] {
set cmd "$cmd [lindex $x 0] {[lindex $x 4]}"
}
}
return $cmd
}
proc fresh-canvas {} {
foreach x [.c.c find all] {
.c.c delete $x
}
}
proc fresh-text {} {
.text delete 1.0 end
}
proc draw-canvas {} {
eval [.text get 1.0 end]
}
proc insert-canvas {} {
.c.c delete lastclick
foreach x [.c.c find all] {
.text insert insert "[creation-cmd $x]\n\n"
}
}
proc newitem {id type args} {
set cmd "drawitem $id $type $args -tag $id\n\n"
.text insert insert $cmd
eval $cmd
}
proc renewitem {args} {
if [string length $args] {
.text insert insert [creation-cmd $args]\n
} {
.text insert insert [creation-cmd [lindex [selection get] 1]]\n
}
}
proc deleteitem {args} {
if [string length $args] {
.c.c delete $args
} {
.c.c delete [lindex [selection get] 1]
}
}
proc drawitem {id type args} {
eval ".c.c delete $id; .c.c create $type $args -tags $id"
}
proc moveitem {tag x1 y1 x2 y2} {
.c.c move $tag [xdiff $x1 $y1 $x2 $y2] [ydiff $x1 $y1 $x2 $y2]
}
proc scaleitem {tag xscale yscale} {
.c.c scale $tag [lindex [.c.c coords $tag] 0] [lindex [.c.c coords $tag] 1] \
$xscale $yscale
}
proc finditem {} {
set x [lindex [.c.c coords lastclick] 0]
set y [lindex [.c.c coords lastclick] 1]
.c.c delete lastclick
newop search "item [.c.c gettags [.c.c find closest $x $y]]"
}
proc xdiff {x1 y1 x2 y2} {return [expr $x2-$x1]}
proc ydiff {x1 y1 x2 y2} {return [expr $y2-$y1]}
proc xysq {x1 y1 x2 y2} {return "$x1 $y1 $x2 [expr $y1+$x2-$x1]"}
catch {destroy .menu}
menu .menu
bind .c.c <ButtonPress-1> {.menu post 300 10}
proc tb24 {} {return "-*-times-bold-r-normal--*-240-*"}
proc tm18 {} {return "-*-times-medium-r-normal--*-180-*"}
proc tm14 {} {return "-*-courier-medium-r-normal--*-140-*"}
proc tm12 {} {return "-*-times-medium-r-normal--*-120-*"}
proc cm18 {} {return "-*-courier-medium-r-normal--*-180-*"}
proc cm6 {} {return "-*-helvetica-medium-r-normal--*-80-*"}
proc defslide {name items} {
.menu add command -label $name -command [
append cmd fresh-canvas\n
append cmd "drawitem logo bitmap 5 5 -bitmap @alc.xbm -anchor nw\n"
append cmd "drawitem title text 200 15 -text [list $name] "
append cmd "-font [tm18] -anchor nw\n"
append cmd $items\n
append cmd ".menu unpost"
]
}
defslide cover {
drawitem face bitmap 320 340 -anchor s -bitmap @face.xbm
drawitem slide text 320 70 \
-anchor n -width 630 -justify center -font [tb24] -text {
X Windows Scripting
with
Tcl/Tk}
drawitem name text 320 345 -anchor n -font [tm12] -text {John Ousterhout}}
defslide outline {
drawitem s1 text 5 40 -anchor nw -width 630 -font [tm18] -text {
tcl language concepts
- general architecture
- everything is text
- simple C interface
- simplified expansion rules
- peculiarities
- packages
overview of the tk widget set
- the hello program
- general concepts
- geometry management
- bitmaps and xv
}
drawitem s2 text 310 40 -anchor nw -width 630 -font [tm18] -text {
demo
- xd, xe, xls, vxref, panel
- edv-browse, xnearest
- newsreader, mailreader
- /apps/Unsupported/lib/tk/demos
}}
defslide tcl_general_architecture {
drawitem s1 text 5 40 -anchor nw -width 630 -font [tm18] -text {
Everything is organized into shell-like commands:}
drawitem s2 text 5 100 -anchor nw -width 630 -font [cm18] -text {
variables: set a foo
files: puts $file "hello world\n"
processes/pipes: exec du -s | sort -n
arithmetic: expr (23+56)/162.0
flow control: if [expr $value<0] {
puts "It was negative"
} {
puts "It was not negative"
}
}}
defslide tcl_everything_text {
drawitem s1 text 5 60 -anchor nw -width 630 -font [tm18] -text {
Though shell-like, the command structure is *very* simple,
organized as lists of white-space separated words,
with two quoting conventions: {} and "",
and two substitution notations: [command] and $variable
}
drawitem s2 text 5 150 -anchor nw -width 630 -font [cm18] -text {
expr pow($a*$a+$b*$b,.5)
puts stderr {Now is the time for all good errors...}
puts "a is $a"
set a [if [expr $a<0] {expr 0-$a} {expr $a}]
}}
defslide tcl_C_interface {
drawitem s1 text 5 40 -anchor nw -width 630 -font [tm18] -text {
Tcl is intended to be extendable in C,
so tcl commands are implemented as C procedures
following the usual interface:
}
drawitem s2 text 5 150 -anchor nw -width 630 -font [cm18] -text {
int cmd( int argc, char **argv );
}}
defslide tcl_expansion_rules {
drawitem s1 text 5 40 -anchor nw -width 630 -font [tm18] -text {
- there is no rescanning
- text inserted via $ and [] cannot form a word break,
even if/when it includes whitespace
- explicit "eval" cleanly applied
to constructs of "list"
- substitutions are only made into "" quoted text,
and the nesting of {} quoted text is important
}
drawitem s2 text 5 180 -anchor nw -width 630 -font [cm18] -text {
format {string is %s} "foo"
button .foo -command { ... %x,%y ... }
eval "cmd $foo $bar" vs eval [list cmd $foo $bar]
vs eval {list cmd $foo $bar}
cmd $foo vs cmd "$foo" vs cmd {$foo}
if [expr $something] {} {} vs if {$something} {} {}
vs if $something {} {}
}}
defslide tcl_peculiarities {
drawitem s1 text 5 40 -anchor nw -width 630 -font [tm18] -text {
most common confusions:
- no resubstitution
- confusing [] with {}
- timing of expansions within {}
- expecting "expr" to do more than it does
- comments within {} or []
}
drawitem s2 text 5 180 -anchor nw -width 630 -font [cm18] -text {
set a "b c"; cmd $a
while [some-condition] {foo}
proc p {$args} { ... }
button .foo -command {manipulate $state_of_the_world}
expr 1/2
}}
defslide tcl_packages {
drawitem s1 text 5 40 -anchor nw -width 630 -font [tm18] -text {
tcl base package, with variables, control structures,
list manipulation, regular expressions, etc
tk X windows widget set
tclX extended tcl, such as data structures, time arithmetic, etc
[incr tcl] (alias "itcl") object oriented extension
dp interface to sockets for distributed programming
expect drive interactive character-oriented programs from scripts
tkSteal manipulate X interactions of other programs
(eg: have emacs or xterm as a widget inside a tk app)
dl dynamic loading of .o, .a, and shared library files
along with html parsing, video image filtering, telephone answering machine dialing, and many, many more (including tcl versions for DOS, OS/2 and NT, and tk versions for Microsoft Windows)
}}
defslide tk_hello {
drawitem s2 text 5 40 -anchor nw -width 630 -font [cm18] -text {
#!/usr/bin/env wish-f
button .hello -text "hello, world" -command {destroy .}
pack .hello}
drawitem s3 text 5 120 -anchor nw -width 500 -font [cm6] \
-text [exec grep -n ^ xhello.c | head -26]
drawitem s4 text 300 120 -anchor nw -width 500 -font [cm6] \
-text [exec grep -n ^ xhello.c | tail -26]
}
defslide tk_general_concepts {
drawitem s1 text 5 40 -anchor nw -width 630 -font [tm18] -text {
- each widget has a creation command,
which in turn creates "widget commands"
(thus, the tk widget namespace is tucked away
inside the tcl command namespace)
- the "bind" command regulates responses of
the widgets to various events in the X event loop
- events propogate between widgets via callbacks
}
drawitem s2 text 5 180 -anchor nw -width 630 -font [cm18] -text {
scrollbar .scroll -command ".text yview"
text .text -yscroll ".scroll set"
bind Text <Down> {%W mark set insert "insert + 1 lines"}
bind Text <ButtonRelease-3> {
%W mark set insert @%x,%y
%W insert insert [selection get]
}
}}
defslide tk_geometry_management {
drawitem s1 text 5 40 -anchor nw -width 630 -font [tm18] -text {
- done via the "pack" command
- widgets can be instructed to be as small as possible,
or they can expand to take any available space
- each widget can be placed relative to any preexisting
one, or at any orientation within its parent
(which means that simple placement is easy)
- XF
}
drawitem s2 text 5 200 -anchor nw -width 630 -font [cm18] -text {
foreach b {quit write commit revert tcl
tclop mark search filter} {
pack .panel.$b -side left
}
pack .panel -side top -fill x
pack .scroll -side left -fill y
pack .text -side left -fill x -fill y
}}
# defslide foo {
# drawitem s1 text 5 40 -anchor nw -width 630 -font [tm18] -text {
# }
# drawitem s2 text 5 180 -anchor nw -width 630 -font [cm18] -text {
# }}
defslide additional_info {
drawitem s1 text 5 40 -anchor nw -width 630 -font [tm14] -text {
man pages online at [/local/path/name]
Tcl and the Tk Toolkit, by John K. Ousterhout
from Addison Wesley, ISBN 0-201-63337-X.
online demos at [/local/path/name]
(invoke via wish -file <filename>)
faq available at
[/local/path/name]
www page (eg) at
http://www.cis.ohio-state.edu/hypertext/faq/usenet/tcl-faq/top.html
materials for this presentation available in dir
[/local/path/name]
lively newsgroup at comp.lang.tcl
}}
:WYSIWYG: /wiz'ee-wig/ adj. Describes a user interface under
which "What You See Is What You Get", as opposed to one that uses
more-or-less obscure commands which do not result in immediate
visual feedback. True WYSIWYG in environments supporting multiple
fonts or graphics is a a rarely-attained ideal; there are variants
of this term to express real-world manifestations including
WYSIAWYG (What You See Is *Almost* What You Get) and
WYSIMOLWYG (What You See Is More or Less What You Get). All these
can be mildly derogatory, as they are often used to refer to
dumbed-down {user-friendly} interfaces targeted at
non-programmers; a hacker has no fear of obscure commands (compare
{WYSIAYG}). On the other hand, {EMACS} was one of the very first
WYSIWYG editors, replacing (actually, at first overlaying) the
extremely obscure, command-based {TECO}. See also {WIMP
environment}. [Oddly enough, WYSIWYG has already made it into the
OED, in lower case yet. --- ESR]
On the other hand, YAFIYGI is the "You asked for it, you got it"
interface, characterized by Unix tools like pic (as opposed to the
MUMBLEdraw family of tools) or nroff tex in general (as opposed to
Framemaker/Interleaf/whatever family of tools).