summaryrefslogtreecommitdiff
path: root/ipl/gprocs
diff options
context:
space:
mode:
authorIgor Pashev <pashev.igor@gmail.com>2013-01-27 23:51:56 +0000
committerIgor Pashev <pashev.igor@gmail.com>2013-01-27 23:51:56 +0000
commit6ab0c0f5bf14ed9c15370407b9ee7e0b4b089ae1 (patch)
tree926065cf45450116098db664e3c61dced9e1f21a /ipl/gprocs
downloadicon-6ab0c0f5bf14ed9c15370407b9ee7e0b4b089ae1.tar.gz
Initial upstream version 9.4.3upstream/9.4.3
Diffstat (limited to 'ipl/gprocs')
-rw-r--r--ipl/gprocs/attribs.icn127
-rw-r--r--ipl/gprocs/autopost.icn71
-rw-r--r--ipl/gprocs/barchart.icn212
-rw-r--r--ipl/gprocs/bevel.icn534
-rw-r--r--ipl/gprocs/bitplane.icn341
-rw-r--r--ipl/gprocs/button.icn183
-rw-r--r--ipl/gprocs/cardbits.icn602
-rw-r--r--ipl/gprocs/cells.icn191
-rw-r--r--ipl/gprocs/clip.icn78
-rw-r--r--ipl/gprocs/clipping.icn135
-rw-r--r--ipl/gprocs/clrnames.icn37
-rw-r--r--ipl/gprocs/clrutils.icn45
-rw-r--r--ipl/gprocs/color.icn526
-rw-r--r--ipl/gprocs/colorway.icn470
-rw-r--r--ipl/gprocs/colrlist.icn63
-rw-r--r--ipl/gprocs/colrmodl.icn273
-rw-r--r--ipl/gprocs/colrspec.icn48
-rw-r--r--ipl/gprocs/cwutils.icn161
-rw-r--r--ipl/gprocs/decay.icn84
-rw-r--r--ipl/gprocs/dialog.icn735
-rw-r--r--ipl/gprocs/dialogs.icn21
-rw-r--r--ipl/gprocs/distance.icn31
-rw-r--r--ipl/gprocs/drag.icn169
-rw-r--r--ipl/gprocs/drawcard.icn194
-rw-r--r--ipl/gprocs/drawcolr.icn69
-rw-r--r--ipl/gprocs/drawlab.icn108
-rw-r--r--ipl/gprocs/dsetup.icn293
-rw-r--r--ipl/gprocs/enqueue.icn157
-rw-r--r--ipl/gprocs/event.icn43
-rw-r--r--ipl/gprocs/evmux.icn236
-rw-r--r--ipl/gprocs/evplay.icn49
-rw-r--r--ipl/gprocs/evrecord.icn51
-rw-r--r--ipl/gprocs/fetchpat.icn45
-rw-r--r--ipl/gprocs/fstars.icn94
-rw-r--r--ipl/gprocs/fstartbl.icn67
-rw-r--r--ipl/gprocs/gdisable.icn81
-rw-r--r--ipl/gprocs/getcolrs.icn377
-rw-r--r--ipl/gprocs/gifsize.icn51
-rw-r--r--ipl/gprocs/glabels.icn68
-rw-r--r--ipl/gprocs/glib.icn789
-rw-r--r--ipl/gprocs/gpxlib.icn130
-rw-r--r--ipl/gprocs/gpxop.icn314
-rw-r--r--ipl/gprocs/graphics.icn34
-rw-r--r--ipl/gprocs/grecords.icn36
-rw-r--r--ipl/gprocs/gtrace.icn203
-rw-r--r--ipl/gprocs/ifg.icn33
-rw-r--r--ipl/gprocs/imagedim.icn64
-rw-r--r--ipl/gprocs/imageseq.icn60
-rw-r--r--ipl/gprocs/imgcolor.icn36
-rw-r--r--ipl/gprocs/imrutils.icn332
-rw-r--r--ipl/gprocs/imscanon.icn61
-rw-r--r--ipl/gprocs/imscolor.icn423
-rw-r--r--ipl/gprocs/imsutils.icn607
-rw-r--r--ipl/gprocs/imutils.icn21
-rw-r--r--ipl/gprocs/imxform.icn488
-rw-r--r--ipl/gprocs/interact.icn409
-rw-r--r--ipl/gprocs/isdplot.icn259
-rw-r--r--ipl/gprocs/isdxplot.icn245
-rw-r--r--ipl/gprocs/joinpair.icn44
-rw-r--r--ipl/gprocs/jolygs.icn55
-rw-r--r--ipl/gprocs/linddefs.icn424
-rw-r--r--ipl/gprocs/linddraw.icn63
-rw-r--r--ipl/gprocs/lindrec.icn22
-rw-r--r--ipl/gprocs/lindterp.icn73
-rw-r--r--ipl/gprocs/lsystem.icn181
-rw-r--r--ipl/gprocs/mapnav.icn320
-rw-r--r--ipl/gprocs/mirror.icn66
-rw-r--r--ipl/gprocs/modlines.icn51
-rw-r--r--ipl/gprocs/navitrix.icn279
-rw-r--r--ipl/gprocs/optwindw.icn177
-rw-r--r--ipl/gprocs/orbits.icn82
-rw-r--r--ipl/gprocs/overlay.icn48
-rw-r--r--ipl/gprocs/palettes.icn405
-rw-r--r--ipl/gprocs/pattread.icn42
-rw-r--r--ipl/gprocs/patutils.icn584
-rw-r--r--ipl/gprocs/patxform.icn504
-rw-r--r--ipl/gprocs/pixelmap.icn59
-rw-r--r--ipl/gprocs/popular.icn54
-rw-r--r--ipl/gprocs/psrecord.icn555
-rw-r--r--ipl/gprocs/putpixel.icn163
-rw-r--r--ipl/gprocs/randarea.icn65
-rw-r--r--ipl/gprocs/randfigs.icn48
-rw-r--r--ipl/gprocs/rawimage.icn143
-rw-r--r--ipl/gprocs/repeats.icn53
-rw-r--r--ipl/gprocs/rgbcomp.icn98
-rw-r--r--ipl/gprocs/rgbrec.icn48
-rw-r--r--ipl/gprocs/rpolys.icn40
-rw-r--r--ipl/gprocs/rstars.icn58
-rw-r--r--ipl/gprocs/rstartbl.icn46
-rw-r--r--ipl/gprocs/select.icn99
-rw-r--r--ipl/gprocs/slider.icn210
-rw-r--r--ipl/gprocs/spirals.icn48
-rw-r--r--ipl/gprocs/spokes.icn54
-rw-r--r--ipl/gprocs/strpchrt.icn126
-rw-r--r--ipl/gprocs/subturtl.icn275
-rw-r--r--ipl/gprocs/symrand.icn48
-rw-r--r--ipl/gprocs/tieedit.icn876
-rw-r--r--ipl/gprocs/tieutils.icn424
-rw-r--r--ipl/gprocs/tile.icn64
-rw-r--r--ipl/gprocs/tiler.icn74
-rw-r--r--ipl/gprocs/turtle.icn446
-rw-r--r--ipl/gprocs/twists.icn83
-rw-r--r--ipl/gprocs/vbuttons.icn418
-rw-r--r--ipl/gprocs/vcoupler.icn327
-rw-r--r--ipl/gprocs/vdialog.icn296
-rw-r--r--ipl/gprocs/vfilter.icn40
-rw-r--r--ipl/gprocs/vframe.icn355
-rw-r--r--ipl/gprocs/vgrid.icn143
-rw-r--r--ipl/gprocs/vidgets.icn28
-rw-r--r--ipl/gprocs/viface.icn421
-rw-r--r--ipl/gprocs/vlist.icn964
-rw-r--r--ipl/gprocs/vmenu.icn673
-rw-r--r--ipl/gprocs/vpane.icn167
-rw-r--r--ipl/gprocs/vquery.icn194
-rw-r--r--ipl/gprocs/vradio.icn322
-rw-r--r--ipl/gprocs/vscroll.icn671
-rw-r--r--ipl/gprocs/vsetup.icn250
-rw-r--r--ipl/gprocs/vslider.icn387
-rw-r--r--ipl/gprocs/vstd.icn146
-rw-r--r--ipl/gprocs/vstyle.icn363
-rw-r--r--ipl/gprocs/vtext.icn479
-rw-r--r--ipl/gprocs/wattrib.icn51
-rw-r--r--ipl/gprocs/weavegif.icn132
-rw-r--r--ipl/gprocs/wifisd.icn324
-rw-r--r--ipl/gprocs/win.icn54
-rw-r--r--ipl/gprocs/window.icn380
-rw-r--r--ipl/gprocs/winsnap.icn62
-rw-r--r--ipl/gprocs/wipe.icn112
-rw-r--r--ipl/gprocs/wopen.icn230
-rw-r--r--ipl/gprocs/xbfont.icn322
-rw-r--r--ipl/gprocs/xcolor.icn21
-rw-r--r--ipl/gprocs/xcompat.icn110
-rw-r--r--ipl/gprocs/xform.icn60
-rw-r--r--ipl/gprocs/xformimg.icn168
-rw-r--r--ipl/gprocs/xgtrace.icn81
-rw-r--r--ipl/gprocs/xio.icn22
-rw-r--r--ipl/gprocs/xplane.icn21
-rw-r--r--ipl/gprocs/xputpixl.icn21
-rw-r--r--ipl/gprocs/xqueue.icn21
-rw-r--r--ipl/gprocs/xutils.icn37
140 files changed, 28214 insertions, 0 deletions
diff --git a/ipl/gprocs/attribs.icn b/ipl/gprocs/attribs.icn
new file mode 100644
index 0000000..612eca6
--- /dev/null
+++ b/ipl/gprocs/attribs.icn
@@ -0,0 +1,127 @@
+############################################################################
+#
+# File: attribs.icn
+#
+# Subject: Procedure to set attributes via dialog
+#
+# Author: Ralph E. Griswold
+#
+# Date: February 17, 1997
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# This procedure produces a dialog in which the user can change
+# the most commonly used graphics attributes.
+#
+# Problems: If a text-entry field is not long enough to hold the current
+# value for an attribute, the attribute has to be edited. Also, a
+# slider is not the best way of changing the gamma attribute -- it's
+# not possible to set a precise value. A slider was used mostly for
+# demonstration purposes.
+#
+############################################################################
+#
+# Requires: Version 9 graphics
+#
+############################################################################
+#
+# Links: dsetup
+#
+############################################################################
+
+link dsetup # dialog setup
+
+procedure attribs(win) #: set graphics attributes via dialog
+ static atts
+
+ initial atts := table() # table of vidget IDs
+
+ /win := &window
+
+ # Assign values from current attributes.
+
+ atts["1_fg"] := Fg(win)
+ atts["2_bg"] := Bg(win)
+ atts["3_font"] := Font(win)
+ atts["4_linewidth"] := WAttrib(win, "linewidth")
+ atts["5_pattern"] := WAttrib(win, "pattern")
+ atts["linestyle"] := WAttrib(win, "linestyle")
+ atts["fillstyle"] := WAttrib(win, "fillstyle")
+ atts["gamma"] := WAttrib(win, "gamma")
+
+ # Call up the dialog.
+
+ repeat {
+
+ attributes(win, atts) == "Okay" | fail
+
+ # Set attributes from table.
+
+ Fg(win, atts["1_fg"]) | {
+ Notice("Invalid foreground color.")
+ next
+ }
+ Bg(win, atts["2_bg"]) | {
+ Notice("Invalid background color.")
+ next
+ }
+ Font(win, atts["3_font"]) | {
+ Notice("Invalid font.")
+ next
+ }
+ WAttrib(win, "linewidth=" || integer(atts["4_linewidth"])) | {
+ Notice("Invalid linewidth.")
+ next
+ }
+ WAttrib(win, "pattern=" || atts["5_pattern"]) | {
+ Notice("Invalid pattern.")
+ next
+ }
+ WAttrib(win, "linestyle=" || atts["linestyle"])
+ WAttrib(win, "fillstyle=" || atts["fillstyle"])
+ WAttrib(win, "gamma=" || atts["gamma"])
+
+ return
+
+ }
+
+end
+
+#===<<vib:begin>>=== modify using vib; do not remove this marker line
+procedure attributes(win, deftbl)
+static dstate
+initial dstate := dsetup(win,
+ ["attributes:Sizer::1:0,0,370,400:attributes",],
+ ["0.5:Label:::105,204,21,13:0.5",],
+ ["1.0:Label:::135,203,21,13:1.0",],
+ ["1_fg:Text::35:10,20,339,19: fg: \\=",],
+ ["2.0:Label:::199,203,21,13:2.0",],
+ ["2_bg:Text::35:10,52,339,19: bg: \\=",],
+ ["3.0:Label:::261,204,21,13:3.0",],
+ ["3_font:Text::35:11,80,339,19: font: \\=",],
+ ["4.0:Label:::324,204,21,13:4.0",],
+ ["4_linewidth:Text::3:11,110,115,19:line width: \\=",],
+ ["5_pattern:Text::35:11,140,339,19: pattern: \\=",],
+ ["button1:Button:regular::206,350,60,30:Cancel",],
+ ["fill label:Label:::202,241,70,13:fill style",],
+ ["fillstyle:Choice::3:195,262,85,63:",,
+ ["solid","textured","masked"]],
+ ["gamma:Slider:h:1:97,174,253,20:0.5,4.0,1.0",],
+ ["glabel:Label:::11,176,84,13: gamma: ",],
+ ["line label:Label:::100,241,70,13:line style",],
+ ["linestyle:Choice::3:96,262,78,63:",,
+ ["solid","striped","dashed"]],
+ ["okay:Button:regular:-1:106,350,60,30:Okay",],
+ ["tick1:Line:::117,196,117,201:",],
+ ["tick2:Line:::146,195,146,200:",],
+ ["tick3:Line:::209,195,209,200:",],
+ ["tick4:Line:::272,195,272,200:",],
+ ["tick5:Line:::335,195,335,200:",],
+ )
+return dpopup(win, deftbl, dstate)
+end
+#===<<vib:end>>=== end of section maintained by vib
diff --git a/ipl/gprocs/autopost.icn b/ipl/gprocs/autopost.icn
new file mode 100644
index 0000000..138814b
--- /dev/null
+++ b/ipl/gprocs/autopost.icn
@@ -0,0 +1,71 @@
+############################################################################
+#
+# File: autopost.icn
+#
+# Subject: Procedures to activate PostScript recorder
+#
+# Author: Gregg M. Townsend
+#
+# Date: October 11, 1994
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# These procedures, when linked with an unsuspecting Icon program,
+# cause psrecord (q.v) to begin recording PostScript commands when
+# an X window is opened. This is done by overloading the built-in
+# "open" function.
+#
+# The results of this may or may not be usable depending on how the
+# original program is coded. Psrecord cannot emulate all the X calls
+# and works best with programs designed for it.
+#
+# "stop" and "exit" are also overloaded to try and terminate the
+# PostScript file properly. Other program exit paths, notably a
+# return from the main procedure, are not caught.
+#
+############################################################################
+#
+# Links: psrecord
+#
+############################################################################
+#
+# Requires: Version 9 graphics
+#
+############################################################################
+
+link psrecord
+
+invocable "open", "stop", "exit"
+
+procedure open(args[])
+ local f
+ static realfunc
+ initial realfunc := proc("open", 0)
+
+ f := (realfunc ! args) | fail
+ if args[2] ? upto('gx') then
+ PSEnable(f)
+ return f
+end
+
+procedure stop(args[])
+ local f
+ static realfunc
+ initial realfunc := proc("stop", 0)
+
+ PSDone()
+ return realfunc ! args
+end
+
+procedure exit(args[])
+ local f
+ static realfunc
+ initial realfunc := proc("exit", 0)
+
+ PSDone()
+ return realfunc ! args
+end
diff --git a/ipl/gprocs/barchart.icn b/ipl/gprocs/barchart.icn
new file mode 100644
index 0000000..5522ebb
--- /dev/null
+++ b/ipl/gprocs/barchart.icn
@@ -0,0 +1,212 @@
+############################################################################
+#
+# File: barchart.icn
+#
+# Subject: Procedures for dynamically growing barchart
+#
+# Author: Gregg M. Townsend
+#
+# Date: August 14, 1996
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# These procedures draw barcharts that can grow dynamically.
+#
+# barchart(win, x, y, dx, dy, sf, n, l, w, b) creates a barchart.
+#
+# setbar(bc, n, v) sets the value of a bar.
+#
+# rebar(bc, sf) redraws a barchart with a new scaling factor.
+#
+############################################################################
+#
+# barchart(win, x, y, dx, dy, sf, n, l, w, b) -- establish a barchart
+#
+# win window
+# x,y position of base of first bar
+# dx,dy distance to base of second bar (either dx or dy should be
+# zero)
+# sf scaling (pixels per unit of value, + or -, need not be
+# integer)
+# n number of bars
+# l,w length (maximum) and width of one bar
+# b logarithmic base, if bars are to be scaled logarithmically
+#
+# barchart() establishes structures for building a barchart. Any of the
+# eight possible orthogonal orientations can be selected depending on the
+# signs of dx, dy, and sf.
+#
+# The absolute value of sf establishes a linear scaling from barchart
+# values to number of pixels. Scaling is handled such that a value of 1
+# makes the first mark on a bar and then each increment of sf lengthens
+# the bar by one pixel. If a bar would exceed the limit then the entire
+# chart is rescaled so that only half the range is then used.
+#
+# setbar(bc, n, v) - set bar n of barchart bc to represent value v
+#
+# It is assumed that v>0 and that bars never shrink; but they may grow.
+#
+# rebar(bc, sf) - redraw barchart with new scaling factor sf.
+#
+# sf is assumed to be of the same sign as the previous scaling factor.
+#
+# Example:
+#
+# Suppose "scores" is a list of scores ranging from 0 to 100.
+# This code fragment dynamically draws a histogram using 21 bins.
+#
+# The call to barchart() specifies:
+# The lower left-hand corner of the barchart is (10, 190).
+# The next bar is 10 pixels to its right, which would be (20, 190).
+# The bars grow upward, to smaller y values, so the scaling factor
+# is negative; each score will grow its bar by 5 pixels.
+# Each bar grows to a maximum length of 180 pixels; the width is 8.
+# No base is given, so scaling is linear.
+#
+# bc := barchart(win, 10, 190, 10, 0, -5, 21, 180, 8)
+# b := list(21, 0) # histogram bins
+# every n := !scores do {
+# i := n / 5 # bin (and bar) number
+# b[i] +:= 1 # increment bin count
+# setbar(bc, i, b[i]) # update display
+# }
+#
+############################################################################
+#
+# Requires: Version 9 graphics
+#
+############################################################################
+
+record BC_rec(win, x, y, dx, dy, sf, n, l, w, b, len, val, round)
+
+procedure barchart(win, x, y, dx, dy, sf, n, l, w, b) #: draw barchart
+ local bc
+ bc := BC_rec(win, x, y, dx, dy, sf, n, l, w, b)
+ bc.len := list(n, 0)
+ bc.val := list(n)
+ if sf > 0 then
+ bc.round := 0.99999
+ else
+ bc.round := -0.99999
+ rebar(bc, sf) # clear area
+ return bc
+end
+
+
+## setbar(bc, n, v) - set bar n of barchart bc to represent value v
+#
+# It is assumed that v>0 and that bars never shrink; but they may grow.
+
+procedure setbar(bc, n, v) #: set bar value on barchart
+ local x, y, o, oldlen, newlen, incr
+
+ v := log(v, \bc.b)
+ oldlen := bc.len[n] | fail
+ newlen := integer(v * bc.sf + bc.round)
+
+ if abs(newlen) > bc.l then {
+ # need to rescale first
+ rebar(bc, 0.5 * bc.sf * real(bc.l) / real(abs(newlen-1)))
+ return setbar(bc, n, v)
+ }
+
+ # lengthen the bar
+ if (incr := newlen - oldlen) ~= 0 then {
+ if bc.dx ~= 0 then {
+
+ # horizontal baseline
+ x := bc.x + (n - 1) * bc.dx
+ y := bc.y + oldlen
+ if incr < 0 then
+ FillRectangle(bc.win, x, y + incr, bc.w, -incr)
+ else
+ FillRectangle(bc.win, x, y, bc.w, incr)
+ }
+
+ else {
+
+ # vertical baseline
+ x := bc.x + oldlen
+ y := bc.y + (n - 1) * bc.dy
+ if incr < 0 then
+ FillRectangle(bc.win, x + incr, y, -incr, bc.w)
+ else
+ FillRectangle(bc.win, x, y, incr, bc.w)
+ }
+ bc.len[n] := newlen
+ bc.val[n] := v
+ }
+ return
+end
+
+
+## rebar(bc, sf) - redraw barchart with new scaling factor sf.
+#
+# sf is assumed to be of the same sign as the previous scaling factor.
+
+procedure rebar(bc, sf) #: redraw barchart
+ local i, l, x, y, dx, dy
+
+ if bc.sf > 0 then
+ l := bc.l
+ else
+ l := -bc.l
+ x := bc.x
+ y := bc.y
+
+ if bc.dx ~= 0 then {
+ dx := bc.n * bc.dx
+ dy := l
+ }
+ else {
+ dx := l
+ dy := bc.n * bc.dy
+ }
+
+ # force all values positive (negative is wrong, but works under OpenWindows!)
+ if dx < 0 then {
+ x +:= dx
+ dx := -dx
+ }
+ if dy < 0 then {
+ y +:= dy
+ dy := -dy
+ }
+ EraseArea(bc.win, x, y, dx, dy)
+
+ bc.len := list(bc.n, 0)
+ bc.sf := sf
+ every i := 1 to *bc.len do
+ setbar(bc, i, \bc.val[i])
+ return
+end
+
+
+# ## test program
+# #
+# # usage: barchart [dx [dy [sf]]]
+# #
+# # background is deliberately different in order to see what gets cleared
+#
+# procedure main(args)
+# local dx, dy, sf, win, n, l, bc, i
+# dx := args[1] | 5
+# dy := args[2] | 0
+# sf := args[3] | -1
+# win := open("bars", "g", "width=500", "height=500")
+# l := list(50, 0)
+# bc := barchart(win, 250, 250, dx, dy, sf, *l, 200, 4)
+# Fg(win, "papayawhip")
+# FillRectangle(win, 0, 0, 500, 500)
+# Fg(win, "black")
+# every 1 to 5000 do {
+# i := ?5 + ?5 + integer(10 * log(1+20*?0)) # nonuniform random bar
+# setbar(bc, i, l[i] +:= 1)
+# flush(win)
+# }
+# while not upto('qQ', reads(win))
+# end
diff --git a/ipl/gprocs/bevel.icn b/ipl/gprocs/bevel.icn
new file mode 100644
index 0000000..fa9c849
--- /dev/null
+++ b/ipl/gprocs/bevel.icn
@@ -0,0 +1,534 @@
+############################################################################
+#
+# File: bevel.icn
+#
+# Subject: Procedures for drawing beveled objects
+#
+# Author: Gregg M. Townsend
+#
+# Date: April 1, 1997
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# These procedures draw objects having a raised or sunken appearance.
+#
+# BevelReset(win) sets/resets shading colors.
+#
+# BevelCircle(win, x, y, r, bw) draws a beveled circle.
+#
+# BevelDiamond(win, x, y, r, bw) draws a beveled diamond.
+#
+# BevelTriangle(win, x, y, r, o, bw) draws a beveled triangle.
+#
+# BevelSquare(win, x, y, r, bw) draws a beveled square.
+#
+# FillSquare(win, x, y, r) fills a square.
+#
+# FillDiamond(win, x, y, r) fills a diamond.
+#
+# FillTriangle(win, x, y, r, o) fills a triangle.
+#
+# RidgeRectangle(win, x, y, w, h, bw) draws a ridged rectangle.
+#
+# GrooveRectangle(win, x, y, w, h, bw) draws a grooved rectangle.
+#
+# BevelRectangle(win, x, y, w, h, bw) draws a beveled rectangle.
+#
+# DrawRidge(win, x1, y1, x2, y2, w) draws a ridged line.
+#
+# DrawGroove(win, x1, y1, x2, y2, w) draws a grooved line.
+#
+############################################################################
+#
+# These procedures allow the drawing of buttons and other objects
+# with a three-dimensional appearance. They are intended to be
+# used like other graphics primitives (DrawRectangle() etc.).
+# However, this abstraction fails if the background color changes
+# or if clipping is set, due to the use of cached graphics contexts.
+#
+# BevelReset(win) -- set/reset colors for beveling
+# This procedure is called automatically by the others.
+# It can be called explicitly if the background color is changed.
+#
+# BevelCircle(win, x, y, r, bw) -- draw beveled circle
+# BevelDiamond(win, x, y, r, bw) -- draw beveled diamond
+# BevelTriangle(win, x, y, r, o, bw) -- draw beveled triangle
+# BevelSquare(win, x, y, r, bw) -- draw beveled square
+# These procedures draw a figure centered at (x,y) and having
+# a "radius" of r. bw is the bevel width, in pixels.
+# o is the triangle orientation: "n", "s", "e", or "w".
+#
+# FillSquare(win, x, y, r) -- fill square centered at (x,y)
+# FillDiamond(win, x, y, r) -- fill diamond centered at (x,y)
+# FillTriangle(win, x, y, r, o) -- fill triangle centered at (x,y)
+# These procedures complement the beveled outline procedures
+# by filling a figure centered at (x,y). Fillcircle is already
+# an Icon function and so is not included here.
+#
+# RidgeRectangle(win, x, y, w, h, bw) -- draw ridged rectangle
+# GrooveRectangle(win, x, y, w, h, bw) -- draw grooved rectangle
+# BevelRectangle(win, x, y, w, h, bw) -- draw beveled rectangle
+# These procedures draw a rectangle with the given external
+# dimensions and border width. Beveled rectangles are raised
+# if bw > 0 or sunken if bw < 0.
+#
+# DrawRidge(win, x1, y1, x2, y2, w) -- draw a ridged line
+# DrawGroove(win, x1, y1, x2, y2, w) -- draw a grooved line
+# These procedures draw a groove or ridge of width 2 at any angle.
+# If w = 0, a groove or ridge is erased to the background color.
+#
+# For BevelSquare() and FillSquare(), the width drawn is 2 * r + 1,
+# not just 2 * r. This is necessary to keep the visual center at the
+# specified (x, y) and is consistent with the other centered procedures
+# and the built-in function FillCircle.
+#
+############################################################################
+#
+# Includes: vdefns
+#
+############################################################################
+#
+# Links: graphics
+#
+############################################################################
+#
+# Requires: Version 9 graphics
+#
+############################################################################
+
+
+$include "vdefns.icn"
+
+link graphics
+
+
+global bev_table
+record bev_record(shadow, hilite)
+
+
+# BevelReset(win) -- set/reset colors for beveling
+#
+# Called automatically the first time a beveling procedure is called;
+# must also be called explicitly if the background color is changed.
+# (Pale, weak background colors work best with beveling.)
+
+procedure BevelReset(win) #: set colors for beveled drawing
+ local b, h, l, s, hilite, shadow, lhilite, lshadow
+
+ /win := &window
+ /bev_table := table()
+
+ if b := \bev_table[win] then {
+ Uncouple(b.hilite)
+ Uncouple(b.shadow)
+ b := &null
+ }
+
+ if WAttrib(win, "depth") >= 4 then {
+
+ HLS(ColorValue(Bg(win))) ? {
+ h := tab(many(&digits))
+ move(1)
+ l := tab(many(&digits))
+ move(1)
+ s := tab(0)
+ }
+
+ case l of {
+ 0 <= l < 10 & l: { lshadow := 25; lhilite := 50 }
+ 10 <= l < 25 & l: { lshadow := 0; lhilite := l + 25 }
+ 25 <= l < 75 & l: { lshadow := l - 25; lhilite := l + 25 }
+ 75 <= l < 90 & l: { lshadow := l - 25; lhilite := 100 }
+ default: { lshadow := 50; lhilite := 75 }
+ }
+ s /:= 2
+
+ shadow := Clone(win, "fg=" || HLSValue(h || ":" || lshadow || ":" || s),
+ "linewidth=1", "linestyle=solid", "fillstyle=solid", "drawop=copy")
+ hilite := Clone(shadow,
+ "fg=" || HLSValue(h || ":" || lhilite || ":" || s))
+ b := bev_record(\shadow, \hilite)
+ }
+
+ if /b then {
+ shadow := Clone(win,
+ "linewidth=1", "linestyle=solid", "fillstyle=solid", "drawop=copy")
+ hilite := Clone(shadow, "fillstyle=textured", "pattern=gray")
+ b := bev_record(shadow, hilite)
+ }
+
+ bev_table[win] := bev_record(shadow, hilite)
+ return win
+end
+
+
+# bev_lookup(win) -- look up and return bev_record for a window.
+#
+# (Internal procedure)
+
+procedure bev_lookup(win)
+ local b, dx, dy
+ b := \(\bev_table)[win] | bev_table[BevelReset(win)]
+ dx := "dx=" || WAttrib(win, "dx")
+ dy := "dy=" || WAttrib(win, "dy")
+ every WAttrib(b.shadow | b.hilite, dx, dy)
+ return b
+end
+
+
+# BevelCircle(win, x, y, r, bw) -- draw beveled circle
+
+procedure BevelCircle(win, x, y, r, bw) #: draw beveled circle
+ local b, upper, lower, a
+ static type
+
+ initial type := proc("type", 0) # protect attractive name
+
+ if type(win) ~== "window" then
+ return BevelCircle((\&window | runerr(140)), win, x, y, r)
+ b := bev_lookup(win)
+
+ /r := 6
+ /bw := 2
+ if bw >= 0 then {
+ upper := b.hilite
+ lower := b.shadow
+ }
+ else {
+ upper := b.shadow
+ lower := b.hilite
+ bw := -bw
+ }
+
+ a := -&pi / 8
+ while (bw -:= 1) >= 0 do {
+ DrawCircle(lower, x, y, r, a, &pi)
+ DrawCircle(upper, x, y, r, a + &pi, &pi)
+ r -:= 1
+ }
+ return win
+end
+
+
+# BevelDiamond(win, x, y, r, bw) -- draw beveled diamond
+
+procedure BevelDiamond(win, x, y, r, bw) #: draw beveled diamond
+ local b, upper, lower
+ static type
+
+ initial type := proc("type", 0) # protect attractive name
+
+ if type(win) ~== "window" then
+ return BevelDiamond((\&window | runerr(140)), win, x, y, r)
+ b := bev_lookup(win)
+
+ /r := 6
+ /bw := 3
+ if bw >= 0 then {
+ upper := b.hilite
+ lower := b.shadow
+ }
+ else {
+ upper := b.shadow
+ lower := b.hilite
+ bw := -bw
+ }
+
+ while (bw -:= 1) >= 0 do {
+ DrawLine(lower, x - r, y, x, y + r, x + r, y)
+ DrawLine(upper, x - r, y, x, y - r, x + r, y)
+ r -:= 1
+ }
+ return win
+end
+
+
+# BevelTriangle(win, x, y, r, o, bw) -- draw beveled triangle
+
+procedure BevelTriangle(win, x, y, r, o, bw)
+ local b, upper, lower
+ static type
+
+ initial type := proc("type", 0) # protect attractive name
+
+ if type(win) ~== "window" then
+ return BevelTriangle((\&window | runerr(140)), win, x, y, r, o)
+ b := bev_lookup(win)
+
+ /r := 6
+ /bw := 2
+ if bw >= 0 then {
+ upper := b.hilite
+ lower := b.shadow
+ }
+ else {
+ upper := b.shadow
+ lower := b.hilite
+ bw := -bw
+ }
+
+ while (bw -:= 1) >= 0 do {
+ case o of {
+ default: { #"n"
+ DrawLine(lower, x - r, y + r, x + r, y + r, x, y - r)
+ DrawLine(upper, x - r, y + r, x, y - r)
+ }
+ "s": {
+ DrawLine(lower, x, y + r, x + r, y - r)
+ DrawLine(upper, x, y + r, x - r, y - r, x + r, y - r)
+ }
+ "e": {
+ DrawLine(lower, x - r, y + r, x + r, y)
+ DrawLine(upper, x - r, y + r, x - r, y - r, x + r, y)
+ }
+ "w": {
+ DrawSegment(lower, x - r, y, x + r, y + r, x + r, y + r, x + r, y-r)
+ DrawLine(upper, x - r, y, x + r, y - r)
+ }
+ }
+ r -:= 1
+ }
+ return win
+end
+
+
+# BevelSquare(win, x, y, r, bw) -- draw beveled square
+
+procedure BevelSquare(win, x, y, r, bw) #: draw beveled square
+ static type
+
+ initial type := proc("type", 0) # protect attractive name
+
+ if type(win) ~== "window" then
+ return BevelSquare((\&window | runerr(140)), win, x, y, r)
+ /r := 6
+ return BevelRectangle(win, x - r, y - r, 2 * r + 1, 2 * r + 1, bw)
+end
+
+
+# RidgeRectangle(win, x, y, w, h, bw) -- draw ridged rectangle
+
+procedure RidgeRectangle(win, x, y, w, h, bw) #: draw ridged rectangle
+ static type
+
+ initial type := proc("type", 0) # protect attractive name
+
+ if type(win) ~== "window" then
+ return RidgeRectangle((\&window | runerr(140)), win, x, y, w, h)
+ /bw := 2
+ return GrooveRectangle(win, x, y, w, h, -bw)
+end
+
+
+# GrooveRectangle(win, x, y, w, h, bw) -- draw grooved rectangle
+
+procedure GrooveRectangle(win, x, y, w, h, bw) #: draw grooved rectangle
+ local abw
+ static type
+
+ initial type := proc("type", 0) # protect attractive name
+
+ if type(win) ~== "window" then
+ return GrooveRectangle((\&window | runerr(140)), win, x, y, w, h)
+
+ /x := -WAttrib(win, "dx")
+ /y := -WAttrib(win, "dy")
+ /w := WAttrib(win, "width") - (x + WAttrib(win, "dx"))
+ /h := WAttrib(win, "height") - (y + WAttrib(win, "dy"))
+
+ if w < 0 then
+ x -:= (w := -w)
+ if h < 0 then
+ y -:= (h := -h)
+
+ /bw := 2
+ if bw >= 0 then
+ bw := (bw + 1) / 2
+ else
+ bw := -((-bw + 1) / 2)
+ abw := abs(bw)
+
+ BevelRectangle(win, x, y, w, h, -bw)
+ BevelRectangle(win, x + abw, y + abw, w - 2 * abw, h - 2 * abw, bw)
+ return win
+end
+
+
+# BevelRectangle(win, x, y, w, h, bw) -- draw beveled rectangle
+#
+# bw is the border width (>0 for raised bevel, <0 for sunken bevel).
+# (x,y,w,h) bounds the entire beveled rectangle, not the usable area inside.
+
+procedure BevelRectangle(win, x, y, w, h, bw) #: draw beveled rectangle
+ local b, upper, lower, xx, yy
+ static type
+
+ initial type := proc("type", 0) # protect attractive name
+
+ if type(win) ~== "window" then
+ return BevelRectangle((\&window | runerr(140)), win, x, y, w, h)
+ b := bev_lookup(win)
+
+ /x := -WAttrib(win, "dx")
+ /y := -WAttrib(win, "dy")
+ /w := WAttrib(win, "width") - (x + WAttrib(win, "dx"))
+ /h := WAttrib(win, "height") - (y + WAttrib(win, "dy"))
+
+ if w < 0 then
+ x -:= (w := -w)
+ if h < 0 then
+ y -:= (h := -h)
+
+ /bw := 2
+ if bw >= 0 then {
+ upper := b.hilite
+ lower := b.shadow
+ }
+ else {
+ upper := b.shadow
+ lower := b.hilite
+ bw := -bw
+ }
+
+ xx := x + w
+ yy := y + h
+ FillRectangle(lower, x, yy, w, -bw, xx, y, -bw, h)
+
+ while (bw -:= 1) >= 0 do {
+ DrawLine(upper, x, yy -:= 1, x, y, xx -:= 1, y)
+ x +:= 1
+ y +:= 1
+ }
+
+ return win
+end
+
+
+# DrawRidge(win, x1, y1, x2, y2, w) -- draw a ridged line
+#
+# If w is negative, a groove is drawn instead.
+
+procedure DrawRidge(win, x1, y1, x2, y2, w) #: draw ridged line
+ static type
+
+ initial type := proc("type", 0) # protect attractive name
+
+ if type(win) ~== "window" then
+ return DrawRidge((\&window | runerr(140)), win, x1, y1, x2, y2)
+ /w := 2
+
+ DrawGroove(win, x1, y1, x2, y2, -w)
+ return win
+end
+
+
+# DrawGroove(win, x1, y1, x2, y2, w) -- draw a grooved line
+#
+# If w > 0, draw groove of width 2.
+# If w = 0, erase groove/ridge of width 2.
+# If w < 0, draw ridge of width 2.
+#
+# Horizontal and vertical grooves fill the same pixels as lines drawn
+# linewidth=2. Angled grooves are not necessarily the same, though.
+
+procedure DrawGroove(win, x1, y1, x2, y2, w) #: draw grooved line
+ local a, n, b, upper, lower, fg
+ static type
+
+ initial type := proc("type", 0) # protect attractive name
+
+ if type(win) ~== "window" then
+ return DrawGroove((\&window | runerr(140)), win, x1, y1, x2, y2)
+
+ /w := 2
+ x1 := integer(x1)
+ y1 := integer(y1)
+ x2 := integer(x2)
+ y2 := integer(y2)
+
+ if w ~= 0 then { # if really drawing
+ b := bev_lookup(win)
+ upper := b.shadow
+ lower := b.hilite
+ }
+ else {
+ fg := Fg(win) # if erasing, draw in bg color
+ Fg(win, Bg(win))
+ upper := lower := win
+ }
+
+ a := atan(y2 - y1, x2 - x1)
+ if a < 0 then
+ a +:= &pi
+ n := integer(8 * a / &pi)
+
+ if w < 0 then # if groove/ridge swap
+ upper :=: lower
+ if n = 2 then # if tricky illumination angle
+ upper :=: lower
+
+ if 2 <= n <= 5 then { # approximately vertical
+ DrawLine(upper, x1 - 1, y1, x2 - 1, y2)
+ DrawLine(lower, x1, y1, x2, y2)
+ }
+ else { # approximately horizontal
+ DrawLine(upper, x1, y1 - 1, x2, y2 - 1)
+ DrawLine(lower, x1, y1, x2, y2)
+ }
+
+ Fg(win, \fg) # restore foreground if changed
+ return win
+end
+
+
+# FillSquare(win, x, y, r) -- fill square centered at (x,y)
+
+procedure FillSquare(win, x, y, r) #: draw filled square
+ static type
+
+ initial type := proc("type", 0) # protect attractive name
+ if type(win) ~== "window" then
+ return FillSquare((\&window | runerr(140)), win, x, y)
+ return FillRectangle(win, x - r, y - r, 2 * r + 1, 2 * r + 1)
+end
+
+
+# FillDiamond(win, x, y, r) -- fill diamond centered at (x,y)
+
+procedure FillDiamond(win, x, y, r) #: draw filled diamond
+ static type
+
+ initial type := proc("type", 0) # protect attractive name
+ if type(win) ~== "window" then
+ return FillDiamond((\&window | runerr(140)), win, x, y)
+ return FillPolygon(win, x - r, y, x, y + r + 1, x + r + 1, y, x, y - r - 1)
+end
+
+
+# FillTriangle(win, x, y, r, o) -- fill triangle centered at (x,y)
+#
+# r is "radius" (1/2 of side of enclosing square)
+# o is orientation ("n", "s", "e", "w")
+
+procedure FillTriangle(win, x, y, r, o) #: draw filled triangle
+ static type
+
+ initial type := proc("type", 0) # protect attractive name
+ if type(win) ~== "window" then
+ return FillTriangle((\&window | runerr(140)), win, x, y, r)
+ return case o of {
+ default: #"n"
+ FillPolygon(win, x - r - 1, y + r + 1, x, y - r, x + r + 1, y + r + 1)
+ "s":
+ FillPolygon(win, x - r, y - r, x, y + r, x + r, y - r)
+ "e":
+ FillPolygon(win, x - r, y - r, x + r, y, x - r, y + r)
+ "w":
+ FillPolygon(win, x + r + 1, y - r - 1, x - r, y, x + r + 1, y + r + 1)
+ }
+end
+
diff --git a/ipl/gprocs/bitplane.icn b/ipl/gprocs/bitplane.icn
new file mode 100644
index 0000000..71e3d52
--- /dev/null
+++ b/ipl/gprocs/bitplane.icn
@@ -0,0 +1,341 @@
+############################################################################
+#
+# File: bitplane.icn
+#
+# Subject: Procedures for bitplane manipulation
+#
+# Author: Gregg M. Townsend
+#
+# Date: May 2, 2001
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# These procedures allow a window to be treated as a series of
+# overlapping, independent layers, subject to some fairly severe
+# restrictions.
+#
+# AlcPlane(W n) allocates planes.
+#
+# FrontPlane(W bp, color) moves a layer to the front.
+#
+# BackPlane(W bp, color) moves a layer to the back.
+#
+# PlaneOp(W bp, op) initializes layer operations.
+#
+# Deplane(W color) restores a window to normal.
+#
+############################################################################
+#
+# These procedures allow drawing and erasing in individual bitplanes of
+# a window. One way to use bitplanes is to think of them as transparent
+# panes in front of a solid background. Each pane can be drawn with a
+# single color, obscuring the panes beyond (and the background). A pane
+# can also be erased, wholly or selectively, exposing what is beyond; and
+# a pane need not be visible to be drawn or erased. Panes can be restacked
+# in a different order, and the color of a pane (or the background) can be
+# changed.
+#
+# For example, the pane in back could be drawn with a city map. The
+# pane in front of that could be used to lay out bus routes, and the paths
+# could be erased and rerouted without having to redraw the map. Using a
+# third plane in front of those, buses could be moved along the routes
+# without having to redraw either the routes or the map behind them.
+#
+# Bitplanes that are allocated together and interact with each other
+# form a bitplane group. A bitplane group need not fill the window;
+# indeed, it can be used in discontiguous portions of a window or even
+# in multiple windows on the same display. On the other hand, multiple
+# bitplane groups can be used different parts of the same window.
+#
+# Bitplanes are implemented using Icon's mutable colors, and they
+# are gluttonous of color map entries. A set of n bitplanes requires
+# at least 2^n color map entries, so the practical limit of n is 5 or 6.
+# On the other hand, sets of 2 or 3 bitplanes are relatively cheap and
+# using several of them is not unreasonable.
+#
+# Each bitplane group is identified by a base index b, which is the
+# index of the mutable color representing the background. The individual
+# bitplanes are referenced as b+1, b+2, b+4 etc. using powers of two.
+# Other indices between b and b+2^n (exclusive) control the colors used
+# used when multiple bitplanes are drawn. The FrontPlane and BackPlane
+# procedures provides simple control of these, and more sophisticated
+# effects (such as making a bitplane partially transparent) are possible
+# by setting them individually.
+#
+#
+#
+# AlcPlane([win,] n) -- alc colors for n bitplanes
+#
+# AlcPlane allocates a set of 2^n mutable colors chosen to be suitable
+# for the bitplane manipulations described below. The colors are
+# consecutively indexed, and the base value b (the most negative index
+# value) is returned. The base color is initialized to the current
+# background color, and the others are initialized to the foreground color.
+#
+# A sequence of AlcPlane calls with different values of n is more
+# likely to succeed if the larger sets are allocated first.
+#
+#
+#
+# FrontPlane([win,] bp, color) -- move indexed plane to "front"
+#
+# FrontPlane sets the pixels in a bitplane to the given color and
+# moves the bitplane in front of the others in the set. The color is
+# optional.
+#
+# bp is the index (base+1, base+2, base+4, or whatever) denoting a
+# particular bitplane. The move-to-the-front effect is accomplished by
+# calling Color() for all colors in the bitplane group whose index
+# after subtracting the base includes the particular bit.
+#
+#
+#
+# BackPlane([win,] bp, color) -- move indexed plane to "back"
+#
+# BackPlane sets the pixels in a bitplane to the given color and
+# moves the bitplane in back of the others in the set. The color is
+# optional.
+#
+# bp is the index (base+1, base+2, base+4, or whatever) denoting a
+# particular bitplane. The move-to-the-back effect is accomplished by
+# calling Color() for all colors in the bitplane group whose index
+# after subtracting the base includes the particular bit.
+#
+# A plane can be effectively rendered invisible by calling
+# BackPlane(win, bp, base); this moves it to the back and sets
+# its color to the color of the background plane.
+#
+#
+#
+# PlaneOp([win,] bp, op) -- set graphics context for plane operation
+#
+# PlaneOp initializes the graphics context for drawing or erasing in
+# a particular bitplane. bp is a bitplane index, as for FrontPlane;
+# multiple bits can be set to draw or erase several bitplanes
+# simultaneously. op is usually one of two strings:
+#
+# "set" to draw the bits in a bitplane
+# "clear" to erase the bits in a bitplane
+#
+# Subsequent drawing operations will affect only the bits in the selected
+# bitplane. Foreground operations are used for both drawing and erasure:
+# use FillRectangle, not EraseArea.
+#
+# After calling PlaneOp with "set" or "clear", be SURE to draw only
+# in portions of the screen previously initialized with pixel values
+# from the same bitplane group. Drawing anywhere else is liable to
+# produce strange, unwanted results. Deplane (below) resets the window
+# for normal operation.
+#
+# The op parameter can also be "copy", in which case the previous
+# contents of the window are immaterial and the drawn pixels are
+# initialized with the bitplanes specified.
+#
+#
+# Deplane([win,] color) -- restore normal drawop and set foreground
+#
+# Deplane is called to restore normal drawing operations after setting
+# or clearing bits in a particular bitplane. The foreground color can be
+# changed optionally.
+#
+#
+#
+# Example:
+#
+# b := AlcPlane(win, 3) # get 3 planes
+# Color(win, b, "white") # background will be white
+# FrontPlane(win, 1, "gray") # city map will be gray
+# FrontPlane(win, 2, "navy") # routes will be dark blue
+# FrontPlane(win, 4, "red") # buses will be red
+# Fg(win, b)
+# DrawRectangle(win, x, y, w, h) # initialize background
+# PlaneOp(win, b+1, "set")
+# drawmap() # draw map
+# repeat {
+# PlaneOp(win, b+2, "clear")
+# DrawRectangle(x, y, w, h) # clear old routes
+# PlaneOp(win, b+2, "set")
+# drawroutes() # draw new routes
+# while routes_ok() do
+# runbuses() # run buses using plane b+4
+# }
+#
+#
+#
+# Caveats
+#
+# AlcPlane must repeatedly ask for new mutable colors until it gets a
+# set that is suitable. Unwanted colors cannot be returned or freed, so
+# some color table entries are usually wasted.
+#
+# No more than 7 bitplanes can be requested, and even that is chancy.
+#
+# These routines will be confused by multiple displays. Multiple
+# windows on a single display, or multiple bitplane sets in a window,
+# are no problem.
+#
+# These routines depend on the internals of Icon, specifically the
+# mapping of window-system pixel values to mutable color indices.
+#
+# The use of unusual "and" and "or" drawops makes the code hard to
+# understand.
+#
+############################################################################
+#
+# Requires: Version 9 graphics
+#
+############################################################################
+
+
+global Plane_Mask
+
+
+# AlcPlane(win, n) -- allocate 2^n colors for bitplanes and return base b
+
+procedure AlcPlane(win, n) #: allocate colors for bitplane
+ local ncolors, mask, b, seqlen, prev, fg, clist
+
+ if type(win) ~== "window" then {
+ n := win
+ win := &window
+ }
+
+ if n < 1 | n > 7 then
+ runerr(205, n)
+ fg := Fg(win)
+
+ ncolors := 2 ^ n
+ mask := ncolors - 1
+
+ # need to get ncolors colors in sequence, with the last one having the
+ # low order n bits (of the actual pixel value) set
+
+ # alternatives on Color are in case current fg/bg would cause failure
+
+ b := NewColor(win, fg | "black") | fail
+ clist := [b]
+ seqlen := 1
+ while seqlen < ncolors | iand(-1 - b, mask) ~= mask do {
+ prev := b
+ b := NewColor(win, fg | "black") | fail
+ push(clist, b)
+ if prev - b ~= 1 then
+ seqlen := 1
+ else
+ seqlen +:= 1
+ }
+
+ # discard unwanted colors
+ every 1 to ncolors do
+ pop(clist)
+ if *clist > 0 then {
+ push(clist, win)
+ FreeColor ! clist
+ }
+
+ # set base color to background and return result
+ Color(win, b, Bg(win) | "white")
+ /Plane_Mask := table()
+ every Plane_Mask [b to b + mask] := mask
+ return b
+end
+
+
+
+# FrontPlane(win, bp, color) -- move indexed plane to "front", set color
+
+procedure FrontPlane(win, bp, color) #: move bitplane to front
+ local mask, base, bits, i
+
+ if type(win) ~== "window" then {
+ win :=: bp :=: color
+ win := &window
+ }
+
+ mask := \Plane_Mask[bp] | runerr(205, bp)
+ base := iand(icom(mask), bp)
+ bits := bp - base
+ /color := bp
+ every i := base to base + mask do
+ if iand(i, bits) = bits then
+ Color(win, i, color)
+ return win
+end
+
+
+
+# BackPlane(win, bp, color) -- move indexed plane to "back", set color
+
+procedure BackPlane(win, bp, color) #: move bitplane to back
+ local mask, base, bits, i
+
+ if type(win) ~== "window" then {
+ win :=: bp :=: color
+ win := &window
+ }
+
+ mask := \Plane_Mask[bp] | runerr(205, bp)
+ base := iand(icom(mask), bp)
+ bits := bp - base
+ Color(win, bp, \color) # set color if specified
+ every i := base to base + mask do
+ if iand(i, bits) = bits & i ~= bp then
+ Color(win, i, ixor(i, bits)) # set color as if plane unset
+ return win
+end
+
+
+
+# PlaneOp(win, bp, op) -- set graphics context for plane operation
+
+procedure PlaneOp(win, bp, op) #: set context for bitplane operation
+ local mask, base, bits, i
+
+ if type(win) ~== "window" then {
+ win :=: bp :=: op
+ win := &window
+ }
+
+ mask := \Plane_Mask[bp] | runerr(205, bp)
+ base := iand(icom(mask), bp)
+ bits := bp - base
+
+ case op of {
+ "copy": {
+ WAttrib(win, "drawop=copy")
+ Fg(win, bp)
+ }
+ "set": {
+ i := base + bits
+ WAttrib(win, "drawop=and")
+ Fg(win, i)
+ }
+ "clear": {
+ i := base + (mask - bits)
+ WAttrib(win, "drawop=or")
+ Fg(win, i)
+ }
+ default:
+ runerr(205, op)
+ }
+ return win
+end
+
+
+
+# Deplane(win, color) -- restore normal drawop and set fg to color
+
+procedure Deplane(win, color)
+
+ if type(win) ~== "window" then {
+ color := win
+ win := &window
+ }
+ WAttrib(win, "drawop=copy")
+ Fg(win, \color)
+ return win
+end
diff --git a/ipl/gprocs/button.icn b/ipl/gprocs/button.icn
new file mode 100644
index 0000000..6b9b176
--- /dev/null
+++ b/ipl/gprocs/button.icn
@@ -0,0 +1,183 @@
+############################################################################
+#
+# File: button.icn
+#
+# Subject: Procedures for pushbutton sensors
+#
+# Author: Gregg M. Townsend
+#
+# Date: August 14, 1996
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# These procedures implement pushbuttons using the "evmux" event
+# multiplexor instead of the usual vidget library.
+#
+# button(win, label, proc, arg, x, y, w, h)
+# establishes a pushbutton.
+#
+# buttonrow(win,x,y,w,h,dx,dy, label,proc,arg, label,proc,arg, ...)
+# establishes a row of buttons.
+#
+# buttonlabel(handle, label) changes a button label.
+#
+############################################################################
+#
+# It is assumed that buttons do not overlap, and that fg, bg, and font
+# do not change beyond the initial call. These restrictions can be
+# accommodated if necessary by using a window clone.
+#
+# button(win, label, proc, arg, x, y, w, h)
+#
+# establishes a button of size (w,h) at (x,y) and returns a handle.
+# "label" is displayed as the text of the button.
+# When the button is pushed, proc(win, arg) is called.
+#
+# If proc is null, the label is drawn with no surrounding box, and
+# the button is not sensitive to mouse events. This can be used to
+# insert a label in a row of buttons.
+#
+# buttonlabel(handle, label)
+#
+# changes the label on a button.
+#
+# buttonrow(win,x,y,w,h,dx,dy, label,proc,arg, label,proc,arg, ...)
+#
+# establishes a row (or column) of buttons and returns a list of handles.
+# Every button has size (w,h) and is offset from its predecessor by
+# (dx,dy).
+#
+# (x,y) give the "anchor point" for the button row, which is a corner
+# of the first button. x specifies the left edge of that button unless
+# dx is negative, in which case it specifies the right edge. Similarly,
+# y is the top edge, or the bottom if dy is negative.
+#
+# One button is created for each argument triple of label,proc,arg.
+# An extra null argument is accepted to allow regularity in coding as
+# shown in the example below.
+#
+# If all three items of the triple are null, a half-button-sized
+# gap is inserted instead of a button.
+#
+# Example:
+#
+# Draw a pushbutton at (x,y) of size (w,h);
+# then change its label from "Slow" to "Reluctant"
+# When the button is pushed, call setspeed (win, -3).
+#
+# b := button (win, "Slow", setspeed, -3, x, y, w, h)
+# buttonlabel (b, "Reluctant")
+#
+# Make a set of buttons extending to the left from (490,10)
+#
+# blist := buttonrow(win, 490, 10, 50, 20, -60, 0,
+# "fast", setspeed, +3,
+# "med", setspeed, 0,
+# "slow", setspeed, -3,
+# )
+#
+############################################################################
+#
+# Links: evmux, graphics
+#
+############################################################################
+#
+# Requires: Version 9 graphics
+#
+############################################################################
+#
+# See also: evmux.icn
+#
+############################################################################
+
+
+link evmux
+link graphics
+
+$define BORDER 2 # border width
+
+record Button_Rec(win, label, proc, arg, x, y, w, h)
+
+procedure button(win, label, proc, arg, x, y, w, h)
+ local r
+
+ r := Button_Rec(win, label, proc, arg, x, y, w, h)
+ buttonlabel(r, label)
+ if \proc then {
+ BevelRectangle(win, x, y, w, h, BORDER)
+ sensor(win, &lpress, Exec_Button, r, x, y, w, h)
+ }
+ return r
+end
+
+procedure buttonrow(win, x, y, w, h, dx, dy, args[])
+ local hlist, label, proc, arg
+
+ if dx < 0 then x -:= w
+ if dy < 0 then y -:= h
+ hlist := []
+ repeat {
+ label := get(args) | break
+ proc := get(args) | break
+ arg := get(args) | break
+ if label === proc === arg === &null then {
+ x +:= dx / 2
+ y +:= dy / 2
+ }
+ else {
+ put(hlist, button(win, label, proc, arg, x, y, w, h))
+ x +:= dx
+ y +:= dy
+ }
+ }
+ return hlist
+end
+
+procedure buttonlabel(r, s)
+ r.label := s
+ if /r.proc then
+ EraseArea(r.win, r.x, r.y, r.w, r.h) # borderless button
+ else
+ EraseArea(r.win, r.x+BORDER, r.y+BORDER, r.w-2*BORDER, r.h-2*BORDER)
+ CenterString(r.win, r.x + r.w/2, r.y + r.h/2, r.label)
+ return
+end
+
+procedure Exec_Button(win, r, x, y)
+ local e, b, t
+
+ WAttrib(win, "drawop=reverse")
+ FillRectangle(win, r.x + BORDER, r.y + BORDER, r.w - 2*BORDER, r.h -2*BORDER)
+ BevelRectangle(win, r.x, r.y, r.w, r.h, b := -BORDER)
+
+ while e := Event(win) do {
+ x := &x
+ y := &y
+ case e of {
+ &ldrag: { # drag
+ t := (if ontarget(r, x, y) then -BORDER else BORDER)
+ if b ~===:= t then {
+ BevelRectangle(win, r.x, r.y, r.w, r.h, b)
+ FillRectangle(win,
+ r.x + BORDER, r.y + BORDER, r.w - 2*BORDER, r.h - 2*BORDER)
+ }
+ }
+ &lrelease: { # release leftbutton
+ if b < 0 then {
+ BevelRectangle(win, r.x, r.y, r.w, r.h, BORDER)
+ FillRectangle(win,
+ r.x + BORDER, r.y + BORDER, r.w - 2*BORDER, r.h - 2*BORDER)
+ WAttrib(win, "drawop=copy")
+ r.proc(win, r.arg)
+ }
+ else
+ WAttrib(win, "drawop=copy")
+ return
+ }
+ }
+ }
+end
diff --git a/ipl/gprocs/cardbits.icn b/ipl/gprocs/cardbits.icn
new file mode 100644
index 0000000..4c961fb
--- /dev/null
+++ b/ipl/gprocs/cardbits.icn
@@ -0,0 +1,602 @@
+############################################################################
+#
+# File: cardbits.icn
+#
+# Subject: Procedure for constructing playing card images
+#
+# Author: Gregg M. Townsend
+#
+# Date: August 14, 1996
+#
+############################################################################
+#
+# cardbits() returns an image for use in drawing playing cards.
+#
+############################################################################
+#
+# cardbits() returns a bilevel image used by the drawcard() library
+# procedure (q.v.). The image contains many small subimages for use in
+# constructing playing cards. The images were collected from the
+# individual X bitmaps of the highly recommended "Spider" solitaire game
+# that is included as a sample program with the XView toolkit for
+# X-windows.
+#
+# Overall structure: 160w x 432h bilevel bitmap.
+# Red area: union of two rectangles (0,0,160,188) (0,404,117,28)
+# Black area: union of two rectangles (0,188,160,216) (117,404,43,28)
+#
+# Pips: 16x20 heart, diamond, club, spade at (144, {0,94,188,282})
+# rotated versions at (144, {20,114,208,302})
+# Small pips: 9x14 H, D, C, S at (148, {40,134,228,322})
+# rotated versions at (148, {54,148,242,336})
+# Large spade, for the Ace: 43x56 at (117,376)
+# Ranks: 9x14 A,2,3,4,5,6,7,8,9,J,Q,K at ({0,12,24,...,144}, 376)
+# rotated versions at ({0,12,24,...,144}, 390)
+# both rows duplicated at ({0,...144}, {404,418})
+# Faces: 48x94 images including 1-pixel-wide frame.
+# Three columns (J,Q,K) of four rows (H,D,C,S)
+# at ({0,48,96},{0,94,188,282}).
+#
+############################################################################
+#
+# Requires: Version 9 graphics
+#
+############################################################################
+#
+# See also: drawcard.icn
+#
+############################################################################
+
+# Original copyrights are as follows; permissions appear at end of file.
+#
+# (c) Copyright 1989, Donald R. Woods and Sun Microsystems, Inc.
+# (c) Copyright 1990, David Lemke and Network Computing Devices Inc.
+# Copyright 1990 Heather Rose and Sun Microsystems, Inc.
+
+procedure cardbits()
+return \
+"160,#_
+0000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF_
+00008080000008018000200554018000CA64CA61_
+38388076DB6DB0018E0E20CEAC018000656AD4C1_
+7C7C8055555560019F1F11CD560187073264C981_
+FEFE83228A28C001BFBF91DAAE018F8F996AD303_
+FEFE84A104108001BFBF901516019FDFCC60C617_
+FFFE82F104113839BFFF933A0E019FDFCFFFFC37_
+FFFEBE9FFFFE7C7DBFFF973E75019FFFCFFFF857_
+7FFCEACAAAAAFEFF9FFF277045019FFFC8055097_
+7FFCAA2FFFFCFEFF9FFF206E7D018FFF85E55137_
+3FF8AACAA004FFFF8FFE2CCC75018FFF84C5513F_
+1FF0AA8AAF1AFFFF87FC5D9E7E8187FF04A5516B_
+0FE0D6FAA0A67FFD83F85D80468183FE09C551AB_
+0FE082554F3E7FFD83F88300868181FC0802A96B_
+07C0C5954C3A3FF981F137028E8181FC1032A93F_
+0380FE354E3E1FF180E1FF818F4180F81B12A897_
+0380822A80A20FE180E00D810F41807004E15657_
+0100FF2A80620FE180403FC40F41897004015937_
+01008155404207C188407AC397418F2007815697_
+0000FFD540030381B600FFE313A186240E075A87_
+0000BCD521B2C381AA03F5613BA18D09081AA707_
+01009E54D0432101DD0FFFF02FA18BF61475FEC7_
+0100CF2A50E29101AA3FCD5857A186883FFFFFF7_
+0380A79FA804F001B61FCEAFADD18184C000073F_
+0380B3C9AAAB5801C80FFB555DD1824191448F39_
+07C0E9EA75554601FE0FF1EAF2D18EA67FFF1EE7_
+0FE0ACFF6A0B2FC1B735FB3F91519A9C00803FDF_
+0FE09A66F8E65679A0F0B6248E29977E7FFE73B7_
+1FF0CBF56FBCF5AFABD4B1E47C998AF32244F3D9_
+3FF8E71DE3F3D995F667381FE64987739189EEED_
+7FFCFC6D580D5A4BD27A1803E22586DDCA53FC77_
+7FFCF187D7F5F4A5AA5D5B67F2259F7FE997393B_
+FFFEC6475D5D59B3D267FEAFD333FAD675AF3C9D_
+FFFEB8A6F5D5B249E23BAD4F5129EB6E742EEE4F_
+FEFECD157D5F5597F325EC9D3921EAD7BA5FF727_
+FEFEC4A7B7F6ED3BF93ACE9EE955DB7BFA59AF93_
+7C7CD6553084B275BD2ACE3BD959B2DD9DB9D5C9_
+3838D2AD55552CF9DF357639B913EB6B9DB7FAE5_
+0000DB96F2A7AFF3AF2D677BB99DDAD7EE7F1F73_
+0000D99751445975D7356B776895B77FFE6626F9_
+0000DDE6508F3AB1AB2ABF677C85EEDC6667F35D_
+0C60C496B9501D399536B7F9648FDDB2666E09AF_
+1EF0F2C5D89F1AB58B2AB5E76C95BB67F67FFDF7_
+1FF0BC4299501C73A53D599D74A5F6CFFF7FFAED_
+1FF08F62A81F1AB9932F5E77A489ADD912244ADB_
+0FE0E3F278902EF5913379DCBCC9D7A894948AED_
+0FE0B8F3F9CFE78FBD1DE773DC5FADAB794F6ADB_
+07C0F1E7F39FCF1DFA3BCEE7B8BDDB56F29ED5B5_
+0380AF74091E4FC7933D3B9ECC89B751292915EB_
+03809D58F81546F19125EE7AF4C9DB5224489BB5_
+0100CE380A99423DA52EB99ABCA5B75FFEFFF36F_
+0100AD58F91BA34FA936E7AD54D1EFBFFE6FE6DD_
+00009CB80A9D6923F1269FED6CA9F59076664DBB_
+00008D5CF10A67BBA13EE6FD54D5BACFE6663B77_
+0000AE9A228AE99BA916EED6ACEB9F64667FFEED_
+0000CFF5E54F69DBB99DDEE6B4F5CEF8FE77EB5B_
+01009F34AAAAB54BC89D9C6EACFBA75FEDB9D6D7_
+0100AE4D210CAA6B9A9BDC7354BD93AB9DB9BB4D_
+0380DCB76FEDE523AA9779735C9FC9F59A5FDEDB_
+0380E9AAFABEA8B3849CB937A4CFE4EFFA5DEB57_
+07C0924DABAF651D948AF2B5DC47F277742E76D7_
+0FE0CD9ABABAE263CCCBF57FE64BB93CF5AE6B5F_
+0FE0A52FAFEBE18FA44FE6DABA55DC9CE997FEF9_
+1FF0D25AB01AB63FA447C0185E4BEE3FCA53BB61_
+1FF0A99BCFC7B8E79267F81CE66FB7779189CEE1_
+1EF0F5AF3DF6AFD3993E278D2BD59BCF2244CF51_
+0C609E6A671F66599471246D0F05EDCE7FFE7EE9_
+000083F4D056FF358A89FCDFACEDFBFC01003959_
+00008062AAAE57978B4F578FF07FE778FFFE6571_
+0000801AD55593CD8BBAAADFF0139CF122898241_
+0000800F2015F9E58BB5F573F86DFCE000032181_
+00008089470A54F385EA1AB3FC55EFFFFFFC1161_
+00008084C20B2A7985F40FFFF0BBE37FAE286FD1_
+000081C34D84AB3D85DC86AFC055E0E5581090B1_
+000081C0C002ABFF85C8C7FF006DE15AE0702461_
+000083E04202AA8182E9C35E0211E96A81E004F1_
+000087F0460154FF82F023FC0201EC9A80200E91_
+000087F04501544182F081B00701EA6A87200E01_
+00008FF87C72AC7F82F181FF8701E91548D81F01_
+00009FFC5C32A9A3817140EC8F81FC954C083F81_
+0000BFFE7CF2AA41816100C11FC1D69540103F81_
+0000BFFE65055F6B816201BA1FC1D58AA3907FC1_
+0000FFFF58F55155817E79BA3FE1D68AA520FFE1_
+0000FFFF2005535580AE33347FF1FC8AA321FFF1_
+0000FF7F3FFFF45580BE7604FFF9EC8AA7A1FFF1_
+0000FF7F5555535780A20EE4FFF9E90AA013FFF9_
+0000BE3E7FFFF97D80AE7CE9FFFDEA1FFFF3FFF9_
+00009C1C88208F4180705CC9FFFDEC3FFFF3FBF9_
+00008001082085218068A809FDFDE8630633FBF9_
+00008003145144C180755B89FDFDC0CB5699F1F1_
+00008006AAAAAA01806AB388F8F98193264CE0E1_
+0000800DB6DB6E01803573047071832B56A60001_
+0000801000000101802AA0040001865326530001_
+0000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF_
+0100FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF_
+010081A222222C0180000149F105800E76EDCE01_
+038080D55555588180800194B7C9808736ED9C01_
+038088E2222230818080032279098083860C3801_
+07C084488888E1C181C00541588981C1FFFFF001_
+0FE086755555C1C181C00980BFE981C0FFFFF003_
+0FE0C528888983E183E00B77EC8983E0C002B007_
+1FF0E5321FFF07F187F017885E4987F0C042A807_
+3FF8A495FFFE07F187F027EF97F987F0FC3EA805_
+7FFCEA93FC020FF98FF84F730F498FF8C282A807_
+3FF8F65FF0F21FFD9FFC5FD71B259FFCFA7EAA05_
+1FF09DDFD10A3FFFBFFE9F1017FDBFFEBA3AAB07_
+0FE09DDF507A1FFD9FFCBF201DA59FFCA222A985_
+0FE0F65550B20FF98FF97DAC17C38FF8A202A947_
+07C0EAB5507907F187F27A900EFF87F0A402A8A5_
+0380A4AAA00107F187F2FD8009C187F0A702A89F_
+0380E52AA02283E183E2FA9F1B4183E0A002A847_
+0100E555404D81C181C5F5CC38E181C0A022AE23_
+0100A6555C3101C189C5EAC062A1C1C3D7C2A911_
+0000E455660200819C89F5E0C8F1E0849183AF0D_
+0000E8555B0E0081AA8BEB21A3D1F08528066107_
+0100A0D55506000DF70BDC9E9FF9B00C441C1E85_
+0100E0AAA54140D7AA13FFA2FFA99037C7F064C5_
+0380FFFFFAAAA0AB9817DFFFFDDDD0D338039967_
+0380C8C63F555D57AC17ABFFEAF5F3DCC7FC66F7_
+07C0E5AD6AAABF6BAA1FF77F77FFDDDE38038DCF_
+0FE0F318C7FF7AD5D53BFAAAAFEB9DBF87FC3BAD_
+0FE0FFFFFFFE34B9D2FD7FDDFF5FBBBEF803D717_
+1FF0FF0003DE259DA35FAFFFFAFDF776EFFEEEB5_
+3FF8C1FFFFAC4DB3DEAED5FFD5DFBE76BBBBDC57_
+7FFCDBC631DCCF33B597FAAAAFCF9CF6EEEEBAB5_
+3FF8D7EB5BFDD8E7E2D7FFD5F26FD9F6BBBB6917_
+1FF0CEB98D7DDE37D5573E7FF2A7F3D6EEEEDAB5_
+0FE0DF75FD9D978D88B7064FF237D7F6BBBDEC4F_
+0FE0FAAAACB92DE5D56E6E4F2FF39F16EEEBDAAD_
+07C0FDCB56D828F3A23E6CDC315BBFF6BBB6E91D_
+0380EACFEB992D59D55F8C9B5549F816EEEEDAAF_
+0380F7A7357B2E2D88AAFE9B519DBFF6BBDBEC55_
+0100AB252AAFAD57D54EABF84EBD9016EEBBDABD_
+0100DE74355B5E8BA275FAAFB92FDFF6BB6CE917_
+0000AAAAAAAA5F55D58C0FEAC55FF816EED77AAF_
+0100F4AADFF85F2388AAF01FD56FD7F6BDAB7C77_
+0100A9CBFAAA5BD5D58A9FE0C55F9C56EB5D9AAD_
+0380D29ADFFB59E9A371403EB96FB7F6B6A2ED35_
+0380B29A536FDB95D49D5F829D5FFBB6ED59EEEF_
+07C0E73D1C9B5F2BCEA35AFAA36BB75EDADB73B5_
+0FE0F23D5FFD5E27D66AADDAAB359A5EB146BD2D_
+07C0E47ABFFABC4FACD55BB5566BB4BD628D7A59_
+0380D4FAD938BCE7D6C55F5AC573ADCEDB5B7AED_
+0380A9DBF6CA594DFAB941FAB92BF7779AB76DDF_
+0100979ADFFB594BF69D7C028EC5ACB7456D6FED_
+0100ABDA555FD395FAA307F951ABB559BAD76A39_
+0000C4FA1FFB552FF6ABF80F5511EE3ED5BD6FEB_
+0000AAFA55555555FAA357F031ABF55EEB77681F_
+0000D17ADAAC2E7BF49DF55FAE45E89736DD6FFB_
+0000EAB5F554A4D5BD721FD572ABBD5BDD776809_
+0100B474DEACE5EFB98AD97F5511AA37DBDD6FFD_
+01009AB499D7F35792AAD931FAABF55B7777681F_
+0380CF141B6AD3BFDA8C3B367C45B8976DDD6FFD_
+0380A7B49D35555FCFF4F27676ABB55BD77768F9_
+07C0B1E9B9BFAEFBEC4FF260ED11F237BDDD6FEB_
+0FE0EC7BBEB19D73E54FFE7CEAABAD5B77776BCF_
+07C0E71BBFDAD7EBF64FABFFEB47E896DDDD6F9B_
+0380CCF33B8C63DBF3F5555FE9ADAD5D77776F39_
+0380CDB235FFFF83FBABFFAB757BEA3BDDDD6E7D_
+0100B9A47BC000FFBF5FFFF5FAC5AD777FF76EEF_
+01009D2C7FFFFFFFFAFFBBFEBF4BE8EBC01F7DDD_
+0000AB5EFFE318CFD7F5555FDCABB5DC3FE1FDB9_
+0000D6FD5556B5A7FFEEFEEFF855F3B1C01C7BBB_
+0000EABAAAFC6313AF57FFD5E835EF663FE33BCF_
+0000D505555FFFFFBBBFFFFBE819E699C01CCB0B_
+0000EB0282A5550795FF45FFC855A3260FE3EC09_
+0000B00060AAAB059FF9793BD0EFA1783822300D_
+0000810070DAAA178BC584D7D155E0866014A10F_
+000081004066AA278F1307AF9139B0F5C1892107_
+000083808C3AAA6585460357A391889543EBC383_
+00008381B202AAA7871C33AFA381C47544050381_
+000087C1440554A782D8F95F47C1E215400507C1_
+00008FE080055525839001BF4FE1F91540E50FE1_
+00008FE09E0AAD57FF70095E4FE1A51540250FE1_
+00009FF04D0AAA6FC3E835BE9FF1E29540451FF1_
+0000BFF85E0AFBB9A5B804FD3FF9A19544453FF9_
+0000FFFC508BFBB9BFE808F97FFDE0D55C5D7FFD_
+0000BFF84F0FFA6FA4D8EBFA3FF9A0557E5F3FF9_
+00009FF0403FC95792F0CEF21FF1E01541431FF1_
+00008FE07FFFA9259FE9F7E40FE1A0157C3F0FE1_
+00008FE0FFF84CA7927A11E80FE1E01542030FE1_
+000087C1911114A39137EED007C1E00D400307C1_
+00008383AAAAAE6197FD01900381C00FFFFF0381_
+0000838711111221911A82A00381800FFFFF8381_
+0000810C44444711909E44C00101801C3061C101_
+0000811AAAAAAB0193ED298001018039B76CE101_
+0000803444444581A08F928000018073B76E7001_
+0000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF_
+0000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF_
+000086820820B00180000292ECC180036B6AAB61_
+038083820820E38180E00329EFE180E1B556D6C1_
+07C081EDB6DBC7C181F00244AFE181F0DB6AAD81_
+0FE080C208218FE183F80292A6C383F86D56DB81_
+0FE0807FFFFF0FE183F80329F60383F8377EF683_
+0FE0803555560FE183F803CFB66783F818000E47_
+07C0801AAAAC07C181F00220D7F581F00FFFFD4D_
+3BB8800FFFFC3BB98EEE03EF57F58EEE0FFFFD9B_
+7FFC800FFFFC7FFD9FFF02E673659FFF0D4004B7_
+FFFE880AA004FFFFBFFF87AE5B05BFFF95400461_
+FFFE9C0AAE3CFFFFBFFF8A405BB5BFFF955F1CFF_
+FFFEBE0AA674FFFFBFFF92684BFFBFFF9540A405_
+7D7CBA0ABE7C7FFD9FFF273079FB9FFF154F1E05_
+3938AA0AA02439398E4E4F202DF38E4E15464C05_
+0380BA0AA024010180409D202DCF8040154E5C05_
+0380AA0AA1E4038180E1390424D980E015402405_
+07C0BA0AA044038180E273BC7EF180E07540E405_
+0000AA3AA1B407C181F267B0F6E181F05D404405_
+0000BA2EA04700018004EFC1F271800057420405_
+0000AA2BB8E480018004DC435B7180005541E805_
+0000BA3AA70680018004F17F0F3980003560C80F_
+07C0AA1EACE0E0018004E45429B980007C701813_
+0380BA3221F1F8018104DD00FC9D8001FFDC3727_
+0380AA6BFF9FFE018283C3B784CD8007DE07E9E5_
+3938BAE2223FFF8186C3627C8FEF801ED79F1CE7_
+7D7CABF888BFFFE18926376DDA27C07AE9FA19F5_
+FFFEBBBE227FDFF9B6CF1DD77333E1EAC462DCEF_
+FFFEABCF88FEBFFFAA9BF8BA3FBBB77ED60619D5_
+FFFEBA63E3F5F7FFA531AFD7FBE99EF6C39E1CBD_
+7FFCAB31FFEB367F95F9F4EF556D957EEBFED57D_
+3BB8BA783EF6363FBA19AC39EEF792EAC1FA1AED_
+07C0EB1C00EAF7BFC5F9DC11555F996AD5E21DD5_
+0FE0FBCE38D5E3DFB3F1ACE2BBBB91FEC046CFBD_
+0FE0EB9E38EF9CFFCF63F8E3555590F6FADD0F6D_
+0FE0FBC6D6D77F7FAEB76B5AEEEF967ED03B0AF5_
+07C0EBF3EEAEEBBFD95E3BBB5555907ACF556DBD_
+0380FBE3D6D5DDDFB7AF2B5BBBBB9826D41B07B5_
+0000EBF710ABAAFFE85B3843555595A1D3AD85BD_
+0000FBF338D7777BD3B018E2EEEF9414650AB59D_
+0000EBF987EB2A7DE470180557F59A1194ED82DD_
+0380FBF98C9DC9FDA9E81887B81F9A6C094AC2DD_
+07C08FFB9086FFF9EBD999C5C0039D0B353D5ACD_
+07C0F8F9B6B667FDABA99AA703019D06824AC16D_
+03808EFDA632D7FFD7501775031B9EB56DD5616D_
+0D60FEF9A082CFCFACE02A2A301F9E86B2BAAD6D_
+1FF08DFBBDDEEFD9D8063C9E300D9E835B6D60BD_
+1FF09BF77BBDDFB1B00C793C601BBD06B6DAC179_
+1FF0F3F341059F7FF80C54540735B6B55D4D6179_
+0D60FFEB4C65BF71D8C0AEE80AEBB686ABB6AD79_
+0000BFE66D6D9F1F80C0E55995D5B683524160B9_
+03809FFF6109DFF1C003A3999BD7B35ABCACD0B9_
+0000BF93B9319FDFF81DE1181795BB4352903659_
+0000BE54D7E19FD7AFEAA0180E27BB41B7298859_
+0000DEEEEB1CCFDFF77747180DCBB9AD50A62829_
+0000FF55D508EFD7AAAAC21CDA17BDA1B5CB85A9_
+0380FBBBAB6BC7DFDDDDDAD4F5EDADE0D82B6419_
+0000FDD77577CFD7AAAADDDC7A9BBDB6AAF35E09_
+0D60FEFEEB6B63DFF7775AD6ED75AF50DC0B7E69_
+1FF0FF39F71C79D7AAAAC71FC6F3B6F0BB5F6F09_
+1FF0FBC7AB1C73DFDDDD47358FCDBDF362037F89_
+1FF0FDEF570038D7FAAA883B9FA3ABB847AB5699_
+0D60FC6C6F7C1E5DEF779C35985DB7585F835749_
+0380FE6CD7FF8CD5B6AAF72F9FA9BEAB7FD77EA9_
+07C0FFEFAFC7C65D97DFEBF58CA5BD3879C36F79_
+07C0FFFD7F11F3D5DDFC5D1FD955AB98606B7EED_
+03809FFBFE447DDDCCCEEBB8F36DF73B46235787_
+000087FFFD111FD5E45BB6EC6491AF985F975E03_
+000081FFFC44475DF7F13E46C361E738F9EB7801_
+0000807FF9FFD655B321EDC3C141A797E07BE001_
+0000801F8F844C5DB93F00BB2081E4EC3BFF8001_
+00008007073578559D942A272001C8180E3E0001_
+0000800160E55C5D9CF0FE8F2001F01306AC0001_
+00008001271DD4558EDAC23B2001A01782AA0001_
+00008000E205745D8E4F83F72001A02042EA0001_
+000083E02D855C55876F0DE64F81A02202BA0F81_
+000081C02205505D8F7E3DCE4701A02702AE0701_
+000081C0278550559B24209C8701A02402A80701_
+000080802405505DF3B404B90201A03A72A80201_
+00009C9C24055055CFB404F27271A03262A87271_
+0000BFFE3E7D505DDF9E0CE4FFF9A078F2A8FFF9_
+0000FFFF2E65507DFFD21649FFFDA02502A9FFFD_
+0000FFFF3C755039ADDA0251FFFDFF38FAA9FFFD_
+0000FFFF20055011A0DA75E1FFFD862002A9FFFD_
+0000BFFE3FFFF001A6CE6740FFF9ED2002B0FFF9_
+00009DDC3FFFF001AFEAF7C07771D9BFFFF07771_
+000083E035555801AFEB04400F81B2BFFFF00F81_
+000087F06AAAAC01E66DF3C01FC1E27000181FC1_
+000087F0FFFFFE01C06F94C01FC1C16F7EEC1FC1_
+000087F184104301C36549401FC181DB6AB61FC1_
+000083E3DB6DB78187F522400F8181B556DB0F81_
+000081C7041041C187F794C00701836B6AAD8701_
+0000800D0410416183374940000186D556D6C001_
+0000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF_
+0100FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF_
+010082410410A001800010B496018040EAEEBAB9_
+038081638E39C201800008194E0180E075D77571_
+038080F7DF7D820180000BB2260180E03AEEBAE5_
+07C08067DF7D070180200B34150581F01DD775CB_
+0FE080210412070180200AB80D0581F00FFFFF8F_
+0FE0803FFFFC0F818070087C750B83F80FFFFF0F_
+1FF0803FFFFC0F8180700F62450B87FC0AA8010B_
+3FF8A02001541FC180F80E7E7D0B8FFE0AA8F10B_
+7FFCF03E79543FE180F80DCC5D119FFF0AAB090F_
+7FFCD82105547FF181FC10DC7A8B9FFF0AA8790F_
+FFFE8C2FB954FFF983FE1F804A8FBFFF8AA8B10B_
+FFFE84269D54FFF987FF1DC18A85BFFF8AA8790F_
+FFFEA42FB955FFFD8FFF9FC08A85BFFF8AA8008B_
+FFFEA4210155FFFD9FFFC3A00A8DBFFF8AA80395_
+7D7C94210155FFFD9FFFC1639A879F5F0AA8990B_
+393894218155FFFD9FFFC1D19D4D8E4E0AA9210F_
+03809436B554FAF99FFFC3302D4780E00AA8DF0B_
+07C0A43C1D5472719FFFC2A82D4D81F00AA80E0F_
+0000A423615407018FAF8444554780000EE8020F_
+0000A42081540F8187270AABAF4D80003BBC020B_
+07C0A4E1C15600018070111113E780002EEB060B_
+038095600355000180F82EAAABED80003BBFFC0F_
+3938955AAD5500018000264446678001F800039F_
+7D7C94A5D3AB8001800041AAAE7D800706DB6DB1_
+FFFEB47FFFFFE001800094911C1780197400027F_
+FFFEB5C40000BC0184018C6AA8BD80ED77FFFFD9_
+FFFEB547FFFFB3018E028A244B37816727BBB4CF_
+FFFEE6AF555507C19502801EB37D873306EEF955_
+7FFCE6ADFFFF6739BB876147E4F7899977FBB033_
+7FFCE6A100016E77956FB0C2A1FDBCCD77BFFE39_
+3FF8C552B8314CE3CE7DDCB491E7E66724A8264D_
+1FF0C556843A1DC7A5DCF60493CDBF3106724AE7_
+0FE0C55687F2D38FB9B66D088F9793DD75ACF1B3_
+0FE0C5284FE2D29DFF6B32C88F3DACB77471B199_
+07C0C6A94FDA9D79ABC5BDB9CE67A18924DBD2EF_
+0380C6AB544222B1B0A5BEDFFCCDB3270404F6A1_
+0380E6AB52242773B155A769CD87AD89FFF93E7D_
+0100E6944005AF97CDB5A3988D1DA122D55A4E27_
+0100E754BFFDBEBF932B29E88B37B388EAAE9393_
+0000FF55BFFD3C5DB5166CE49A2DAD225DDBA4CB_
+01009155AAAC787DD60CCE74BF47A198AEE8C967_
+0100FF5E2AA87C5F9730803EA38DF2B2355A3271_
+03808A62555B5EE9B07FFFFFFF87BC9C9AA88CBF_
+0380FEA2D63B4FBF9084444445ADB1A42FFA2539_
+07C08B5ED77A4769BFFFFFFFFFA7BA570C99DA4D_
+0FE0FEA109C842BF98EAAAAAAB8D9CF94B694F9D_
+1FF0FD421390857FB1D555555719B9F296D29F39_
+1FF096E25EEB7AD1E5FFFFFFFFFDB25B9930EA5D_
+1FF0FDF2DC6B457FB5A2222221099CA45FF4258D_
+0D60977ADAAA4651E1FFFFFFFE0DFD311559393D_
+0100FA3E15547AFFB1C57C010CE98E4C5AAC4D4F_
+0380BE1E3555AA89E2FD2E73306BE69317751985_
+0000BA3CBFFDAAFFB459273668ADD325DBBA44B5_
+0000FD7DBFFD2AE7ECD11794D4C9C9C9755711CD_
+0380E9F5A0022967B8B119C5ADB3E4725AAB4485_
+0100CEE4244AD567E1B396E5AA8DBE7C9FFF91B5_
+0D608D44422AD563B33FFB7DA50D856F2020E4CD_
+1FF09EB95BF29563E6739DBDA3D5F74BDB249185_
+1FF0B94B47F214A3BCF1134CD6FF998D8E2EED35_
+1FF0F1CB4FE16AA3E9F110B66D9DCD8F35AEBBC9_
+0FE0E3B85C216AA3B3C9206F3BA5E7524E608CFD_
+07C0C7328C1D4AA3E7892D3BBE73B2641524E667_
+0380EE7680008567BF85430DF6A99C7FFDEEB33D_
+03809CE6FFFFB567EF27E286E1DDCC0DDFEE9991_
+010083E0AAAAF567BECD780140A9AA9F7760CCE1_
+010080CDFFFFE2ADECD224514071F32DDDE4E681_
+0000803D000023ADBD15563180219BFFFFEEB701_
+00008007FFFFFE2DE83889290001FE40002E9801_
+00008001D5CBA529BE75558200018DB6DB60E001_
+00008000AAB55AA9E66222640001F9C0001F8001_
+00008000AAC006A9B7D555741F01F03FFDDC0001_
+000080006A838725E7C888880E01D060D7740001_
+000081F02A810425B2F5D550E4E1D0403DDC0001_
+000080E02A86C425E2AA2221F5F1F04017700001_
+00008E4E2AB83C25B2B41543FFF9F07015500F81_
+00009F5F2AAD6C29E2B40CC3FFF9D0FB15500701_
+0000BFFFAA818429B2B98B83FFF9F08495507271_
+0000BFFFAA808429E159C683FFF9D0991550FAF9_
+0000BFFFAA808425B15005C3FFF9A9C01551FFFD_
+0000BFFFAA9DF425A15103F9FFF1D1001551FFFD_
+00009FFF2AB96421A15183B8FFE1F09E1551FFFD_
+00009FFF2A9DF431F15201F87FC1D08D1551FFFD_
+00008FFE2AA0841BD15E3B083F81F09E1550FFF9_
+000087FC2A9E7C0F88BA33B01F01F090D550FFF9_
+000083F82A800405D0BE7E701F01D08F15507FF1_
+000081F03FFFFC01D0A246F00E01D08015503FE1_
+000081F03FFFFC01D0AE3E100E01F0FFFFF01FC1_
+000080E048208401A0B01D500401F1FFFFF00F81_
+000080E0BEFBE601A0A82CD00401D3AEEBB80F81_
+00008041BEFBEF0180644DD00001A75D775C0701_
+000080439C71C6818072981000018EAEEBAE0701_
+000080050820824180692D0800019D5D77570201_
+0000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF_
+00000000001EF1C781E67C3E7FCF8FF707FCF838_
+00000000001EF3E783F6FE7F7FDFCFF787FDFC38_
+00000400000E67730337C7E3E0F8E037870F8E38_
+0000040000076633033783C1B0106036C383066C_
+000004000003E633033783E3B00067B6C1C3006C_
+00000E000001E6330337837F180F6FF661E3806C_
+00000E000001E6330337C73E181FFC7663F1C06C_
+00000E000003E6330337FE7F0C38F8263720E0C6_
+00001F00000366FB0337BCE38C307806360070FE_
+00001F00000767FB0F3780C18C30780FFE0038FE_
+00003F80000667B30F3782C18630782FFE081CC6_
+00003F80000E67739F37C7E38638FC76071F0F83_
+00007FC0001EFFE1FBF6FE7F061FCFEF03FBFF83_
+0000FFE0001EF5C0F1E67C3E060F87CF01F3FF83_
+0000FFE0001EF1D1E33C7C3E0C0F87C079F3FF83_
+0001FFF0001EF3FBF37EFE7F0C1FCFE07BFBFF83_
+0003FFF8000CE7773B67C7E38C38FC70371F8783_
+0003FFF8000CC6F61B6683C18C30683FFA0DC0C6_
+0007FFFC000DCFF61B6603C18630603FF80CE0FE_
+000FFFFE000D8FB01B667BE386306036300C70FE_
+001FFFFF000F86301B66FF7F0638E836309C38C6_
+003FFFFF800F06301B67C73E033FDC7331F81C6C_
+007FFFFFC00F06301B67837F03379FE330F00E6C_
+00FFFFFFE00F86301B6783E381B01BC1B070066C_
+01FFFFFFF00DC6301B6783C181B05801B03B066C_
+03FFFFFFF80CE7701B67C7E3E0F8F800F61F8E38_
+07FFFFFFFC1EF3E03F7EFE7F7FDFDFE0F7FDFC38_
+07FFFFFFFC1EF1C03F3C7C3E7FCF9FE077FCF838_
+0FFFFFFFFE1EF1C781E67C3E7FCF8FF707FCF838_
+0FFFFFFFFE1EF3E783F6FE7F7FDFCFF787FDFC38_
+1FFFFFFFFF0E67730337C7E3E0F8E037870F8E38_
+1FFFFFFFFF076633033783C1B0106036C383066C_
+1FFFFFFFFF03E633033783E3B00067B6C1C3006C_
+3FFFFFFFFF81E6330337837F180F6FF661E3806C_
+3FFFFFFFFF81E6330337C73E181FFC7663F1C06C_
+3FFFFFFFFF83E6330337FE7F0C38F8263720E0C6_
+3FFFFFFFFF8366FB0337BCE38C307806360070FE_
+3FFFFFFFFF8767FB0F3780C18C30780FFE0038FE_
+1FFFFFFFFF0667B30F3782C18630782FFE081CC6_
+1FFFFFFFFF0E67739F37C7E38638FC76071F0F83_
+0FFFDF7FFE1EFFE1FBF6FE7F061FCFEF03FBFF83_
+0FFF8E3FFE1EF5C0F1E67C3E060F87CF01F3FF83_
+07FF0E1FFC1EF1D1E33C7C3E0C0F87C079F3FF83_
+03FE0E0FF81EF3FBF37EFE7F0C1FCFE07BFBFF83_
+00F80E03E00CE7773B67C7E38C38FC70371F8783_
+00000E00000CC6F61B6683C18C30683FFA0DC0C6_
+00001F00000DCFF61B6603C18630603FF80CE0FE_
+00001F00000D8FB01B667BE386306036300C70FE_
+00003F80000F86301B66FF7F0638E836309C38C6_
+00003F80000F06301B67C73E033FDC7331F81C6C_
+00007FC0000F06301B67837F03379FE330F00E6C_
+0000FFE0000F86301B6783E381B01BC1B070066C_
+0001FFF0000DC6301B6783C181B05801B03B066C_
+0003FFF8000CE7701B67C7E3E0F8F800F61F8E38_
+00000000001EF3E03F7EFE7F7FDFDFE0F7FDFC38_
+00000000001EF1C03F3C7C3E7FCF9FE077FCF838_
+"
+end
+
+
+# The following notices accompanied the original Spider source from which
+# these bitmaps were taken.
+
+
+# Copyright 1990 Heather Rose and Sun Microsystems, Inc.
+#
+# Permission to use, copy, modify, distribute, and sell this software and its
+# documentation for any purpose is hereby granted without fee, provided that
+# the above copyright notice appear in all copies and that both that copyright
+# notice and this permission notice appear in supporting documentation, and
+# that the names of Donald Woods and Sun Microsystems not be used in
+# advertising or publicity pertaining to distribution of the software without
+# specific, written prior permission. Heather Rose and Sun Microsystems not
+# be used in [_sic_]
+# advertising or publicity pertaining to distribution of the software without
+# specific, written prior permission. Heather Rose and Sun Microsystems make
+# no representations about the suitability of this software for any purpose.
+# It is provided "as is" without express or implied warranty.
+#
+# THE ABOVE-NAMED DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+# INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT
+# SHALL HEATHER ROSE OR SUN MICROSYSTEMS BE LIABLE FOR ANY SPECIAL, INDIRECT OR
+# CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
+# DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
+# TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
+# OF THIS SOFTWARE.
+#
+# Author:
+# Heather Rose
+# hrose@sun.com
+#
+# Sun Microsystems, Inc.
+# 2550 Garcia Avenue
+# Mountain View, CA 94043
+
+
+# Copyright 1990 David Lemke and Network Computing Devices
+#
+# Permission to use, copy, modify, distribute, and sell this software and its
+# documentation for any purpose is hereby granted without fee, provided that
+# the above copyright notice appear in all copies and that both that
+# copyright notice and this permission notice appear in supporting
+# documentation, and that the name of Network Computing Devices not be
+# used in advertising or publicity pertaining to distribution of the
+# software without specific, written prior permission. Network Computing
+# Devices makes no representations about the suitability of this software
+# for any purpose. It is provided "as is" without express or implied
+# warranty.
+#
+# NETWORK COMPUTING DEVICES DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS
+# SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS,
+# IN NO EVENT SHALL NETWORK COMPUTING DEVICES BE LIABLE FOR ANY SPECIAL,
+# INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE
+# OR PERFORMANCE OF THIS SOFTWARE.
+#
+# Author:
+# Dave Lemke
+# lemke@ncd.com
+#
+# Network Computing Devices, Inc
+# 350 North Bernardo Ave
+# Mountain View, CA 94043
+#
+# @(#)copyright.h 2.2 90/04/27
+
+
+# Copyright (c) 1989, Donald R. Woods and Sun Microsystems, Inc.
+#
+# Permission to use, copy, modify, distribute, and sell this software and its
+# documentation for any purpose is hereby granted without fee, provided that
+# the above copyright notice appear in all copies and that both that copyright
+# notice and this permission notice appear in supporting documentation, and
+# that the names of Donald Woods and Sun Microsystems not be used in
+# advertising or publicity pertaining to distribution of the software without
+# specific, written prior permission. Donald Woods and Sun Microsystems make
+# no representations about the suitability of this software for any purpose.
+# It is provided "as is" without express or implied warranty.
+#
+# THE ABOVE-NAMED DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+# INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT
+# SHALL DONALD WOODS OR SUN MICROSYSTEMS BE LIABLE FOR ANY SPECIAL, INDIRECT OR
+# CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
+# DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
+# TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
+# OF THIS SOFTWARE.
+#
+# History: Spider is a solitaire card game that can be found in various books
+# of same; the rules are presumed to be in the public domain. The author's
+# first computer implementation was on the Stanford Artificial Intelligence Lab
+# system (SAIL). It was later ported to the Xerox Development Environment.
+# The card images are loosely based on scanned-in images but were largely
+# redrawn by the author with help from Larry Rosenberg.
+#
+# This program is written entirely in NeWS and runs on OPEN WINDOWS 1.0.
+# It could be made to run much faster if parts of it were written in C, using
+# NeWS mainly for its display and input capabilities, but that is left as an
+# exercise for the reader. Spider may also run with little or no modification
+# on subsequent releases of OPEN WINDOWS, but no guarantee is made on this
+# point (nor any other; see above!). To run Spider, feed this file to 'psh'.
+#
+# Author: Don Woods
+# woods@sun.com
+#
+# Sun Microsystems, Inc.
+# 2550 Garcia Avenue
+# Mountain View, CA 94043
diff --git a/ipl/gprocs/cells.icn b/ipl/gprocs/cells.icn
new file mode 100644
index 0000000..4bd59f0
--- /dev/null
+++ b/ipl/gprocs/cells.icn
@@ -0,0 +1,191 @@
+############################################################################
+#
+# File: cells.icn
+#
+# Subject: Procedures for creating and coloring panels of cells
+#
+# Author: Ralph E. Griswold
+#
+# Date: December 16, 2002
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# These procedures create an manipulate panels of cells.
+#
+# makepanel(n, m, size, fg, bg, pg)
+# makes a panel in a hidden window with nxm cells of the
+# given size, default 10. fg, bg, and pg are the
+# colors for the window and panel backgrounds. fg
+# and bg default to black and white, respectively.
+# If pg is not given a patterned background is used.
+#
+# matrixpanel(matrix, size, fg, bg, pg)
+# same as makepanel(), except matrix determines the
+# dimensions.
+#
+# clearpanel(panel)
+# restores the panel to its original state as made by
+# makepanel.
+#
+# colorcell(panel, n, m, color)
+# colors the cell (n,m) in panel with color.
+#
+# colorcells(panel, tier)
+# is like colorcell(), except it operates on a tie-up
+# record.
+#
+# cell(panel, x, y)
+# returns Cell() record for the cell in which x,y
+# lies. If fails if the point is out of bounds.
+#
+# tiercells(panel, matrix)
+# is like colorcell(), except all cells are colored
+# using a matrix of colors.
+#
+############################################################################
+#
+# Requires: Version 9 graphics
+#
+############################################################################
+#
+# Links: wopen
+#
+############################################################################
+
+link wopen
+
+record Cell(n, m, color)
+record Panel(window, n, m, size, fg, bg, pg)
+
+procedure makepanel(n, m, cellsize, fg, bg, pg) #: make panel of cells
+ local window, x, y, width, height, panel
+
+ /fg := "black"
+ /bg := "white"
+
+ /cellsize := 10
+
+ width := (n * cellsize) + 1
+ height := (m * cellsize) + 1
+
+ window := WOpen("width=" || width, "height=" || height,
+ "fg=" || fg, "bg=" || bg, "canvas=hidden") | fail
+
+ panel := Panel(window, n, m, cellsize, fg, bg, pg)
+
+ clearpanel(panel)
+
+ return panel
+
+end
+
+procedure clearpanel(panel)
+ local width, height, x, y
+
+ if \panel.pg then { # default is textured
+ WAttrib(panel.window, "fillstyle=textured")
+ Pattern(panel.window, "checkers")
+ Bg(panel.window, "very dark gray")
+ }
+ else Fg(panel.window, panel.fg)
+
+ width := WAttrib(panel.window, "width")
+ height := WAttrib(panel.window, "height")
+
+ every x := 0 to width by panel.size do
+ DrawLine(panel.window, x, 0, x, height)
+
+ every y := 0 to height by panel.size do
+ DrawLine(panel.window, 0, y, width, y)
+
+ WAttrib(panel.window, "fillstyle=solid")
+
+ return panel
+
+end
+
+procedure matrixpanel(matrix, cellsize, fg, bg, pg)
+
+ return makepanel(*matrix[1], *matrix, cellsize, fg, bg)
+
+end
+
+procedure colorcell(panel, n, m, color) #: color cell in panel
+ local cellsize
+
+ if not(integer(n) & integer(m)) then
+ stop("Non-integer value to colorcell(). n=", image(n), " m=", image(m))
+
+ cellsize := panel.size
+
+ Fg(panel.window, color)
+
+ FillRectangle(panel.window, (n - 1) * cellsize + 1, (m - 1) * cellsize + 1,
+ cellsize - 1, cellsize - 1)
+
+ return panel
+
+end
+
+procedure colorcells(panel, matrix) #: color all cells in panel
+ local i, j, n, m, cellsize
+
+ cellsize := panel.size
+
+ m := *matrix
+ n := *matrix[1]
+
+ every i := 1 to m do {
+ every j := 1 to n do {
+ # fudge 0/1 matrix
+ if matrix[i, j] === "1" then matrix[i, j] := "white"
+ else if matrix[i, j] === "0" then matrix[i, j] := "black"
+ Fg(panel.window, matrix[i, j])
+ stop("Fg() failed in colorcells() with matrix[" ||
+ i || "," || j || "]=" || matrix[i, j] || ".")
+ FillRectangle(panel.window, (j - 1) * cellsize + 1, (i - 1) * cellsize + 1,
+ cellsize - 1, cellsize - 1)
+ }
+ }
+
+ return panel
+
+end
+
+procedure tiercells(panel, tier) #: color all cells in panel
+ local i, j, n, m, cellsize, matrix
+
+ cellsize := panel.size
+
+ m := tier.shafts
+ n := tier.treadles
+ matrix := tier.matrix
+
+ every i := 1 to m do {
+ every j := 1 to n do {
+ if matrix[i, j] === "1" then Fg(panel.window, "white")
+ else Fg(panel.window, "black")
+ FillRectangle(panel.window, (j - 1) * cellsize + 1, (i - 1) * cellsize + 1,
+ cellsize - 1, cellsize - 1)
+ }
+ }
+
+ return panel
+
+end
+
+procedure cell(panel, x, y)
+ local n, m
+
+ n := x / panel.size + 1
+ m := y / panel.size + 1
+
+ if (n > panel.n) | (m > panel.m) then fail
+
+ return Cell(n, m, Pixel(panel.window, x, y))
+
+end
diff --git a/ipl/gprocs/clip.icn b/ipl/gprocs/clip.icn
new file mode 100644
index 0000000..a3b9538
--- /dev/null
+++ b/ipl/gprocs/clip.icn
@@ -0,0 +1,78 @@
+############################################################################
+#
+# File: clip.icn
+#
+# Subject: Procedures for clipboard operations
+#
+# Author: Ralph E. Griswold
+#
+# Date: May 26, 1994
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# XCopy(window, x, y, w, h) copies an area of window to the clipboard.
+#
+# XCut(window, x, y, w, h) copies an area of window to the clipboard and
+# erases it from window.
+#
+# XPaste(window, x, y) copies the clipboard to position x,y in window.
+#
+# NewClip(w, h) is a utility procedure that discards the old clipboard and
+# creates a new one of the specified dimensions.
+#
+############################################################################
+#
+# Requires: Version 9 graphics
+#
+############################################################################
+#
+# Links: xcompat
+#
+############################################################################
+
+link xcompat
+
+global Clipboard
+
+procedure NewClip(w, h)
+
+ close(\Clipboard)
+
+ Clipboard := XBind(, , "width=" || w, "height=" || h) |
+ stop("*** cannot create clipboard")
+
+ return
+
+end
+
+procedure XCopy(window, x, y, w, h)
+
+ NewClip(w, h)
+
+ CopyArea(window, Clipboard, x, y, w, h)
+
+ return
+
+end
+
+procedure XCut(window, x, y, w, h)
+
+ XCopy(window, x, y, w, h)
+
+ EraseArea(window, x, y, w, h)
+
+ return
+
+end
+
+procedure XPaste(window, x, y)
+
+ CopyArea(Clipboard, window, , , , , x, y)
+
+ return
+
+end
diff --git a/ipl/gprocs/clipping.icn b/ipl/gprocs/clipping.icn
new file mode 100644
index 0000000..5220690
--- /dev/null
+++ b/ipl/gprocs/clipping.icn
@@ -0,0 +1,135 @@
+############################################################################
+#
+# File: clipping.icn
+#
+# Subject: Procedures for clipping lines
+#
+# Authors: William S. Evans and Gregg M. Townsend
+#
+# Date: June 16, 2000
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# ClipLine(W, L, x, y, w, h) clips the multisegment line specified
+# by coordinates in L to the region (x, y, w, h), which defaults
+# to the clipping region of the window W. ClipLine() returns a
+# list of coordinates suitable for calling DrawSegment(). If no
+# segments remain after clipping, ClipLine() fails.
+#
+# Coalesce(L) connects adjoining segments from a DrawSegment()
+# argument list such as is produced by ClipLine(). Coalesce()
+# returns a list of DrawLine() lists.
+#
+# DrawClipped(W, x1, y1, x2, y2, ...) draws a line using ClipLine()
+# with the clipping region of the window W. DrawClipped() is
+# superior to DrawLine() only when lines with extremely large
+# coordinate values (beyond +/-32767) are involved.
+#
+############################################################################
+
+
+# DrawClipped(W, x1, y1, x2, y2, ...) -- draw line using ClipLine()
+
+procedure DrawClipped(a[]) #: draw line with clipping
+ local win
+
+ if type(a[1]) == "window" then
+ win := pop(a)
+ else
+ win := &window
+
+ DrawSegment ! push(ClipLine(win, a), win)
+ return win
+end
+
+
+# ClipLine(W, L, x, y, w, h) -- clip polyline to region, returning segments.
+#
+# Cyrus-Beck parametric line clipping with Liang-Barsky
+# optimizations for axis-aligned rectangular clipping regions.
+
+procedure ClipLine(win, L, x, y, w, h) #: clip line for DrawSegment
+ local i, ret, tin, tout, delx, dely, x0, x1, xmax, y0, y1, ymax
+
+ if (type(win) == "list") then # window param is optional
+ return ClipLine(&window, win, L, x, y, w)
+
+ /x := WAttrib(win, "clipx") - WAttrib(win, "dx")
+ /y := WAttrib(win, "clipy") - WAttrib(win, "dy")
+ /w := WAttrib(win, "clipw")
+ /h := WAttrib(win, "cliph")
+ if w < 0 then
+ x -:= (w := -w)
+ if h < 0 then
+ y -:= (h := -h)
+ xmax := x + w
+ ymax := y + h
+
+ ret := []
+ x1 := L[1]
+ y1 := L[2]
+
+ every i := 3 to *L by 2 do {
+ x0 := x1
+ y0 := y1
+ x1 := L[i]
+ y1 := L[i + 1]
+ tin := 0.0
+ tout := 1.0
+
+ delx := real(x1 - x0)
+ if delx < 0.0 then {
+ tin <:= (xmax - x0) / delx
+ tout >:= (x - x0) / delx
+ }
+ else if delx > 0.0 then {
+ tin <:= (x - x0) / delx
+ tout >:= (xmax - x0) / delx
+ }
+ else
+ x <= x0 <= xmax | next
+ if tout < tin then next
+
+ dely := real(y1 - y0)
+ if dely < 0.0 then {
+ tin <:= (ymax - y0) / dely
+ tout >:= (y - y0) / dely
+ }
+ else if dely > 0.0 then {
+ tin <:= (y - y0) / dely
+ tout >:= (ymax - y0) / dely
+ }
+ else
+ y <= y0 <= ymax | next
+ if tout < tin then next
+
+ put(ret, x0 + tin*delx, y0 + tin*dely, x0 + tout*delx, y0 + tout*dely)
+ }
+
+ if *ret > 0 then
+ return ret
+ else
+ fail
+end
+
+
+# Coalesce(L) -- connect adjoining segments
+
+procedure Coalesce(L) #: connect adjoining segments
+ local i, all, seg, x1, y1, x2, y2
+
+ all := []
+ every i := 1 to *L by 4 do {
+ x1 := L[i]
+ y1 := L[i + 1]
+ if x1 ~=== x2 | y1 ~=== y2 then
+ put(all, seg := [x1, y1])
+ put(seg, x2 := L[i + 2], y2 := L[i + 3])
+ }
+
+ return all
+end
diff --git a/ipl/gprocs/clrnames.icn b/ipl/gprocs/clrnames.icn
new file mode 100644
index 0000000..80b110e
--- /dev/null
+++ b/ipl/gprocs/clrnames.icn
@@ -0,0 +1,37 @@
+############################################################################
+#
+# File: clrnames.icn
+#
+# Subject: Procedure to generate color names
+#
+# Author: Ralph E. Griswold
+#
+# Date: March 4, 1995
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# This procedure generates all the color names in the Icon portable color
+# naming system. Not all names produce unique colors.
+#
+############################################################################
+
+procedure clrnames()
+ static lightness, saturation, hue1, hue2
+
+ hue2 := ["black", "gray", "white", "pink", "violet",
+ "brown", "red", "orange", "yellow", "green", "cyan",
+ "blue", "purple", "magenta"]
+ hue1 := hue2 ||| ["blackish", "grayish", "whitish", "pinkish",
+ "violetish", "brownish", "reddish", "orangish", "yellowish",
+ "greenish", "cyanish", "bluish", "purplish", "magentaish"]
+ saturation := ["weak", "moderate", "strong", "vivid"]
+ lightness := ["very light", "light", "medium", "dark", "very dark"]
+
+ suspend !lightness || " " || !saturation || " " || !hue2
+ suspend !lightness || " " || !saturation || " " || !hue1 || " " || !hue2
+
+end
diff --git a/ipl/gprocs/clrutils.icn b/ipl/gprocs/clrutils.icn
new file mode 100644
index 0000000..9dcbed6
--- /dev/null
+++ b/ipl/gprocs/clrutils.icn
@@ -0,0 +1,45 @@
+############################################################################
+#
+# File: clrutils.icn
+#
+# Subject: Procedures to convert color formats
+#
+# Author: Ralph E. Griswold
+#
+# Date: September 17, 1998
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# These procedures convert between comma-separated Icon color
+# specifications and a record with r, g, and b fields.
+#
+############################################################################
+
+record RGB(r, g, b)
+
+procedure colortorgb(color) #: rgb record for color
+ local rgb
+
+ rgb := RGB()
+
+ color ? {
+ rgb.r := tab(upto(',')) | fail
+ move(1)
+ rgb.g := tab(upto(',')) | fail
+ move(1)
+ rgb.b := tab(0)
+ }
+
+ return rgb
+
+end
+
+procedure rgbtocolor(rgb)
+
+ return rgb.r || "," || rgb.g || "," || rgb.b
+
+end
diff --git a/ipl/gprocs/color.icn b/ipl/gprocs/color.icn
new file mode 100644
index 0000000..615ca05
--- /dev/null
+++ b/ipl/gprocs/color.icn
@@ -0,0 +1,526 @@
+############################################################################
+#
+# File: color.icn
+#
+# Subject: Procedures dealing with colors
+#
+# Author: Gregg M. Townsend
+#
+# Date: April 1, 1997
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# These procedures deal with colors in various ways.
+#
+# ScaleGamma(v, g) scales a number with gamma correction.
+#
+# Blend(k1, k2, ...) generates a sequence of colors.
+#
+# Contrast(win, k) returns "white" or "black" contrasting with k.
+#
+# Shade(win, k) sets Fg(), with dithering on a bilevel screen.
+#
+# RandomColor(W, p) returns a randomly chosen color from a palette.
+#
+# PaletteGrays(W, p) returns the gray entries of a palette.
+#
+# RGBKey(W, p, r, g, b) returns the palette key closest to (r,g,b).
+#
+# HSVKey(W, p, h, s, v) returns the palette key closest to (h/s/v).
+#
+# HSV(k) returns the h/s/v interpretation of a color.
+#
+# HSVValue(hsv) returns the ColorValue() of an h/s/v string.
+#
+# HLS(k) returns the h:l:s interpretation of a color.
+#
+# HLSValue(hls) returns the ColorValue() of an h:l:s string.
+#
+############################################################################
+#
+# ScaleGamma(v, g) nonlinearly scales the number v (between 0.0 and 1.0)
+# to an integer between 0 and 65535 using a gamma correction factor g.
+# the default value of g is 2.5.
+#
+# Blend(color1, color2, color3,...) generates ColorValue(color1), then
+# some intermediate shades, then ColorValue(color2), then some more
+# intermediate shades, and so on, finally generating the color value of
+# the last argument. An integer argument can be interpolated at any
+# point to set the number of steps (the default is four) from one color
+# to the next.
+#
+# Contrast(win, colr) returns either "white" or "black", depending
+# on which provides the greater contrast with the specified color.
+#
+# Shade(win, colr) sets the foreground for an area filling operation.
+# On a color screen, Shade() sets the foreground color and returns the
+# window. On a bilevel monochrome screen, Shade() sets the foreground
+# to a magic-square dithering pattern approximating the luminance of the
+# color specified. If the environment variable XSHADE is set to "gray"
+# (or "grey") then Shade simulates a multilevel grayscale monitor.
+# If it is set to any other value, Shade simulates a bilevel monitor.
+#
+# RandomColor(win, palette) returns a randomly chosen color from the
+# given image palette, excluding the "extra" grays of the palette, if
+# any. (Colors are selected from a small finite palette, rather than
+# from the entire color space, to avoid running out of colors if a
+# large number of random choices are desired.) The default palette
+# for this procedure is "c6".
+#
+# PaletteGrays([win,] palette) is like PaletteChars but it returns only
+# the characters corresponding to shades of gray. The characters are
+# ordered from black to white, and in all palettes the shades of gray
+# are equally spaced.
+#
+# RGBKey([win,] palette, r, g, b) returns a palette key given the
+# three color components as real number from 0.0 to 1.0.
+# HSVKey([win,] palette, h, s, v) returns a palette key given a
+# hue, saturation, and value as real numbers from 0.0 to 1.0.
+#
+# HSV() and HSVValue() convert between Icon color strings and strings
+# containing slash-separated HSV values with maxima of "360/100/100".
+# HSV(k) returns the h/s/v interpretation of an Icon color specification;
+# HSVValue(hsv) translates an h/s/v value into an Icon r,g,b value.
+#
+# HLS() and HLSValue() convert between Icon color strings and strings
+# containing colon-separated HLS values with maxima of "360:100:100".
+# HLS(k) returns the h:l:s interpretation of an Icon color specification;
+# HLSValue(hls) translates an h:l:s value into an Icon r,g,b value.
+#
+############################################################################
+#
+# Requires: Version 9 graphics
+#
+############################################################################
+
+
+# ScaleGamma(v, g) -- scale fraction to int with gamma correction.
+
+procedure ScaleGamma(v, g) #: scale with gamma correction
+ /g := 2.5
+ return integer(65535 * v ^ (1.0 / g))
+end
+
+
+# Blend(color1, color2, ...) -- generate sequence of colors
+
+procedure Blend(args[]) #: generate sequence of colors
+ local win, n, s, a, i, f1, f2, r1, g1, b1, r2, g2, b2, r3, g3, b3
+ static type
+
+ initial type := proc("type", 0) # protect attractive name
+
+ n := 4
+ if type(args[1]) == "window" then
+ win := get(args)
+ else
+ win := &window
+
+ while a := get(args) do
+ if integer(a) >= 0 then
+ n := integer(a)
+ else {
+ s := ColorValue(win, a) | fail
+ s ? {
+ r2 := tab(many(&digits)); move(1)
+ g2 := tab(many(&digits)); move(1)
+ b2 := tab(many(&digits))
+ }
+ if /r1 then
+ suspend s
+ else
+ every i := 1 to n do {
+ f2 := real(i) / real(n)
+ f1 := 1.0 - f2
+ r3 := integer(f1 * r1 + f2 * r2)
+ g3 := integer(f1 * g1 + f2 * g2)
+ b3 := integer(f1 * b1 + f2 * b2)
+ suspend r3 || "," || g3 || "," || b3
+ }
+ r1 := r2
+ g1 := g2
+ b1 := b2
+ }
+end
+
+
+# Contrast(win, color) -- return "white" or "black" to maximize contrast
+
+procedure Contrast(win, color) #: choose contrasting color
+ static l, type
+ initial {
+ l := ["white", "black"]
+ type := proc("type", 0) # protect attractive name
+ }
+
+ if type(win) == "window" then
+ return l[1 + PaletteKey(win, "g2", color)]
+ else
+ return l[1 + PaletteKey("g2", win)]
+end
+
+
+# Shade(win, color) -- approximate a shade with a pattern if bilevel screen
+
+procedure Shade(win, color) #: dither shade using pattern
+ local r, g, b
+ static dmat, env, type
+
+ initial {
+ env := ("" ~== map(getenv("XSHADE")))
+ type := proc("type", 0) # protect attractive name
+ }
+
+ if type(win) ~== "window" then {
+ color := win
+ win := &window
+ }
+ if WAttrib(win, "depth") ~== "1" & /env then {
+ Fg(win, color) | fail
+ return win
+ }
+ (ColorValue(win, color) | fail) ? {
+ r := tab(many(&digits)); move(1)
+ g := tab(many(&digits)); move(1)
+ b := tab(many(&digits))
+ }
+ g := integer(0.30 * r + 0.59 * g + 0.11 * b)
+
+ if \env == ("gray" | "grey") then {
+ Fg(win, g || "," || g || "," || g)
+ return win
+ }
+
+ /dmat := [
+ "4,15,15,15,15",
+ "4,15,15,13,15",
+ "4,11,15,13,15",
+ "4,10,15,13,15",
+ "4,10,15,5,15",
+ "4,10,7,5,15",
+ "4,10,7,5,14",
+ "4,10,7,5,10",
+ "4,10,5,5,10",
+ "4,10,5,5,2",
+ "4,10,4,5,2",
+ "4,10,0,5,2",
+ "4,10,0,5,0",
+ "4,8,0,5,0",
+ "4,8,0,1,0",
+ "4,8,0,0,0",
+ "4,0,0,0,0",
+ ]
+ WAttrib(win, "fillstyle=textured")
+ g := g / 3856 + 1
+ Pattern(win, dmat[g])
+ return win
+end
+
+
+# RandomColor(win, palette) -- choose random color
+
+procedure RandomColor(win, palette) #: choose random color
+ local s, n
+ static type
+
+ initial type := proc("type", 0) # protect attractive name
+
+ if type(win) ~== "window" then
+ palette:= win # window allowed but ignored
+ /palette := "c6"
+
+ s := PaletteChars(palette)
+ palette ?
+ if ="c" & any('23456') then {
+ n := integer(move(1))
+ s := s[1 +: n * n * n]
+ }
+ return PaletteColor(palette, ?s)
+
+end
+
+
+# PaletteGrays(win, palette) -- return grayscale entries from palette.
+
+procedure PaletteGrays(win, palette) #: grayscale entries from palette
+ static type
+
+ initial type := proc("type", 0) # protect attractive name
+ if (type(win) ~== "window") then
+ palette := win # window not needed
+
+ palette := string(palette) | runerr(103, palette)
+
+ if palette ? ="g" then
+ return PaletteChars(palette)
+
+ return case palette of {
+ "c1": "0123456"
+ "c2": "kxw"
+ "c3": "@abMcdZ"
+ "c4": "0$%&L*+-g/?@}"
+ "c5": "\0}~\177\200\37\201\202\203\204>\205\206\207\210]_
+ \211\212\213\214|"
+ "c6": "\0\330\331\332\333\334+\335\336\337\340\341V\342\343\344\345_
+ \346\201\347\350\351\352\353\254\354\355\356\357\360\327"
+ default: fail
+ }
+end
+
+
+# RGBKey(win, palette, r, g, b) -- find key given real-valued color
+
+procedure RGBKey(win, palette, r, g, b) #: return palette key for color
+ static type
+
+ initial type := proc("type", 0) # protect attractive name
+ if type(win) ~== "window" then # allow unused window argument
+ win :=: palette :=: r :=: g :=: b
+ r := integer(r * 65535.99)
+ g := integer(g * 65535.99)
+ b := integer(b * 65535.99)
+ return PaletteKey(palette, r || "," || g || "," || b)
+end
+
+
+# HSVKey(win, palette, h, s, v) -- find nearest key from h,s,v in [0.0,1.0]
+#
+# HSV conversion based on Foley et al, 2/e, p.593
+
+procedure HSVKey(win, palette, h, s, v) #: nearest key from HSV specification
+ local i, f, p, q, t, r, g, b
+ static type
+
+ initial type := proc("type", 0) # protect attractive name
+ if type(win) ~== "window" then # allow unused window argument
+ win :=: palette :=: h :=: s :=: v
+
+ if s = 0.0 then # achromatic case
+ return RGBKey(palette, v, v, v)
+
+ h *:= 6.0 # hue [0.0 - 6.0)
+ if h >= 6.0 then
+ h := 0.0
+
+ i := integer(h)
+ f := h - i
+ p := v * (1.0 - s)
+ q := v * (1.0 - f * s)
+ t := v * (1.0 - (1.0 - f) * s)
+
+ case i of {
+ 0: { r := v; g := t; b := p } # red - yellow
+ 1: { r := q; g := v; b := p } # yellow - green
+ 2: { r := p; g := v; b := t } # green - cyan
+ 3: { r := p; g := q; b := v } # cyan - blue
+ 4: { r := t; g := p; b := v } # blue - magenta
+ 5: { r := v; g := p; b := q } # magenta - red
+ }
+
+ return RGBKey(palette, r, g, b)
+end
+
+
+# HSV(k) -- return h/s/v interpretation of color spec.
+#
+# h is hue (0 <= h < 360)
+# s is saturation (0 <= s <= 100)
+# v is value (0 <= v <= 100)
+#
+# based on Foley et al, 2/e, p.592
+
+procedure HSV(k) #: HSV interpretation of color
+ local r, g, b, h, s, v, min, max, d
+
+ (ColorValue(k) | fail) ? {
+ r := tab(many(&digits)) / 65535.0
+ move(1)
+ g := tab(many(&digits)) / 65535.0
+ move(1)
+ b := tab(many(&digits)) / 65535.0
+ }
+
+ min := r; min >:= g; min >:= b # minimum
+ max := r; max <:= g; max <:= b # maximum
+ d := max - min # difference
+
+ v := max # value is max of all values
+ if max > 0 then
+ s := d / max # saturation is (max-min)/max
+ else
+ s := 0.0
+
+ if s = 0 then
+ h := 0.0 # use hue 0 if unsaturated
+ else if g = max then
+ h := 2 + (b - r) / d # yellow through cyan
+ else if b = max then
+ h := 4 + (r - g) / d # cyan through magenta
+ else if g < b then
+ h := 6 + (g - b) / d # magenta through red
+ else
+ h := (g - b) / d # red through yellow
+
+ return integer(60 * h + 0.5) || "/" ||
+ integer(100 * s + 0.5) || "/" || integer(100 * v + 0.5)
+end
+
+
+# HSVValue(hsv) -- return ColorValue of h/s/v string
+#
+# h is hue (0 <= h <= 360)
+# s is saturation (0 <= s <= 100)
+# v is value (0 <= v <= 100)
+#
+# based on Foley et al, 2/e, p.593
+
+procedure HSVValue(hsv) #: color value of HSV specification
+ local h, s, v, r, g, b, i, f, p, q, t
+
+ hsv ? {
+ h := tab(many(&digits)) / 360.0 | fail
+ ="/" | fail
+ s := tab(many(&digits)) / 100.0 | fail
+ ="/" | fail
+ v := tab(many(&digits)) / 100.0 | fail
+ pos(0) | fail
+ }
+ if (h | s | v) > 1 then fail
+
+ if s = 0.0 then { # achromatic case
+ v := integer(65535 * v + 0.499999)
+ return v || "," || v || "," || v
+ }
+
+ h *:= 6.0 # hue [0.0 - 6.0)
+ if h >= 6.0 then
+ h := 0.0
+
+ i := integer(h)
+ f := h - i
+ p := v * (1.0 - s)
+ q := v * (1.0 - f * s)
+ t := v * (1.0 - (1.0 - f) * s)
+
+ case i of {
+ 0: { r := v; g := t; b := p } # red - yellow
+ 1: { r := q; g := v; b := p } # yellow - green
+ 2: { r := p; g := v; b := t } # green - cyan
+ 3: { r := p; g := q; b := v } # cyan - blue
+ 4: { r := t; g := p; b := v } # blue - magenta
+ 5: { r := v; g := p; b := q } # magenta - red
+ }
+
+ return integer(65535 * r + 0.499999) || "," ||
+ integer(65535 * g + 0.499999) || "," || integer(65535 * b + 0.499999)
+end
+
+
+# HLS(k) -- return h:l:s interpretation of color spec.
+#
+# h is hue (0 <= h < 360)
+# l is lightness (0 <= l <= 100)
+# s is saturation (0 <= s <= 100)
+#
+# based on Foley et al, 2/e, p.595
+
+procedure HLS(k) #: HLS interpretation of color
+ local r, g, b, h, l, s, min, max, delta
+
+ (ColorValue(k) | fail) ? {
+ r := tab(many(&digits)) / 65535.0
+ move(1)
+ g := tab(many(&digits)) / 65535.0
+ move(1)
+ b := tab(many(&digits)) / 65535.0
+ }
+
+ min := r; min >:= g; min >:= b # minimum
+ max := r; max <:= g; max <:= b # maximum
+ delta := max - min # difference
+
+ l := (max + min) / 2 # lightness
+
+ if max = min then
+ h := s := 0 # achromatic
+
+ else {
+
+ if l <= 0.5 then
+ s := delta / (max + min) # saturation
+ else
+ s := delta / (2 - max - min)
+
+ if r = max then
+ h := (g - b) / delta # yellow through magenta
+ else if g = max then
+ h := 2 + (b - r) / delta # cyan through yellow
+ else # b = max
+ h := 4 + (r - g) / delta # magenta through cyan
+ if h < 0 then
+ h +:= 6 # ensure positive value
+ }
+
+ return integer(60 * h + 0.5) || ":" ||
+ integer(100 * l + 0.5) || ":" || integer(100 * s + 0.5)
+end
+
+
+# HLSValue(hls) -- return ColorValue of h:l:s string
+#
+# h is hue (0 <= h <= 360)
+# l is lightness (0 <= l <= 100)
+# s is saturation (0 <= s <= 100)
+#
+# based on Foley & Van Dam, 1/e, p.619
+
+procedure HLSValue(hls) #: color value of HLS specification
+ local h, l, s, r, g, b, m1, m2
+
+ hls ? {
+ h := tab(many(&digits)) / 360.0 | fail
+ =":" | fail
+ l := tab(many(&digits)) / 100.0 | fail
+ =":" | fail
+ s := tab(many(&digits)) / 100.0 | fail
+ pos(0) | fail
+ }
+ if (h | l | s) > 1 then fail
+
+ if l <= 0.5 then
+ m2 := l * (1 + s)
+ else
+ m2 := l + s - (l * s)
+ m1 := 2 * l - m2
+
+ if s = 0.0 then
+ r := g := b := l # achromatic
+ else {
+ r := hls_rgb_val(m1, m2, h + 0.3333333)
+ g := hls_rgb_val(m1, m2, h)
+ b := hls_rgb_val(m1, m2, h - 0.3333333)
+ }
+
+ return integer(65535 * r + 0.499999) || "," ||
+ integer(65535 * g + 0.499999) || "," || integer(65535 * b + 0.499999)
+end
+
+procedure hls_rgb_val(n1, n2, hue) # helper function for HLSValue
+ hue *:= 6
+ if hue >= 6 then
+ hue -:= 6
+ else if hue < 0 then
+ hue +:= 6
+ if (hue < 1) then
+ return n1 + (n2 - n1) * hue
+ else if (hue < 3) then
+ return n2
+ else if (hue < 4) then
+ return n1 + (n2 - n1) * (4 - hue)
+ else
+ return n1
+end
diff --git a/ipl/gprocs/colorway.icn b/ipl/gprocs/colorway.icn
new file mode 100644
index 0000000..1324286
--- /dev/null
+++ b/ipl/gprocs/colorway.icn
@@ -0,0 +1,470 @@
+############################################################################
+#
+# File: colorway.icn
+#
+# Subject: Procedures to manipulate color ways
+#
+# Author: Ralph E. Griswold
+#
+# Date: August 3, 2000
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# Note: This file contains procedures that can be linked by programs
+# to add a visual interface, including programs that have one of their
+# own.
+#
+# These procedures support the interactive creation and modification of
+# color ways. ("Color way" is a the term used in the fashion industry for
+# a list of colors used in coordination for fabric design or other
+# decorative purposes. Think color scheme if you like.)
+#
+############################################################################
+#
+# A color way is represented by a list of color specifications. A
+# color specification consists of a name and an associated color.
+# Color ways are presented in alphabetical order of their color names,
+# with the name at the left and a swatch for the corresponding color
+# at the right of the name.
+#
+# The "edit" button is used to switch between two modes: control and
+# edit.
+#
+# In the control mode, the interface menus and the "edit" button
+# are available. The "File" menu provides for creating a new color
+# way, loading an existing color way from a file, and saving the
+# current color way. (Only one color way can be manipulated at a time.)
+# A new color way starts empty. There also is an item to pick a colorway
+# file (which must have suffix "cw").
+#
+# The "Ways" menu allows adding and deleting color specifications from
+# the current color way. When adding, a name dialog is presented first,
+# followed by a color dialog. Color specifications are added until
+# the user cancels one of the dialogs. When deleting, all of the
+# current color specifications are listed by name, and more than one
+# can be selected for deletion.
+#
+# In the edit mode, changes can be made to the current color way. This is
+# done in the window displaying the current color way. Clicking on a name
+# in the color way window produces a dialog to change that name. (The new
+# name cannot be one already in use in the color way.) Clicking on a
+# color swatch to the right of a name beings up a color dialog for selecting
+# a new color for that name. (The same color can appear in more than one
+# color specification.)
+#
+# In the editing mode, pressing the meta key while clicking on a
+# line of the color way causes the color to be deleted.
+#
+# The editing mode is exited by typing a "q" in the color way display
+# window.
+#
+# Shortcuts exist for all interface features. @E is a shortcut for
+# entering the edit mode.
+#
+# Note: The current mode is shown by the "edit" button, which is high-
+# lighted when in the edit mode. There nonetheless can be confusion about
+# the current mode.
+#
+# Unimplemented feature: Prompting user to save color way that has been
+# modified since last save.
+#
+############################################################################
+#
+# See also: cw.icn
+#
+############################################################################
+#
+# Requires: Version 9 graphics, UNIX for "pick feature"
+#
+############################################################################
+#
+# Links: interact, io, lists, strings, tables, vsetup, xcode
+#
+############################################################################
+
+link interact
+link io
+link lists
+link strings
+link tables
+link vsetup
+link xcode
+
+global cw_active # edit-mode switch
+global cw_active_vidget # edit-mode vidget
+global cw_touched
+global cw_vidgets
+global cw_root
+global cw # current color way
+global cw_file # file name for current color way
+global cw_names # list of color way names
+global cw_col # position of color field in cw_win
+global cw_win # window for current cw
+global cw_interface # interface window
+global cw_yoff # y offset from top of interface window
+
+record colorway(table) # note: "table" does not conflict
+ # with the function name. The
+ # field contains a table.
+
+$define ui cw_ui # to avoid conflict with other VIB interfaces
+$define ui_atts cw_ui_atts
+
+$define Pad 10 # name padding
+$define Lheight 30 # line height
+$define Cwidth 100 # color width
+
+procedure cw_init()
+ local atts
+
+ atts := ui_atts()
+
+ put(atts, "posx=10", "posy=10")
+
+ cw_interface := (WOpen !atts) | stop("can't open window")
+ cw_vidgets := ui() # set up vidgets
+
+ cw_yoff := WAttrib(cw_interface, "height") + 45
+
+ cw_root := cw_vidgets["root"]
+ cw_active_vidget := cw_vidgets["active"]
+ cw_active := &null # initially inactive
+
+ return
+
+end
+
+procedure edit_cw()
+ local name
+
+ expose(cw_win)
+
+ repeat {
+ case Event(cw_win) of {
+ &lpress | &mpress | &rpress: {
+ name := cw_names[(&y / Lheight) + 1]
+ if &meta then {
+ delete(cw.table, name)
+ cw_touched := 1
+ win_cw()
+ }
+ else if &x > cw_col then {
+ if ColorDialog("Select color:", cw.table[name]) ==
+ "Cancel" then next
+ cw.table[name] := dialog_value
+ cw_touched := 1
+ win_cw()
+ }
+ else {
+ repeat {
+ if TextDialog("Change name:", , name, 60) ==
+ "Cancel" then break
+ if dialog_value[1] == name then break # no change
+ if member(cw.table, dialog_value[1]) then {
+ Notice("Name " || image(dialog_value[1]) || " exists")
+ next
+ }
+ else {
+ cw.table[dialog_value[1]] := cw.table[name]
+ delete(cw.table, name)
+ win_cw()
+ cw_touched := 1
+ break
+ }
+ }
+ }
+ }
+ "q": return control_mode()
+ }
+ }
+
+end
+
+procedure control_mode()
+
+ VSetState(cw_active_vidget, &null)
+
+ expose(cw_interface)
+
+ return
+
+end
+
+procedure active_cb(vidget, value)
+
+ cw_active := value
+
+ return
+
+end
+
+procedure way_cb(vidget, value)
+
+ case value[1] of {
+ "add @A": add_way()
+ "delete @D": delete_way()
+ }
+
+ return
+
+end
+
+procedure file_cb(vidget, value)
+
+ case value[1] of {
+ "load @L": load_cw()
+ "new @N": new_cw()
+ "pick @P": pick()
+ "quit @Q": quit()
+ "save @S": save_cw()
+ "save as": save_cw_as()
+ }
+
+ return
+
+end
+
+procedure shortcuts(e)
+
+ if &meta then case map(e) of {
+ "a": add_way()
+ "d": delete_way()
+ "e": VSetState(cw_active_vidget, 1)
+ "l": load_cw()
+ "n": new_cw()
+ "p": pick()
+ "q": quit()
+ "s": save_cw()
+ }
+
+ return
+
+end
+
+procedure add_way()
+ local name
+
+ repeat {
+ repeat {
+ if TextDialog("Add color:", "name", , 60) == "Cancel" then return
+ if \cw.table[dialog_value[1]] then {
+ Notice("Name is in use.")
+ next
+ }
+ name := dialog_value[1]
+ if ColorDialog("Choose color:") == "Cancel" then return
+ cw.table[name] := dialog_value
+ win_cw()
+ cw_touched := 1
+ next
+ }
+ }
+
+end
+
+# NOTE: Got error in line comparing dialog_value[i]: &null.
+
+procedure delete_way()
+ local i, x, count
+
+ if ToggleDialog("Delete ways:", cw_names) == "Cancel" then fail
+
+ count := 0
+
+ every i := 1 to *dialog_value do
+ if dialog_value[i] == 1 then {
+ delete(cw.table, cw_names[i])
+ count +:= 1
+ cw_touched := 1
+ }
+
+ if count > 0 then win_cw()
+
+ return
+
+end
+
+procedure load_cw()
+ local input, x
+
+ repeat {
+ if OpenDialog() == "Cancel" then fail
+ input := open(dialog_value) | {
+ Notice("Cannot open file")
+ next
+ }
+ x := xdecodet(input, "colorway") | {
+ Notice("File does not contain color way")
+ close(input)
+ next
+ }
+ cw_file := dialog_value
+ cw := x
+ win_cw()
+ expose(cw_interface)
+ close(input)
+ cw_touched := &null
+ return
+ }
+
+end
+
+
+procedure win_cw()
+ local y, name, height
+
+ WClose(\cw_win)
+
+ cw_col := 2 * Pad # in case the color way is empty
+ cw_names := (keylist(cw.table) | [])
+ cw_col := maxlen(cw_names, TextWidth) + (2 * Pad)
+
+ height := Lheight
+ height <:= Lheight * *cw.table
+
+ cw_win := WOpen("label=" || cw_file, "size=" || (cw_col + Cwidth) ||
+ "," || height, "posx=" || WAttrib(cw_interface, "posx"),
+ "posy=" || WAttrib(cw_interface, "posy") + cw_yoff) |
+ ExitNotice("Cannot open window")
+
+ y := 0
+
+ every name := !cw_names do {
+ Fg(cw_win, "black")
+ CenterString(cw_win, cw_col / 2, y + (Lheight / 2), name)
+ Fg(cw_win, cw.table[name]) | {
+ Notice("Invalid color: " || cw.table[name], "substituting black")
+ Fg(cw_win, "black")
+ }
+ FillRectangle(cw_win, cw_col, y, Cwidth, Lheight)
+ y +:= Lheight
+ }
+
+ if \cw_active then expose(cw_win)
+
+ return
+
+end
+
+procedure new_cw()
+
+ if /cw_touched then {
+ # ask if colorway is to be saved first
+ }
+
+ cw := colorway(table())
+ win_cw()
+ cw_touched := &null
+
+ return
+
+end
+
+procedure save_cw()
+ local output
+
+ repeat {
+ if SaveDialog(, cw_file) == "Cancel" then fail
+ output := open(dialog_value, "w") | {
+ Notice("Cannot open " || dialog_value || " for writing")
+ next
+ }
+ xencodet(cw, output, "colorway") |
+ ExitNotice("Internal inconsistency: color way is corrupt")
+ close(output)
+ cw_touched := &null
+ return
+ }
+
+end
+
+procedure save_cw_as()
+ local output, temp
+
+ repeat {
+ if SaveDialog("Save as:") == "Cancel" then fail
+ if dialog_value == \cw_file then {
+ temp := dialog_value
+ if TextDialog("Overwrite existing file?") == "Cancel" then next
+ dialog_value := temp
+ }
+ output := open(dialog_value, "w") | {
+ Notice("Cannot open " || dialog_value || " for writing")
+ next
+ }
+ xencodet(cw, output, "colorway") |
+ ExitNotice("Internal inconsistency: color way is corrupt")
+ close(output)
+ cw_touched := &null
+ return
+ }
+
+end
+
+procedure quit()
+
+ if \cw_touched then {
+ # ask for save if touched
+ }
+
+ exit()
+
+end
+
+# Utility procedure to let user pick an image file in the current directory.
+
+procedure pick()
+ local plist, ls, input, x
+
+ plist := filelist("*.cw") |
+ return FailNotice("Pick not supported on this platform")
+
+ if *plist = 0 then return FailNotice("No files found.")
+
+ repeat {
+ if SelectDialog("Select color way:", plist, plist[1]) == "Cancel"
+ then fail
+ input := open(dialog_value) | {
+ Notice("Cannot open file")
+ next
+ }
+ x := xdecodet(input, "colorway") | {
+ Notice("File does not contain color way")
+ close(input)
+ next
+ }
+ cw_file := dialog_value
+ cw := x
+ win_cw()
+ expose(cw_interface)
+ close(input)
+ cw_touched := &null
+ return
+ }
+
+ return
+
+end
+
+#===<<vib:begin>>=== modify using vib; do not remove this marker line
+procedure ui_atts()
+ return ["size=134,169", "bg=gray-white"]
+end
+
+procedure ui(win, cbk)
+return vsetup(win, cbk,
+ [":Sizer:::0,0,134,169:",],
+ ["active:Button:regular:1:17,38,49,20:edit",active_cb],
+ ["file:Menu:pull::2,2,36,21:File",file_cb,
+ ["new @N","load @L","pick @P","save @S","save as",
+ "quit @Q"]],
+ ["line:Line:::0,25,200,25:",],
+ ["ways:Menu:pull::40,2,36,21:Ways",way_cb,
+ ["add @A","delete @D"]],
+ )
+end
+#===<<vib:end>>=== end of section maintained by vib
diff --git a/ipl/gprocs/colrlist.icn b/ipl/gprocs/colrlist.icn
new file mode 100644
index 0000000..865cb34
--- /dev/null
+++ b/ipl/gprocs/colrlist.icn
@@ -0,0 +1,63 @@
+############################################################################
+#
+# File: colrlist.icn
+#
+# Subject: Procedures to produce list of colors
+#
+# Author: Ralph E. Griswold
+#
+# Date: November 24, 1996
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# colrlist(f) returns a list of the colors given in a file.
+#
+# colrplte(p) returns a list of colors for the palette p.
+#
+############################################################################
+#
+# Requires: Version 9 graphics
+#
+############################################################################
+#
+# Links: io
+#
+############################################################################
+
+link io
+
+procedure colrlist(f) #: list of colors from file
+ local input, colors, line
+
+ if f === "-" then input := &input
+ else (input := dopen(f)) | fail
+ colors := []
+
+ while line := read(input) do
+ put(colors, ColorValue(line ? tab(upto('\t') | 0)))
+
+ close(input)
+
+ if *colors = 0 then fail
+
+ return colors
+
+end
+
+procedure colrplte(p) #: list of colors from palette
+ local colors
+
+ colors := []
+
+ every put(colors, PaletteColor(p, !PaletteChars(p)))
+
+ if *colors = 0 then fail # invalid palette
+
+ return colors
+
+
+end
diff --git a/ipl/gprocs/colrmodl.icn b/ipl/gprocs/colrmodl.icn
new file mode 100644
index 0000000..3bbd9fa
--- /dev/null
+++ b/ipl/gprocs/colrmodl.icn
@@ -0,0 +1,273 @@
+############################################################################
+#
+# File: colrmodl.icn
+#
+# Subject: Procedures to convert between color models
+#
+# Author: Ralph E. Griswold
+#
+# Date: December 5, 1995
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# These procedures convert between various color models. A color
+# value is represented by a record (see the declarations below).
+#
+# Color values are normalized to a maximum of 1.0.
+#
+############################################################################
+#
+# Acknowledgement: Some of the procedures here are based on information
+# given in Computer Graphics; Principles and Practice, second edition;
+# James D. Foley, Andries van Dam, Steven K. Feiner, and John F. Hughes;
+# Addison-Wesley Publishing Company; 1990.
+#
+############################################################################
+#
+# Note: These procedures have not been extensively tested. Those related
+# to the YIQ model are particularly in question.
+#
+############################################################################
+#
+# Links: matrix, numbers
+#
+############################################################################
+
+link matrix
+link numbers
+
+record rgb(r, g, b)
+record cmy(c, m, y)
+record cmyk(c, m, y, k)
+record yiq(y, i, q)
+record hsv(h, s, v)
+record hls(h, l, s)
+
+procedure rgb2cmy(color)
+
+ return cmy(1.0 - color.r, 1.0 - color.g, 1.0 - color.b)
+
+end
+
+procedure cmy2rgb(color)
+
+ return rgb(1.0 - color.c, 1.0 - color.m, 1.0 - color.y)
+
+end
+
+# Note: The following procedure illustrates the principle of
+# undercolor removal, but for pragmatic reasons, it does not
+# produce acceptable results in process printing.
+
+procedure cmy2cmyk(color)
+ local k
+
+ k := min(color.c, color.m, color.y)
+
+ return cmyk(color.c - k, color.m - k, color.y - k, k)
+
+end
+
+procedure cmyk2cmy(color)
+ local kdelta
+
+ kdelta := color.k / 3
+
+ return cmy(color.c + kdelta, color.m + kdelta, color.y + kdelta)
+
+end
+
+#
+# Note: The RGB specification is assumed to be based on the standard
+# NTSC phosphors. See the reference cited above.
+
+procedure rgb2yiq(color)
+ static M, R, Y
+
+ initial {
+ M := create_matrix(3, 3)
+ M[1, 1] := 0.299
+ M[1, 2] := 0.587
+ M[1, 3] := 0.114
+ M[2, 1] := 0.596
+ M[2, 2] := -0.275
+ M[2, 3] := -0.321
+ M[3, 1] := 0.212
+ M[3, 2] := -0.528
+ M[3, 3] := 0.311
+ }
+
+ R := create_matrix(3, 1)
+ R[1][1] := color.r
+ R[2][1] := color.g
+ R[3][1] := color.b
+
+ Y := mult_matrix(M, R)
+
+ return yiq(Y[1][1], Y[2][1], Y[3][1])
+
+end
+
+procedure yiq2rgb(color)
+ static M, R, Y
+
+ initial {
+ M := create_matrix(3, 3)
+ M[1, 1] := 1.0031
+ M[1, 2] := 0.9548
+ M[1, 3] := 0.6179
+ M[2, 1] := 0.9968
+ M[2, 2] := -0.2707
+ M[2, 3] := -0.6448
+ M[3, 1] := 1.0084
+ M[3, 2] := -1.1005
+ M[3, 3] := 1.6996
+ }
+
+ Y := create_matrix(3, 1)
+ Y[1][1] := color.y
+ Y[2][1] := color.i
+ Y[3][1] := color.q
+
+ R := mult_matrix(M, Y)
+
+ return rgb(R[1][1], R[2][1], R[3][1])
+
+end
+
+procedure rgb2hsv(color)
+ local maximum, minimum, delta, h, s, v
+
+ maximum := max(color.r, color.g, color.b)
+ minimum := min(color.r, color.g, color.b)
+ delta := maximum - minimum
+
+ v := maximum
+
+ if maximum ~= 0 then s := delta / maximum
+ else s := 0
+
+ if s = 0 then h := -1.0 # undefined
+ else {
+ if color.r = maximum then {
+ h := (color.g - color.b) / delta
+ }
+ else if color.g = maximum then {
+ h := 2 + (color.b - color.r) / delta
+ }
+ else if color.b = maximum then {
+ h := 4 + (color.r - color.g) / delta
+ }
+ h := h * 60
+ if h < 0 then h +:= 360.0 # make sure hue is nonnegative
+ }
+
+ return hsv(h, s, v)
+
+end
+
+procedure hsv2rgb(color)
+
+ local h, i, f, p, q, t, s, v
+
+ if color.s = 0 then {
+ if color.h = -1 then {
+ return rgb(color.v, color.v, color.v)
+ }
+ else stop("*** error in HSV to RGB conversion")
+ }
+ else {
+ h := color.h
+ v := color.v
+ s := color.s
+ if h = 360.0 then h := 0.0
+ h /:= 60
+ i := floor(h)
+ f := h - i
+ p := v * (1.0 - s)
+ q := v * (1.0 - s * f)
+ t := v * (1.0 - (s * (1.0 - f)))
+ return case i of {
+ 0: rgb(v, t, p)
+ 1: rgb(q, v, p)
+ 2: rgb(p, v, t)
+ 3: rgb(p, q, v)
+ 4: rgb(t, p, v)
+ 5: rgb(v, p, q)
+ default: stop("*** error in HSV to RGB conversion")
+ }
+ }
+
+end
+
+procedure rgb2hls(color)
+ local maximum, minimum, delta, sum, h, s, l
+
+ maximum := max(color.r, color.b, color.g)
+ minimum := min(color.r, color.b, color.g)
+
+ delta := maximum - minimum
+ sum := maximum + minimum
+ l := sum / 2 # lightness
+
+ if maximum = minimum then { # achromatic case
+ s := 0.0
+ h := -1.0
+ }
+ else {
+ if l <= 0.5 then
+ s := delta / sum
+ else s := delta / (2 - sum)
+
+ if color.r = maximum then
+ h := (color.g - color.r) / delta
+ else if color.g = maximum then
+ h := 2 + (color.b - color.r) / delta
+ else if color.b = maximum then
+ h := 4 + (color.r - color.g) / delta
+ h *:= 60 # convert to degrees
+ if h < 0.0 then h +:= 360.0 # make positive
+
+ return hls(h, l, s)
+ }
+
+end
+
+procedure hls2rgb(color)
+ local h, l, s, m1, m2
+
+ h := color.h
+ l := color.l
+ s := color.s
+
+ if l <= 0.5 then m2 := l * (1 + s)
+ else m2 := l + s - l * s
+ m1 := 2 * l - m2
+ if s = 0 then { # achromatic case
+ if h = -1.0 then return rgb(l, l, l)
+ else stop("*** error in HLS specification")
+ }
+ else {
+ return rgb(
+ color_value(m1, m2, h + 120.0),
+ color_value(m1, m2, h),
+ color_value(m1, m2, h - 120.0)
+ )
+ }
+
+end
+
+procedure color_value(m1, m2, h)
+
+ if h > 360.0 then h -:= 360.0
+ else if h < 0.0 then h +:= 360.0
+ if h < 60.0 then return m1 + (m2 - m1) * h / 60.0
+ else if h < 180.0 then return m2
+ else if h < 240.0 then return m1 + (m2 - m1) * (240.0 - h) / 60.0
+ else return m1
+
+end
diff --git a/ipl/gprocs/colrspec.icn b/ipl/gprocs/colrspec.icn
new file mode 100644
index 0000000..03b4322
--- /dev/null
+++ b/ipl/gprocs/colrspec.icn
@@ -0,0 +1,48 @@
+############################################################################
+#
+# File: colrspec.icn
+#
+# Subject: Procedure to produce VRML color specifications
+#
+# Author: Ralph E. Griswold
+#
+# Date: May 3, 1997
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# Requires: Version 9 graphics
+#
+############################################################################
+#
+# Links: wopen
+#
+############################################################################
+
+link wopen
+
+procedure colrspec(s)
+ local color
+ static max, win
+
+ initial {
+ max := real(2 ^ 16 - 1)
+ WOpen("canvas=hidden")
+ }
+
+ color := ""
+
+ ColorValue(s) ? {
+ every 1 to 3 do {
+ color ||:= (tab(upto(",") | 0) / max) || " "
+ move(1)
+ }
+ return color
+ }
+
+ fail
+
+end
diff --git a/ipl/gprocs/cwutils.icn b/ipl/gprocs/cwutils.icn
new file mode 100644
index 0000000..4a46207
--- /dev/null
+++ b/ipl/gprocs/cwutils.icn
@@ -0,0 +1,161 @@
+############################################################################
+#
+# File: cwutils.icn
+#
+# Subject: Procedures to support color ways
+#
+# Author: Ralph E. Griswold
+#
+# Date: September 2, 1996
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+#
+#
+############################################################################
+#
+# Requires: Version 9 graphics
+#
+############################################################################
+#
+# Links: dialog, interact, tables, wopen, xcode
+#
+############################################################################
+
+link dialog
+link interact
+link tables
+link wopen
+link xcode
+
+# Note: This duplicates declaration in colorway.icn
+
+record colorway(table) # note: "table" does not conflict
+ # with the function name. The
+ # field contains a table.
+
+$define Width 50 # width of image produced in way2image()
+
+
+procedure list2way(L) #: convert list of color specs. to colorway
+ local cw, i, c
+
+ cw := colorway(table())
+
+ i := 0
+
+ every c := !L do {
+ c := ColorValue(c) | "black"
+ cw.table["Color " || right(i +:= 1, 3, "0")] := c
+ }
+
+ return cw
+
+end
+
+# Note: code is identical to procedure above.
+
+procedure file2way(f) #: convert file of color specs. to color way
+ local cw, i, c
+
+ cw := colorway(table())
+
+ i := 0
+
+ every c := !f do {
+ c := ColorValue(c) | "black"
+ cw.table["Color " || (i +:= 1)] := c
+ }
+
+ return cw
+
+end
+
+procedure way2list(cw) #: convert color way to list of colors
+
+ return kvallist(cw.table)
+
+end
+
+procedure way2file(cw) #: convert color way to file of colors
+
+ every write(!kvallist(cw.table))
+
+end
+
+procedure way2image(cw) #: create image from color way
+ local win, y
+
+ win := WOpen("canvas=hidden", "size="|| Width || "," || *cw.table) |
+ return FailNotice("Cannot open window for color way image")
+
+ y := 0
+
+ every Fg(!kvallist(cw.table)) do {
+ DrawLine(win, 0, y, Width - 1, y)
+ y +:= 1
+ }
+
+ snapshot(win)
+
+ WClose(win)
+
+ return
+
+end
+
+procedure saveway(cw, output) #: save color way
+
+ xencodet(cw, output, "colorway") | fail
+
+end
+
+procedure loadway(input) #: load color way
+
+ return xdecodet(input, "colorway") | fail
+
+end
+
+procedure image2way(s, direction) #: convert image to color way
+ local result, width, color, old_color, stripes, w, h
+
+ /direction := "horizontal"
+
+ result := []
+
+ stripes := WOpen("canvas=hidden", "image=" || s) |
+ return FailNotice("Cannot open " || image(s))
+
+ width := 0
+ old_color := ""
+
+ case direction of {
+ "horizontal": {
+ w := 1
+ h := WAttrib(stripes, "height")
+ }
+ "vertical": {
+ w := WAttrib(stripes, "width")
+ h := 1
+ }
+ default: stop("*** invalid direction specification in image2way()")
+ }
+
+ every color := Pixel(stripes, 0, 0, w, h) do {
+ if (color ~== old_color) & (width ~= 0) then {
+ put(result, old_color)
+ width := 0
+ }
+ old_color := color
+ width +:= 1
+ }
+
+ WClose(stripes)
+
+ return list2way(result)
+
+end
diff --git a/ipl/gprocs/decay.icn b/ipl/gprocs/decay.icn
new file mode 100644
index 0000000..414504c
--- /dev/null
+++ b/ipl/gprocs/decay.icn
@@ -0,0 +1,84 @@
+############################################################################
+#
+# File: decay.icn
+#
+# Subject: Procedures for decaying-displays for windows
+#
+# Author: Gregg M. Townsend
+#
+# Date: August 14, 1996
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# These procedures provide a way to draw objects and then have them
+# automatically redrawn (say, in a lighter color) n steps later.
+# A user routine is called to do the actual drawing. If a second
+# call to draw an object comes before its time has expired, the
+# object's counter is reset and the drawing routine is not called.
+#
+# dpipe() initializes a decay pipeline and returns a pipeline object.
+#
+# decay() marks an object, unmarks another, and advances the clock.
+#
+############################################################################
+#
+# dpipe(proc, length, gc1, gc2) -- create a decay pipeline
+#
+# dpipe() initializes a decay pipeline and returns a pipeline object.
+#
+# proc user marking procedure: proc(gc, i) marks entry i using gc
+# length length of the delay pipeline (number of steps)
+# gc1 gc to mark an entry when it becomes active
+# gc2 gc to mark an entry when it decays (becomes inactive)
+#
+# decay(dp, i) -- mark entry i with later decay
+#
+# decay() marks an object, unmarks another, and advances the clock.
+#
+# Using decay pipe dp, entry i (anything but &null) is drawn in an
+# active state, and the oldest entry in the pipe is drawn in an
+# inactive state.
+#
+# Records are kept, though, so that an already-active entry is not
+# redrawn, and a decayed entry reaching the end of the pipe is not
+# drawn as inactive if it was more recently renewed.
+#
+# The decay pipe can be flushed by a sufficient number of
+# decay(dp, &null) calls.
+#
+############################################################################
+#
+# Requires: Version 9 graphics
+#
+############################################################################
+
+record Decay_Rec( # decay pipe record
+ pipe, # queue of active indices
+ tab, # table of activity for each index
+ proc, # marking procedure
+ gc1, # gc to use to turn on
+ gc2) # gc to use to turn off
+
+
+## dpipe(proc, length, gc1, gc2) -- create a decay pipeline
+
+procedure dpipe(proc, length, gc1, gc2) #: create a decay pipeline
+ return Decay_Rec(list(length), table(0), proc, gc1, gc2)
+end
+
+
+## decay(dp, i) -- mark entry i with later decay
+
+procedure decay(dp, i) #: mark entry for later decay
+ local j
+ j := get(dp.pipe)
+ if (dp.tab[\i] +:= 1) = 1 then
+ dp.proc(dp.gc1, i)
+ if (dp.tab[\j] -:= 1) = 0 then
+ dp.proc(dp.gc2, j)
+ put(dp.pipe, i)
+end
diff --git a/ipl/gprocs/dialog.icn b/ipl/gprocs/dialog.icn
new file mode 100644
index 0000000..d10648c
--- /dev/null
+++ b/ipl/gprocs/dialog.icn
@@ -0,0 +1,735 @@
+############################################################################
+#
+# File: dialog.icn
+#
+# Subject: Procedures for dialogs
+#
+# Authors: Ralph E. Griswold and Gregg M. Townsend
+#
+# Date: December 14, 1999
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# This file contains several procedures for posting dialog boxes:
+#
+# AskDialog() -- TextDialog() with only caption and "No" instead of "Cancel"
+# Notice(win, captions) -- notice dialog (a simple text dialog)
+# TextDialog(win, captions, labels, defaults...) -- text dialog
+# ToggleDialog(win, captions, labels, defaults...) -- toggle dialog
+# SelectDialog(win, captions, labels, defaults...) -- selection dialog
+# SaveDialog(win, caption, filename, len) -- save file dialog
+# OpenDialog(win, caption, filename, len) -- open file dialog
+# ColorDialog(win, captions, refcolor, callback, id) -- color dialog
+#
+# In all cases, the first or only caption is used as a dialog box ID,
+# used to remember the dialog box location when it is closed. A later
+# posting using the same ID places the new box at the same location.
+#
+############################################################################
+#
+# ColorDialog(win, captions, color, callback, id) -- display color dialog
+#
+# captions list of dialog box captions; default is ["Select color:"]
+# color reference color setting; none displayed if not supplied
+# callback procedure to call when the setting is changed
+# id arbitrary value passed to callback
+#
+# ColorDialog displays a dialog window with R/G/B and H/S/V sliders for
+# color selection. When the "Okay" or "Cancel" button is pressed,
+# ColorDialog returns the button name, with the ColorValue of the final
+# settings stored in the global variable dialog_value.
+#
+# If a callback procedure is specified, callback(id, k) is called whenever
+# the settings are changed; k is the ColorValue of the settings.
+#
+############################################################################
+#
+# Popup(x, y, w, h, proc, args...) creates a subwindow of the specified
+# size, calls proc(args), and awaits its success or failure. Then, the
+# overlaid area is restored and the result of proc is produced. &window,
+# as seen by proc, is a new binding of win in which dx, dy, and clipping
+# have been set. The usable area begins at (0,0); its size is
+# (WAttrib(win, "clipw"), WAttrib(win, "cliph")). Defaults are:
+# x, y positioned to center the subwindow
+# w, h 250, 150
+# proc Event
+#
+############################################################################
+#
+# Requires: Version 9 graphics
+#
+############################################################################
+#
+# Links: graphics, vbuttons, vdialog, vradio, vslider, vidgets
+#
+############################################################################
+
+link graphics
+link vbuttons
+link vdialog
+link vradio
+link vslider
+link vidgets
+
+$include "vdefns.icn"
+
+global dialog_button
+global dialog_value
+
+$define ButtonWidth 50 # minimum button width
+$define ButtonHeight 30 # button height
+$define FieldWidth 10 # default field width
+$define OpenWidth 50 # default field width for Open/SaveDialog
+
+$define XOff 0 # offset for text vidgets
+$define XOffButton 85 # initial x offset for buttons
+$define XOffIncr 15 # space between buttons
+
+procedure Dialog(win, captions, labels, defaults, widths, buttons, index)
+ Dialog := TextDialog
+ return Dialog(win, captions, labels, defaults, widths, buttons, index)
+end
+
+procedure AskDialog(win, caption)
+
+ return TextDialog(win, caption, , , , , ["Okay", "No"])
+
+end
+
+procedure TextDialog( #: text dialog
+ win, captions, labels, defaults, widths, buttons, index
+ )
+ local maxl, lead, pad, default_button, i, maxw, cwidth, id, label_width
+ local button, maxb, dialog, x, y, button_space, default_width, box_id
+ local temp_win
+ static type
+
+ initial type := proc("type", 0) # protect attractive name
+
+ if type(win) ~== "window" then {
+ win :=: captions :=: labels :=: defaults :=: widths :=: buttons :=:
+ index
+ win := &window
+ /win := temp_win := WOpen("canvas=hidden", "bg=" || VBackground)
+ }
+
+ /captions := []
+ /labels := []
+ /defaults := []
+ /widths := []
+ /buttons := ["Okay", "Cancel"]
+ /index := 1
+
+ if type(captions) ~== "list" then captions := [captions]
+ if type(labels) ~== "list" then labels := [labels]
+ if type(defaults) ~== "list" then defaults := [defaults]
+ if type(widths) ~== "list" then widths := [widths]
+ if type(buttons) ~== "list" then buttons := [buttons]
+
+ default_button := buttons[index] # null if out of bounds
+ default_width := widths[-1] | FieldWidth
+
+ maxl := 0
+ every maxl <:= *(labels | defaults | widths)
+ until *labels = maxl do put(labels, labels[-1] | "")
+ until *defaults = maxl do put(defaults, defaults[-1] | "")
+ until *widths = maxl do put(widths, widths[-1] | 10)
+
+ id := 0
+
+ label_width := 0
+ every label_width <:= TextWidth(win, !labels)
+ if label_width > 0 then label_width +:= 15
+
+ maxb := 0
+ every maxb <:= TextWidth(win, !buttons)
+ maxb +:= 10
+ maxb <:= ButtonWidth
+
+ lead := WAttrib(win, "leading")
+ pad := 2 * lead
+ cwidth := WAttrib(win, "fwidth")
+
+ dialog := Vdialog(win, pad, pad)
+
+ maxw := 0
+ every maxw <:= TextWidth(win, !captions)
+
+ y := -lead
+
+ every VInsert(dialog, Vmessage(win, !captions), 0, y +:= lead)
+ every i := 1 to maxl do {
+ y +:= pad
+ if *labels[i] > 0 then
+ VInsert(dialog, Vmessage(win, labels[i]), 0, y)
+ VRegister(dialog, Vtext(win, "", , id +:= 1,
+ widths[i]), label_width, y)
+ maxw <:= label_width + widths[i] * cwidth
+ }
+
+ y +:= (3 * pad) / 2
+
+ button_space := maxb * *buttons + XOffIncr * (*buttons - 1)
+ maxw <:= button_space
+
+ x := ((maxw - button_space) / 2)
+
+ every button := !buttons do {
+ VInsert(dialog, Vbutton(win, button, dialog_cb, V_OK, , maxb,
+ ButtonHeight), x, y)
+ x +:= maxb + XOffIncr
+ }
+
+ VFormat(dialog)
+
+ box_id := captions[1] | "TextDialog"
+ dialog_value := VOpenDialog(dialog, , box_id, defaults, default_button)
+
+ WClose(\temp_win)
+
+ return dialog_button
+
+end
+
+procedure ToggleDialog( #: toggle dialog
+ win, captions, labels, defaults, buttons, index
+ )
+ local maxl, lead, pad, default_button, i, maxw, cwidth, id, label_width
+ local button, maxb, dialog, x, y, button_space, default_width, box_id
+ local temp_win
+ static type
+
+ initial type := proc("type", 0) # protect attractive name
+
+ if type(win) ~== "window" then {
+ win :=: captions :=: labels :=: defaults :=: buttons :=: index
+ win := &window
+ /win := temp_win := WOpen("canvas=hidden", "bg=" || VBackground)
+ }
+
+ /captions := []
+ /labels := []
+ /defaults := []
+ /buttons := ["Okay", "Cancel"]
+ /index := 1
+
+ if type(captions) ~== "list" then captions := [captions]
+ if type(labels) ~== "list" then labels := [labels]
+ if type(defaults) ~== "list" then defaults := [defaults]
+ if type(buttons) ~== "list" then buttons := [buttons]
+
+ default_button := buttons[index] # null if out of bounds
+
+ maxl := 0
+ every maxl <:= *(labels | defaults)
+ every maxl <:= *labels
+ until *labels = maxl do put(labels, labels[-1] | "")
+ until *defaults = maxl do put(defaults, defaults[-1] | &null)
+
+ id := 0
+
+ label_width := 0
+ every label_width <:= TextWidth(win, !labels)
+ if label_width > 0 then label_width +:= 30
+
+ maxb := 0
+ every maxb <:= TextWidth(win, !buttons)
+ maxb +:= 10
+ maxb <:= ButtonWidth
+
+ lead := WAttrib(win, "leading")
+ pad := 2 * lead
+ cwidth := WAttrib(win, "fwidth")
+
+ dialog := Vdialog(win, pad, pad)
+
+ maxw := 0
+ every maxw <:= TextWidth(win, !captions)
+
+ y := -lead
+
+ every VInsert(dialog, Vmessage(win, !captions), 0, y +:= lead)
+ every i := 1 to maxl do {
+ y +:= pad
+ VRegister(dialog, Vtoggle(win, labels[i], , id +:= 1, V_CHECK_NO,
+ label_width), 0, y)
+ maxw <:= label_width
+ }
+
+ y +:= (3 * pad) / 2
+
+ button_space := maxb * *buttons + XOffIncr * (*buttons - 1)
+ maxw <:= button_space
+
+ x := ((maxw - button_space) / 2)
+
+ every button := !buttons do {
+ VInsert(dialog, Vbutton(win, button, dialog_cb, V_OK, , maxb,
+ ButtonHeight), x, y)
+ x +:= maxb + XOffIncr
+ }
+
+ VFormat(dialog)
+
+ box_id := captions[1] | "ToggleDialog"
+ dialog_value := VOpenDialog(dialog, , box_id, defaults, default_button)
+
+ WClose(\temp_win)
+
+ return dialog_button
+
+end
+
+procedure SelectDialog( #: selection dialog
+ win, captions, labels, deflt, buttons, index
+ )
+ local maxl, lead, pad, default_button, i, maxw, cwidth, label_width
+ local button, maxb, dialog, x, y, button_space, box_id
+ local temp_win
+ static type
+
+ initial type := proc("type", 0) # protect attractive name
+
+ if type(win) ~== "window" then {
+ win :=: captions :=: labels :=: deflt :=: buttons :=: index
+ win := &window
+ /win := temp_win := WOpen("canvas=hidden", "bg=" || VBackground)
+ }
+
+ /captions := []
+ /labels := []
+ /buttons := ["Okay", "Cancel"]
+ /index := 1
+
+ if type(captions) ~== "list" then captions := [captions]
+ if type(labels) ~== "list" then labels := [labels]
+ if type(buttons) ~== "list" then buttons := [buttons]
+
+ default_button := buttons[index] # null if out of bounds
+
+ maxl := 0
+ every maxl <:= *labels
+ until *labels = maxl do put(labels, labels[-1] | "")
+
+ label_width := 0
+ every label_width <:= TextWidth(win, !labels)
+ if label_width > 0 then label_width +:= 15
+
+ maxb := 0
+ every maxb <:= TextWidth(win, !buttons)
+ maxb +:= 10
+ maxb <:= ButtonWidth
+
+ lead := WAttrib(win, "leading")
+ pad := 2 * lead
+ cwidth := WAttrib(win, "fwidth")
+
+ dialog := Vdialog(win, pad, pad)
+
+ maxw := 0
+ every maxw <:= TextWidth(win, !captions)
+
+ y := -lead
+
+ every VInsert(dialog, Vmessage(win, !captions), 0, y +:= lead)
+ y +:= 2 * lead
+ VRegister(dialog, Vvert_radio_buttons(win, labels, , 1, V_DIAMOND_NO), 0, y)
+
+ y +:= integer(0.83 * (pad * (*labels - 1)) + 1.5 * pad)
+
+ button_space := maxb * *buttons + XOffIncr * (*buttons - 1)
+ maxw <:= button_space
+
+ x := ((maxw - button_space) / 2)
+
+ every button := !buttons do {
+ VInsert(dialog, Vbutton(win, button, dialog_cb, V_OK, , maxb,
+ ButtonHeight), x, y)
+ x +:= maxb + XOffIncr
+ }
+
+ VFormat(dialog)
+
+ box_id := captions[1] | "ToggleDialog"
+ dialog_value := VOpenDialog(dialog, , box_id, [deflt], default_button)[1]
+
+ WClose(\temp_win)
+
+ return dialog_button
+
+end
+
+procedure Notice(captions[]) #: notice dialog
+ local win, temp_win
+ static type
+
+ initial type := proc("type", 0) # protect attractive name
+
+ if type(captions[1]) == "window" then
+ win := get(captions)
+ else {
+ win := &window
+ /win := temp_win := WOpen("canvas=hidden", "bg=" || VBackground)
+ }
+
+ TextDialog(win, captions, , , , "Okay")
+
+ dialog_value := &null
+
+ WClose(\temp_win)
+
+ return dialog_button
+
+end
+
+procedure SaveDialog(win, caption, filename, len) #: save dialog
+ local temp_win
+ static type
+
+ initial type := proc("type", 0) # protect attractive name
+
+ if type(win) ~== "window" then {
+ win :=: caption :=: filename :=: len
+ win := &window
+ /win := temp_win := WOpen("canvas=hidden", "bg=" || VBackground)
+ }
+
+ /caption := "Save:"
+ /filename := ""
+ /len := OpenWidth
+
+ TextDialog(win, caption, , filename, len, ["Yes", "No", "Cancel"])
+
+ dialog_value := dialog_value[1]
+
+ WClose(\temp_win)
+
+ return dialog_button
+
+end
+
+procedure OpenDialog(win, caption, filename, len) #: open dialog
+ local temp_win
+ static type
+
+ initial type := proc("type", 0) # protect attractive name
+
+ if type(win) ~== "window" then {
+ win :=: caption :=: filename :=: len
+ win := &window
+ /win := temp_win := WOpen("canvas=hidden", "bg=" || VBackground)
+ }
+
+ /caption := "Open:"
+ /filename := ""
+ /len := OpenWidth
+
+ TextDialog(win, caption, , filename, len)
+
+ dialog_value := dialog_value[1]
+
+ WClose(\temp_win)
+
+ return dialog_button
+
+end
+
+procedure dialog_cb(vidget, s)
+
+ dialog_button := vidget.s
+
+ return
+
+end
+
+# ColorDialog(win, captions, color, callback, id) -- display color dialog
+
+record cdl_rec(rect, orgcolor, refcolor, mutable, callback, id,
+ r, g, b, h, s, v, rv, gv, bv, hv, sv, vv, fg, fillargs, dialog, nc)
+
+global cdl_data # data for current color dialog
+
+$define PickerWidth 300 # overall color picker width
+$define SliderHeight 200 # height of a slider
+$define SliderWidth 15 # width of one slider
+$define SliderPad 5 # distance between sliders
+$define MaxStaticCol 200 # maximum colors before recycling
+
+procedure ColorDialog( #: color dialog
+ win, captions, refcolor, callback, id
+ )
+ local x1, x2, dx, y, bw, lead, pad, dialog, box_id, temp_win
+ static type
+
+ initial type := proc("type", 0) # protect attractive name
+
+ if type(win) ~== "window" then {
+ win :=: captions :=: refcolor :=: callback :=: id
+ win := &window
+ /win := temp_win := WOpen("canvas=hidden", "bg=" || VBackground)
+ }
+
+ /captions := "Select color:"
+ if type(captions) ~== "list" then captions := [captions]
+
+ cdl_data := cdl_rec()
+ cdl_data.callback := callback
+ cdl_data.id := id
+ cdl_data.refcolor := refcolor
+ cdl_data.orgcolor := ColorValue(win, \refcolor | Fg(win) | "gray")
+
+ cdl_data.orgcolor ? {
+ cdl_data.r := integer(tab(many(&digits)))
+ move(1)
+ cdl_data.g := integer(tab(many(&digits)))
+ move(1)
+ cdl_data.b := integer(tab(many(&digits)))
+ }
+ HSV(cdl_data.orgcolor) ? {
+ cdl_data.h := integer(tab(many(&digits)))
+ move(1)
+ cdl_data.s := integer(tab(many(&digits)))
+ move(1)
+ cdl_data.v := integer(tab(many(&digits)))
+ }
+
+ lead := WAttrib(win, "leading")
+ pad := 2 * lead
+
+ y := -lead
+
+ dialog := Vdialog(win, pad, pad, cdl_init)
+ every VInsert(dialog, Vmessage(win, !captions), 0, y +:= lead)
+
+ dx := SliderWidth + SliderPad
+ x1 := 0 - dx
+ x2 := PickerWidth + SliderPad
+ y +:= pad
+
+ cdl_data.dialog := dialog
+ cdl_data.rv := cdl_slider(dialog, "r", x1 +:= dx, y, 0, 65535, cdl_data.r)
+ cdl_data.gv := cdl_slider(dialog, "g", x1 +:= dx, y, 0, 65535, cdl_data.g)
+ cdl_data.bv := cdl_slider(dialog, "b", x1 +:= dx, y, 0, 65535, cdl_data.b)
+ cdl_data.vv := cdl_slider(dialog, "v", x2 -:= dx, y, 0, 100, cdl_data.v)
+ cdl_data.sv := cdl_slider(dialog, "s", x2 -:= dx, y, 0, 100, cdl_data.s)
+ cdl_data.hv := cdl_slider(dialog, "h", x2 -:= dx, y, 0, 360, cdl_data.h)
+
+ x1 +:= dx + SliderPad
+ x2 -:= 2 * SliderPad
+ cdl_data.rect := Vpane(win, , , "sunken",
+ x2 - x1, SliderHeight - 3 * lead - SliderPad)
+ VInsert(dialog, cdl_data.rect, x1, y)
+
+ y +:= SliderHeight + pad
+ bw := TextWidth(win, "Cancel") + 10
+ VInsert(dialog, Vbutton(win, "Okay", cdl_exit, V_OK, ,
+ bw, ButtonHeight), PickerWidth / 2 - bw - 10, y)
+ VInsert(dialog, Vbutton(win, "Cancel", cdl_exit, V_OK, ,
+ bw, ButtonHeight), PickerWidth / 2 + 10, y)
+
+ VFormat(dialog)
+ box_id := captions[1] | "ColorDialog"
+ VOpenDialog(dialog, , box_id, , "Okay")
+
+ dialog_value := cdl_data.r || "," || cdl_data.g || "," || cdl_data.b
+
+ WClose(\temp_win)
+
+ return dialog_button
+
+end
+
+procedure cdl_slider(dialog, id, x, y, low, high, init) # place a slider
+ local v
+
+ v := Vvert_slider(dialog.win, cdl_setval, id,
+ SliderHeight, SliderWidth, low, high, init)
+ VInsert(dialog, v, x, y)
+ return v
+end
+
+procedure cdl_init() # initialize non-vidget part of dialog
+ local r
+
+ r := cdl_data.rect
+ cdl_data.fg := Fg(r.win)
+ cdl_data.fillargs := [r.win, r.ux, r.uy, r.uw, r.uh]
+ if cdl_data.mutable := NewColor(cdl_data.rect.win, cdl_data.orgcolor) then {
+ Fg(r.win, cdl_data.mutable)
+ FillRectangle ! cdl_data.fillargs
+ }
+ else
+ cdl_data.nc := 0
+ if Fg(r.win, \cdl_data.refcolor) then {
+ cdl_data.fillargs[-1] -:= r.uh / 8
+ FillRectangle(r.win, r.ux, r.uy + r.uh, r.uw, -r.uh / 8)
+ }
+ Fg(r.win, cdl_data.fg)
+ cdl_sethsv()
+ return
+end
+
+procedure cdl_exit(vidget, s) # save position and button name on exit
+ dialog_button := vidget.s
+ FreeColor(cdl_data.rect.win, \cdl_data.mutable)
+ EraseArea(cdl_data.rect.win)
+ return
+end
+
+procedure cdl_setval(v, x) # set value in response to slider motion
+ static recurse
+
+ if /recurse then { # if not a recursive call
+ recurse := 1 # note to prevent recursion
+ case v.id of {
+ "r": { cdl_data.r := x; cdl_sethsv(); }
+ "g": { cdl_data.g := x; cdl_sethsv(); }
+ "b": { cdl_data.b := x; cdl_sethsv(); }
+ "h": { cdl_data.h := x; cdl_setrgb(); }
+ "s": { cdl_data.s := x; cdl_setrgb(); }
+ "v": { cdl_data.v := x; cdl_setrgb(); }
+ }
+ recurse := &null
+ }
+ return
+end
+
+procedure cdl_sethsv() # set h/s/v values from r/g/b
+ local c
+
+ HSV(c := cdl_data.r || "," || cdl_data.g || "," || cdl_data.b) ? {
+ VSetState(cdl_data.hv, cdl_data.h := integer(tab(many(&digits))))
+ move(1)
+ VSetState(cdl_data.sv, cdl_data.s := integer(tab(many(&digits))))
+ move(1)
+ VSetState(cdl_data.vv, cdl_data.v := integer(tab(many(&digits))))
+ }
+ cdl_setcolor(c)
+ return
+end
+
+procedure cdl_setrgb() # set r/g/b values from h/s/v
+ local c
+
+ (c := HSVValue(cdl_data.h || "/" || cdl_data.s || "/" || cdl_data.v)) ? {
+ VSetState(cdl_data.rv, cdl_data.r := integer(tab(many(&digits))))
+ move(1)
+ VSetState(cdl_data.gv, cdl_data.g := integer(tab(many(&digits))))
+ move(1)
+ VSetState(cdl_data.bv, cdl_data.b := integer(tab(many(&digits))))
+ }
+ cdl_setcolor(c)
+ return
+end
+
+procedure cdl_setcolor(c) # display new color and invoke callback
+ local r, win, x1, x2, y, dy
+
+ r := cdl_data.rect
+ win := r.win
+ if \cdl_data.mutable then
+ Color(win, cdl_data.mutable, c) # set the mutable color
+ else {
+ if ((cdl_data.nc +:= 1) > MaxStaticCol) | (not Fg(win, c)) then {
+ EraseArea(win) # free allocated colors
+ VDraw(cdl_data.dialog) # redraw vidget
+ if Fg(r.win, \cdl_data.refcolor) then # redraw reference color
+ FillRectangle(r.win, r.ux, r.uy + r.uh, r.uw, -r.uh / 8)
+ Fg(win, c) # set new foreground
+ cdl_data.nc := 1
+ }
+ FillRectangle ! cdl_data.fillargs
+ Fg(win, cdl_data.fg)
+ }
+
+ x1 := cdl_data.rect.ax
+ x2 := x1 + cdl_data.rect.aw
+ y := cdl_data.rect.ay + cdl_data.rect.ah + SliderPad
+ dy := WAttrib(win, "leading")
+
+ EraseArea(win, x1, y, x2 - x1, 3 * dy) # erase and redraw text area
+ y +:= WAttrib(win, "ascent")
+ x2 -:= TextWidth(win, "h: 360")
+
+ DrawString(win, x1, y, "r: " || right(cdl_data.r, 5))
+ DrawString(win, x2, y, "h: " || right(cdl_data.h, 3))
+ y +:= dy
+ DrawString(win, x1, y, "g: " || right(cdl_data.g, 5))
+ DrawString(win, x2, y, "s: " || right(cdl_data.s, 3))
+ y +:= dy
+ DrawString(win, x1, y, "b: " || right(cdl_data.b, 5))
+ DrawString(win, x2, y, "v: " || right(cdl_data.v, 3))
+
+ (\cdl_data.callback)(cdl_data.id, c) # invoke user callback, if any
+ return
+end
+
+# Popup(win, x, y, w, h, proc, args[])
+
+$define BorderWidth 4
+$define ShadowWidth 4
+
+procedure Popup(args[]) #: create popup subwindow
+ local win, x, y, w, h, xx, yy, ww, hh, dx, dy, s, proc, retv, ampwin, save
+
+ # Get parameters.
+ PushWin(args)
+ win := get(args)
+ x := get(args); integer(x) | runerr(101, \x)
+ y := get(args); integer(y) | runerr(101, \y)
+ w := \get(args) | 250; integer(w) | runerr(101, w)
+ h := \get(args) | 150; integer(h) | runerr(101, h)
+ proc := \get(args) | Event
+
+ # Handle defaults
+ dx := WAttrib(win, "dx")
+ dy := WAttrib(win, "dy")
+ w >:= WAttrib(win, "width") # limit to size of full win
+ h >:= WAttrib(win, "height")
+ /x := (WAttrib(win, "width") - w) / 2 - dx # center the subwindow
+ /y := (WAttrib(win, "height") - h) / 2 - dy
+
+ # Adjust subwindow configuration parameters.
+ xx := x - BorderWidth
+ yy := y - BorderWidth
+ ww := w + 2 * BorderWidth + ShadowWidth
+ hh := h + 2 * BorderWidth + ShadowWidth
+
+ # Save original window contents.
+ save := ScratchCanvas(ww, hh, "__Popup__") |
+ stop("can't get ScratchCanvas in Popup()")
+ CopyArea(win, save, xx, yy, ww, hh)
+
+ # Save &window and create subwindow.
+ ampwin := &window
+ &window := Clone(win) | stop("can't Clone in Popup()")
+ WAttrib("drawop=copy", "fillstyle=solid", "linestyle=solid", "linewidth=1",
+ "dx=" || (dx + x), "dy=" || (dy + y))
+ DrawRectangle(-BorderWidth, -BorderWidth, ww-ShadowWidth-1, hh-ShadowWidth-1)
+ BevelRectangle(-BorderWidth + 1, -BorderWidth + 1,
+ ww - ShadowWidth - 2, hh - ShadowWidth - 2, BorderWidth)
+ FillRectangle(-BorderWidth + ShadowWidth, h + BorderWidth,
+ ww - ShadowWidth, ShadowWidth)
+ FillRectangle(w + BorderWidth, -BorderWidth + ShadowWidth,
+ ShadowWidth, hh - ShadowWidth)
+ Clip(0, 0, w, h)
+ EraseArea()
+
+ # Flush any previously entered events on the window
+ while *Pending(win) > 0 do
+ Event(win)
+
+ # Call proc; save result, if any, or use args as flag if none.
+ retv := (proc ! args) | args
+
+ # Restore window and return result. Use &window to ensure drawop=copy.
+ Clip(-BorderWidth, -BorderWidth, ww, hh)
+ CopyArea(save, &window, 0, 0, ww, hh, -BorderWidth, -BorderWidth)
+ EraseArea(save)
+ &window := ampwin
+ return args ~=== retv
+end
diff --git a/ipl/gprocs/dialogs.icn b/ipl/gprocs/dialogs.icn
new file mode 100644
index 0000000..d916389
--- /dev/null
+++ b/ipl/gprocs/dialogs.icn
@@ -0,0 +1,21 @@
+############################################################################
+#
+# File: dialogs.icn
+#
+# Subject: Declaration to link to dialog
+#
+# Author: Gregg M. Townsend
+#
+# Date: November 4, 1995
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# compatibility file
+#
+############################################################################
+
+link dialog
diff --git a/ipl/gprocs/distance.icn b/ipl/gprocs/distance.icn
new file mode 100644
index 0000000..60fe238
--- /dev/null
+++ b/ipl/gprocs/distance.icn
@@ -0,0 +1,31 @@
+############################################################################
+#
+# File: distance.icn
+#
+# Subject: Procedure to compute distance in n-dimensions
+#
+# Author: Ralph E. Griswold
+#
+# Date: January 3, 1994
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# distance(d1, d2, d3, ...) returns the distance between points in n-space
+# distances d1, d2, d3, ... from the origin.
+#
+############################################################################
+
+procedure distance(d[])
+ local sum
+
+ sum := 0
+
+ every sum +:= !d ^ 2
+
+ return sqrt(sum)
+
+end
diff --git a/ipl/gprocs/drag.icn b/ipl/gprocs/drag.icn
new file mode 100644
index 0000000..67d4602
--- /dev/null
+++ b/ipl/gprocs/drag.icn
@@ -0,0 +1,169 @@
+############################################################################
+#
+# File: drag.icn
+#
+# Subject: Procedures for dragging rectangles
+#
+# Author: Gregg M. Townsend
+#
+# Date: August 21, 1998
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# These procedures drag rectangular objects in a window.
+#
+# Drag(x, y, w, h) provides an opaque move.
+#
+# DragOutline(x, y, w, h) drags only the outline.
+#
+############################################################################
+#
+# Drag(x, y, w, h) lets the user move a rectangular area using the
+# mouse. Called when a mouse button is pressed, Drag() handles all
+# subsequent events until a mouse button is released. As the mouse
+# moves, the rectangular area originally at (x,y,w,h) follows it
+# across the screen; vacated pixels at the original location are
+# filled with the background color. The rectangle cannot be dragged
+# off-screen or outside the clipping region. When the mouse button
+# is released, Drag() sets &x and &y to the upper-left corner of the
+# new location and returns.
+#
+# DragOutline(x, y, w, h) lets the user move a reverse-mode rectangle
+# using the mouse. Called when a mouse button is pressed, DragOutline
+# draws a reverse-mode rectangle inside the limits of the rectangle
+# (x,y,w,h) and handles all subsequent events until a mouse button is
+# released. As the mouse moves, the rectangle follows it. When the
+# mouse button is released, the rectangle disappears, and DragOutline
+# sets &x and &y to the upper-left corner of the new location. It is
+# up to the calling program to update the display as necessary.
+#
+############################################################################
+#
+# Requires: Version 9 graphics
+#
+############################################################################
+
+link graphics
+
+
+# Drag(x, y, w, h) -- opaque drag
+
+procedure Drag(win, x, y, w, h) #: opaque rectangle drag
+ local dx, dy, x0, y0, x1, y1
+ local behind, xoff, yoff, xnew, ynew, xshift, yshift
+
+ if type(win) ~== "window" then
+ return Drag((\&window | runerr(140)), win, x, y, w)
+
+ if w < 0 then
+ x -:= (w := -w)
+ if h < 0 then
+ y -:= (h := -h)
+
+ dx := WAttrib(win, "dx")
+ dy := WAttrib(win, "dy")
+
+ x0 := -dx # set limits due to window size
+ y0 := -dy
+ x1 := WAttrib(win, "width") - dx - w
+ y1 := WAttrib(win, "height") - dy - h
+
+ x0 <:= \WAttrib(win, "clipx") # adjust limits for clipping
+ y0 <:= \WAttrib(win, "clipy")
+ x1 >:= \WAttrib(win, "clipx") + \WAttrib(win, "clipw") - w
+ y1 >:= \WAttrib(win, "clipy") + \WAttrib(win, "cliph") - h
+
+ behind := ScratchCanvas(win, , , "__Drag__") |
+ stop("can't get ScratchCanvas in Drag()")
+ CopyArea(win, behind, -dx, -dy)
+ Bg(behind, Bg(win))
+ EraseArea(behind, x + dx, y + dy, w, h)
+
+ xoff := x - &x
+ yoff := y - &y
+
+ until Event(win) === (&lrelease | &mrelease | &rrelease) do {
+
+ # move the rectangle
+ xnew := &x + xoff
+ ynew := &y + yoff
+ xnew <:= x0
+ ynew <:= y0
+ xnew >:= x1
+ ynew >:= y1
+ CopyArea(win, x, y, w, h, xnew, ynew)
+
+ # repaint the area exposed by its movement
+ xshift := xnew - x
+ yshift := ynew - y
+
+ if abs(xshift) >= w | abs(yshift) >= h then {
+
+ # completely disjoint from new location
+ CopyArea(behind, win, x + dx, y + dy, w, h, x, y)
+ }
+
+ else {
+
+ # new area overlaps old
+ if xshift > 0 then
+ CopyArea(behind, win, x + dx, y + dy, xshift, h, x, y)
+ else if xshift < 0 then
+ CopyArea(behind, win,
+ x + dx + w + xshift, y + dy, -xshift, h, x + w + xshift, y)
+ if yshift > 0 then
+ CopyArea(behind, win, x + dx, y + dy, w, yshift, x, y)
+ else if yshift < 0 then
+ CopyArea(behind, win,
+ x + dx, y + dy + h + yshift, w, -yshift, x, y + h + yshift)
+ }
+
+ x := xnew
+ y := ynew
+ }
+
+ EraseArea(behind)
+ &x := x
+ &y := y
+ return win
+end
+
+
+# DragOutline(x, y, w, h) -- outlined drag
+
+procedure DragOutline(win, x, y, w, h) #: outlined rectangle drag
+ local wrev, xoff, yoff
+
+ if type(win) ~== "window" then
+ return DragOutline((\&window | runerr(140)), win, x, y, w)
+
+ if w < 0 then
+ x -:= (w := -w)
+ if h < 0 then
+ y -:= (h := -h)
+
+ wrev := Clone(win, "drawop=reverse")
+ xoff := x - &x
+ yoff := y - &y
+
+ w -:= 1 # adjust Draw/Fill inconsistency
+ h -:= 1
+
+ DrawRectangle(wrev, x, y, w, h) # draw initial rectangle
+ until Event(wrev) === (&lrelease | &mrelease | &rrelease) do {
+ DrawRectangle(wrev, x, y, w, h) # erase old rectangle
+ x := &x + xoff
+ y := &y + yoff
+ DrawRectangle(wrev, x, y, w, h) # draw new rectangle
+ }
+ DrawRectangle(wrev, x, y, w, h) # erase final rectangle
+ Uncouple(wrev)
+
+ &x := x
+ &y := y
+ return win
+end
diff --git a/ipl/gprocs/drawcard.icn b/ipl/gprocs/drawcard.icn
new file mode 100644
index 0000000..341aeaa
--- /dev/null
+++ b/ipl/gprocs/drawcard.icn
@@ -0,0 +1,194 @@
+############################################################################
+#
+# File: drawcard.icn
+#
+# Subject: Procedure to draw a playing card
+#
+# Author: Gregg M. Townsend
+#
+# Date: June 23, 2000
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# drawcard(win, x, y, c) draws the playing card labeled <c> with its
+# upper left corner at (x,y). The card size is fixed at 80w x 124h.
+#
+# Card labelings are those used in the examples in the "Mappings and
+# Labelings" chapter of the Icon book (pp 205-207, 2/e).
+#
+# label: ABCDEFGHIJKLM NOPQRSTUVWXYZ abcdefghijklm nopqrstuvwxyz
+# rank: A23456789TJQK A23456789TJQK A23456789TJQK A23456789TJQK
+# suit: clubs........ diamonds..... hearts....... spades.......
+#
+# If the label is unrecognized, the back of a card is drawn.
+# "-" is suggested as a conventional label for a card back.
+#
+############################################################################
+#
+# Requires: Version 9 graphics
+#
+############################################################################
+#
+# Links: cardbits, graphics
+#
+############################################################################
+
+link cardbits
+link graphics
+
+procedure drawcard(win, x, y, label)
+ static cmap, gc, bk, plist, deck
+ local ysuit, yrank, r, s, i, l, dx, dy
+
+ if type(win) ~== "window" then {
+ win :=: x :=: y :=: label
+ win := &window
+ }
+ if /gc then {
+ # funny order of card deck is for conversion to ranks below
+ deck := "ABCDEFGHIJKLMnopqrstuvwxyzabcdefghijklmNOPQRSTUVWXYZ"
+ cmap := cardmap() | stop("can't initialize card fragments")
+ gc := Clone(win, "fg=black", "bg=white")
+ bk := Clone(gc)
+ Pattern(bk, "32,#_
+ 04444044_
+ 0A08000A_
+ 11101011_
+ 0A00080A_
+ 44004404_
+ 8000A000_
+ 10011001_
+ A0002000_
+ 40044404_
+ 000A0A02_
+ 01111101_
+ 020A0A00_
+ 44440440_
+ 00A00020_
+ 11100111_
+ 008000A0_
+ 40440444_
+ 000A0A08_
+ 10111110_
+ 080A0A00_
+ 44044400_
+ A0008000_
+ 10011001_
+ 2000A000_
+ 44044004_
+ 0A02000A_
+ 11010111_
+ 0A00020A_
+ 04404444_
+ 002000A0_
+ 01111110_
+ 00A00080")
+ WAttrib(bk, "fillstyle=textured")
+ if WAttrib(bk, "depth") > 1 then
+ WAttrib(bk, "fg=dark red-yellow", "bg=light red-yellow")
+ plist := [
+ [0, 0], # A
+ [0, 39], # 2
+ [0, 39, 0, 0], # 3
+ [16, 39], # 4
+ [16, 39, 0, 0], # 5
+ [16, 0, 16, 39], # 6
+ [16, 0, 16, 39, 0, -20], # 7
+ [16, 0, 16, 39, 0, 20], # 8
+ [16, 13, 16, 39, 0, 0], # 9
+ [16, 13, 16, 39, 0, 26] # 10
+ ]
+ }
+
+ if (i := (deck ? find(label)) - 1) then {
+ r := i % 13 + 1 # 1 to 13 for A,2,...,9,10,J,Q,K
+ s := i / 13 + 1 # 1=heart, 2=diamond, 3=spade, 4=club
+ }
+ else {
+ # unrecognized; draw card back
+ DrawRectangle(gc, x, y, 80-1, 124-1)
+ FillRectangle(bk, x+1, y+1, 80-2, 124-2)
+ return
+ }
+
+ ClearOutline(gc, x, y, 80-1, 124-1)
+ ysuit := 94 * (s-1)
+ yrank := (if s <= 2 then 404 else 376)
+
+ CopyArea(cmap, gc, 9 * (r-1), yrank, 9, 14, x+4, y+6) # rank
+ CopyArea(cmap, gc, 9 * (r-1), yrank+14, 9, 14, x+67, y+104) # inverted rank
+ CopyArea(cmap, gc, 148, ysuit+40, 9, 14, x+4, y+22) # suit
+ CopyArea(cmap, gc, 148, ysuit+54, 9, 14, x+67, y+88) # inverted suit
+
+ if r > 10 then
+ CopyArea(cmap, gc, 48 * (r-11), ysuit, 48, 94, x+16, y+15) # faces
+ else if (r = 1) & (s = 4) then
+ CopyArea(cmap, gc, 117, 376, 43, 56, x+18, y+34) # ace of spaces
+ else {
+ l := plist[r]
+ i := 0
+ while (dx := l[i +:= 1]) & (dy := l[i +:= 1]) do {
+ if dy = 0 then {
+ # pip in center row; reflect horizontally if dx positive
+ CopyArea(cmap, gc, 144, ysuit, 16, 20, x + dx + 32, y + 52)
+ if dx > 0 then
+ CopyArea(cmap, gc, 144, ysuit, 16, 20, x - dx + 32, y + 52)
+ }
+ else if dx = 0 then {
+ # pip in center column; reflect vertically if dy positive
+ if dy > 0 then {
+ CopyArea(cmap, gc, 144, ysuit + 20, 16, 20, x + 32, y + dy + 52)
+ CopyArea(cmap, gc, 144, ysuit, 16, 20, x + 32, y - dy + 52)
+ }
+ else
+ CopyArea(cmap, gc, 144, ysuit, 16, 20, x + 32, y + dy + 52)
+ }
+ else {
+ # all other positions are 4-way symmetric
+ CopyArea(cmap, gc, 144, ysuit+20, 16, 20, x + dx + 32, y + dy + 52)
+ CopyArea(cmap, gc, 144, ysuit+20, 16, 20, x - dx + 32, y + dy + 52)
+ CopyArea(cmap, gc, 144, ysuit, 16, 20, x + dx + 32, y - dy + 52)
+ CopyArea(cmap, gc, 144, ysuit, 16, 20, x - dx + 32, y - dy + 52)
+ }
+ }
+ }
+ return
+end
+
+# cardmap() -- create and load card bitmap
+#
+# The bitmap is in a separate source file cardbits.icn due to its size.
+# It is represented there as a bilevel image.
+
+procedure cardmap() # create and load card bitmap
+ local ims, cmap, rmap
+
+ ims := cardbits()
+ cmap := open("cardbits", "g", "canvas=hidden", "size=160,432") | fail
+ # make offscreen canvas
+ DrawImage(cmap, 0, 0, cardbits()) # load card fragments
+
+ if WAttrib(cmap, "depth") == "1" then { # if monochrome screen
+ # dither red portions
+ Pattern(cmap, "4,#4141")
+ WAttrib(cmap, "fillstyle=masked", "fg=white")
+ FillRectangle(cmap, 0, 0, 160, 188, 0, 404, 117, 128)
+ # redraw face outlines
+ WAttrib(cmap, "fillstyle=solid", "fg=black")
+ every DrawRectangle(cmap, 0 to 96 by 48, 0 to 282 by 94, 47, 93)
+ }
+ else { # if color screen
+ # replace red portions with red bitmaps
+ rmap := open("redcards", "g", "canvas=hidden", "size=160,432",
+ "fg=dark red") | fail
+ DrawImage(rmap, 0, 0, cardbits())
+ CopyArea(rmap, cmap, 0, 0, 160, 188, 0, 0)
+ CopyArea(rmap, cmap, 0, 404, 117, 128, 0, 404)
+ Uncouple(rmap)
+ }
+ return cmap # return pixmap
+end
diff --git a/ipl/gprocs/drawcolr.icn b/ipl/gprocs/drawcolr.icn
new file mode 100644
index 0000000..216f9e2
--- /dev/null
+++ b/ipl/gprocs/drawcolr.icn
@@ -0,0 +1,69 @@
+############################################################################
+#
+# File: drawcolr.icn
+#
+# Subject: Procedure to display color list
+#
+# Author: Ralph E. Griswold
+#
+# Date: May 2, 2001
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# This program displays the colors given in a list.
+#
+############################################################################
+#
+# Requires: Version 9 graphics
+#
+############################################################################
+#
+# Links: graphics
+#
+############################################################################
+
+$define Cells 16
+$define Width 20
+
+link graphics
+
+procedure draw_colors(clist)
+ local i, j, k, depth, color, colors
+
+ depth := *clist / Cells
+ if *clist % Cells ~= 0 then depth +:= 1
+
+ WClose(\colors)
+
+ colors := WOpen("size=" || (Cells * Width) || "," || (depth * Width),
+ "bg=black") | {
+ Notice("Cannot open window for color map.")
+ exit()
+ }
+
+ every j := 0 to depth - 1 do
+ every i := 0 to Cells - 1 do {
+ color := get(clist) | break break
+ Fg(colors, color) | {
+ Notice("Cannot set foreground to " || image(color) || ".")
+ next
+ }
+ FillRectangle(colors, i * Width + 1, j * Width + 1, Width - 1,
+ Width - 1)
+ }
+
+ Bg(colors, "dark gray")
+ Fg(colors, "black")
+ WAttrib(colors, "fillstyle=textured")
+ WAttrib(colors, "pattern=checkers")
+
+ every k := i to Width - 1 do # fill out rest
+ FillRectangle(colors, k * Width + 1, j * Width + 1, Width - 1, Width - 1)
+
+ return colors
+
+end
diff --git a/ipl/gprocs/drawlab.icn b/ipl/gprocs/drawlab.icn
new file mode 100644
index 0000000..f19139a
--- /dev/null
+++ b/ipl/gprocs/drawlab.icn
@@ -0,0 +1,108 @@
+############################################################################
+#
+# File: drawlab.icn
+#
+# Subject: Procedure to draw figures
+#
+# Author: Ralph E. Griswold
+#
+# Date: August 3, 2000
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# This procedure is a general-purpose interface used by various programs
+# that draw figures of various kinds.
+#
+# Although it's listed as requiring graphics, that's really not necessary
+# for interfaces to other devices or just producing coordinates.
+#
+############################################################################
+#
+# Requires: Version 9 graphics
+#
+############################################################################
+#
+# Links: ifg, gtrace, gdisable, wopen, xgtrace
+#
+############################################################################
+
+link ifg
+link gtrace
+link gdisable
+link wopen
+link xgtrace
+
+global size # can be set by caller to control the window size
+
+procedure drawlab(p, callt, label)
+ local line, ws, calls, arg, trace, dlist, name
+
+ /size := 600
+
+ ws := ' \t'
+
+ calls := callt()
+
+ dlist := []
+ every put(dlist, key(calls))
+ dlist := sort(dlist)
+
+# If a window can be opened, set things up for drawing. If not, just
+# list coordinates. (This is useful for testing when an X server
+# is not available.)
+
+ if ifg() then {
+ WOpen("label=" || label, "width=" || size, "height=" || size) |
+ stop("*** cannot open window")
+ trace := line_trace
+ }
+ else {
+ gdisable()
+ trace := list_coords
+ }
+
+ while line := read() do {
+ EraseArea() # clear window if there is one
+ args := []
+ line ? {
+ tab(many(ws))
+ if ="=" then {
+ name := tab(0)
+ GotoRC(2, 2)
+ writes(&window, name)
+ trace(\calls[name]) | {
+ write(&errout, "*** erroneous specification")
+ next
+ }
+ }
+ else if ="all" then {
+ every name := !dlist do {
+ GotoRC(2, 2)
+ writes(&window, name)
+ trace(calls[name])
+ Event()
+ EraseArea()
+ }
+ }
+ else { # not tested yet
+ tab(many(ws))
+ while arg := tab(upto(',')) do {
+ if *arg = 0 then put(args, &null) else {
+ put(args, numeric(arg)) | {
+ write(&errout, "*** erroneous specification")
+ next
+ }
+ }
+ move(1) | break
+ tab(many(ws))
+ }
+ trace(call(p, args))
+ }
+ }
+ }
+
+end
diff --git a/ipl/gprocs/dsetup.icn b/ipl/gprocs/dsetup.icn
new file mode 100644
index 0000000..0d5492a
--- /dev/null
+++ b/ipl/gprocs/dsetup.icn
@@ -0,0 +1,293 @@
+############################################################################
+#
+# File: dsetup.icn
+#
+# Subject: Procedures for creating dialog boxes
+#
+# Authors: Gregg M. Townsend and Ralph E. Griswold
+#
+# Date: May 2, 2001
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# dsetup(win, wlist) initializes a set of widgets according to
+# a list of specifications created by the interface editor VIB.
+#
+# win can be an existing window, or null.
+#
+# wlist is a list of specifications; the first must be the Sizer and
+# the last may be null. Each specification is itself a list consisting
+# of a specification string, a callback routine, and an optional list
+# of additional specifications. Specification strings vary by vidget
+# type, but the general form is "ID:type:style:n:x,y,w,h:label".
+#
+# dsetup() returns a table of values from the dialog, indexed by ID.
+#
+############################################################################
+#
+# Includes: vdefns
+#
+############################################################################
+#
+# Links: dialog, xio, xutils,
+# vidgets, vslider, vmenu, vscroll, vtext, vbuttons, vradio
+# vdialog
+#
+############################################################################
+
+$include "vdefns.icn"
+
+link dialog
+link vdialog
+link vidgets
+link vslider
+link vmenu
+link vscroll
+link vtext
+link vbuttons
+link vradio
+link vsetup
+
+record DL_rec(var, typ, sty, num, x, y, w, h, lbl, cbk, etc)
+record DL_state(dialog, list, deflabel)
+
+global did_list, did_label
+
+## dsetup(win, wlist) -- set up vidgets and return table of handles
+#
+# wlist is a list of vidget specs as constructed by vib (or uix).
+
+procedure dsetup(win, wlist[])
+ local r, dialog, obj, num, wspec, alist
+
+ if type(win) ~== "window" then
+ win := &window
+
+ win := Clone(win, "fg=black", "linewidth=1", "linestyle=solid",
+ "fillstyle=solid", "drawop=copy") # clone window with standard attribs
+ VSetFont(win) # set standard VIB font
+ if ColorValue(Bg(win)) == ("65535,65535,65535" | "0,0,0") then
+ Bg(win, VBackground) # change black or white bg to pale gray
+
+ while /wlist[-1] do # ignore trailing null elements
+ pull(wlist)
+ wspec := get(wlist) # first spec gives wdow size
+
+ r := DL_crack(wspec) | stop("dsetup: bad spec")
+
+ did_list := []
+ did_label := &null
+
+ dialog := Vdialog(win, 0, 0) # create dialog frame
+ dialog.id := r.var
+ VInsert(dialog, Vmessage(win, ""), # set dialog box dimensions
+ r.x + r.w - 1, r.y + r.h - WAttrib(win, "fheight") - 1)
+
+ every r := DL_crack(!sort(wlist), &null) do {
+ DL_obj(win, dialog, r) # insert other vidgets
+ }
+
+ VFormat(dialog) # create the dialog
+
+ return DL_state(dialog, did_list, did_label) # return state for dpopup()
+
+end
+
+procedure dpopup(win, dftbl, dstate)
+ local did_list, init_list, i
+
+ if type(win) ~== "window" then {
+ win :=: dftbl
+ }
+
+ /dftbl := table()
+ did_list := dstate.list
+
+ init_list := list(*did_list)
+ every i := 1 to *did_list do
+ init_list[i] := \dftbl[did_list[i]]
+
+ dialog_value := VOpenDialog(dstate.dialog, , dstate.dialog.id,
+ init_list, dstate.deflabel)
+
+ every i := 1 to *did_list do
+ dftbl[did_list[i]] := dialog_value[i]
+
+ dialog_value := dftbl
+
+ return dialog_button
+
+end
+
+## DL_crack(wspec, cbk) -- extract elements of spec and put into record
+#
+# cbk is a default callback to use if the spec doesn't supply one.
+
+procedure DL_crack(wspec, cbk)
+ local r, f
+
+ r := DL_rec()
+ (get(wspec) | fail) ? {
+ r.var := tab(upto(':')) | fail; move(1)
+ r.typ := tab(upto(':')) | fail; move(1)
+ r.sty := tab(upto(':')) | fail; move(1)
+ r.num := tab(upto(':')) | fail; move(1)
+ r.x := tab(upto(',')) | fail; move(1)
+ r.y := tab(upto(',')) | fail; move(1)
+ r.w := tab(upto(',')) | fail; move(1)
+ r.h := tab(upto(':')) | fail; move(1)
+ r.lbl := tab(0)
+ }
+ get(wspec) # skip callback field
+ r.cbk := cbk # always use parameter
+ r.etc := get(wspec)
+ return r
+end
+
+
+## DL_obj(win, dialog, r) -- create vidget depending on type
+
+procedure DL_obj(win, dialog, r)
+ local obj, gc, style, lo, hi, iv, args
+
+ case r.typ of {
+ "Label" | "Message": {
+ obj := Vmessage(win, r.lbl)
+ VInsert(dialog, obj, r.x, r.y, r.w, r.h)
+ }
+ "Line": {
+ obj := Vline(win, r.x, r.y, r.w, r.h)
+ VInsert(dialog, obj, r.x, r.y, 1, 1)
+ }
+# "Rect": { # doesn't work
+# gc := Clone(win)
+# if r.num == "" | r.num = 0 then
+# r.num := &null
+# obj := Vpane(gc, r.cbk, r.var, r.num)
+# VInsert(dialog, obj, r.x, r.y, r.w, r.h)
+# }
+ "Rect": &null
+ "List": &null
+ "Check": {
+ obj := Vcheckbox(win, r.cbk, r.var, r.w)
+ VInsert(dialog, obj, r.x, r.y, r.w, r.h)
+ }
+ "Button": {
+ style := case r.sty of {
+ "regular": V_RECT
+ "regularno":V_RECT_NO
+ "check": V_CHECK
+ "checkno": V_CHECK_NO
+ "circle": V_CIRCLE
+ "circleno": V_CIRCLE_NO
+ "diamond": V_DIAMOND
+ "diamondno":V_DIAMOND_NO
+ "xbox": V_XBOX
+ "xboxno": V_XBOX_NO
+ default: V_RECT
+ }
+ if r.num == "1" then { # toggle
+ put(did_list, r.var)
+ obj := Vtoggle(win, r.lbl, r.cbk, r.var, style, r.w, r.h)
+ VRegister(dialog, obj, r.x, r.y)
+ }
+ else { # dismiss
+ obj := Vbutton(win, r.lbl, dialog_cb, V_OK, style, r.w, r.h)
+ VInsert(dialog, obj, r.x, r.y)
+ if r.num == "-1" then
+ did_label := r.lbl
+ }
+ }
+ "Choice": {
+ obj := Vradio_buttons(win, r.etc, r.cbk, r.var, V_DIAMOND_NO)
+ put(did_list, r.var)
+ VRegister(dialog, obj, r.x, r.y)
+ }
+ "Slider" | "Scrollbar" : {
+ r.lbl ? {
+ lo := numeric(tab(upto(',')))
+ move(1)
+ hi := numeric(tab(upto(',')))
+ move(1)
+ iv := numeric(tab(0))
+ }
+ if r.num == "" then
+ r.num := &null
+ obj := case (r.sty || r.typ) of {
+ "hSlider":
+ Vhoriz_slider(win, r.cbk, r.var, r.w, r.h, lo, hi, iv, r.num)
+ "vSlider":
+ Vvert_slider(win, r.cbk, r.var, r.h, r.w, hi, lo, iv, r.num)
+ "hScrollbar":
+ Vhoriz_scrollbar(win, r.cbk, r.var, r.w, r.h, lo, hi, , , r.num)
+ "vScrollbar":
+ Vvert_scrollbar(win, r.cbk, r.var, r.h, r.w, hi, lo, , , r.num)
+ }
+ put(did_list, r.var)
+ VRegister(dialog, obj, r.x, r.y)
+ }
+ "Text": {
+ obj := Vtext(win, r.lbl, r.cbk, r.var, r.num)
+ put(did_list, r.var)
+ VRegister(dialog, obj, r.x, r.y)
+ }
+# "Menu": {
+# obj := Vmenu_bar(win, r.lbl, DL_submenu(win, r.etc, r.cbk))
+# VInsert(dialog, obj, r.x, r.y)
+# }
+ "Menu": &null
+ default: {
+ stop("dsetup: unrecognized object: ", r.typ)
+ fail
+ }
+ }
+ return obj
+end
+
+
+
+## DL_submenu(win, lst, cbk) -- create submenu vidget
+
+procedure DL_submenu(win, lst, cbk)
+ local a, c, lbl
+
+ a := [win]
+ while *lst > 0 do {
+ put(a, get(lst))
+ if type(lst[1]) == "list" then
+ put(a, DL_submenu(win, get(lst), cbk))
+ else
+ put(a, cbk)
+ }
+ return Vsub_menu ! a
+end
+
+
+
+## dproto(proc, font, w, h) -- prototype a dialog box procedure built by vib
+#
+# n.b. "font" is now ignored, although it was once significant.
+
+procedure dproto(proc, font, w, h)
+ local win, s, l
+
+ w <:= 150
+ h <:= 100
+ win := Window([], "canvas=hidden")
+ VSetFont(win)
+ repeat {
+ if write(image(proc), " returned ", image(proc(win))) then {
+ l := sort(dialog_value, 3)
+ while write(" dialog_value[\"", get(l), "\"] = ", image(get(l)))
+ }
+ else
+ write(image(proc), " failed")
+ if TextDialog(win,"Test prototype",,,,["Again","Quit"]) == "Quit" then
+ break
+ }
+ WClose(win)
+end
diff --git a/ipl/gprocs/enqueue.icn b/ipl/gprocs/enqueue.icn
new file mode 100644
index 0000000..69c81b1
--- /dev/null
+++ b/ipl/gprocs/enqueue.icn
@@ -0,0 +1,157 @@
+############################################################################
+#
+# File: enqueue.icn
+#
+# Subject: Procedures for queued events
+#
+# Author: Gregg M. Townsend
+#
+# Date: May 2, 2001
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# These procedures manipulate Icon window events.
+#
+# Enqueue(W, eventcode, x, y, modkeys, interval) posts an event.
+#
+# pack_modkeys(s) encodes the modifier keys for an event.
+# unpack_modkeys(n) decodes a modifier key value.
+#
+# pack_intrvl(n) encodes an event interval.
+# unpack_intrvl(n) decodes an event interval.
+#
+############################################################################
+#
+# Icon's event queue is a list accessed via Pending(); the list
+# can be inspected or altered by the Icon program. An event is stored
+# as three consecutive entries on the list. The first is the event code:
+# a string for a keypress, or an integer for any other event. The next
+# two list entries are integers, interpreted as a packed structure:
+# 0000 0000 0000 0SMC XXXX XXXX XXXX XXXX (second entry)
+# 0EEE MMMM MMMM MMMM YYYY YYYY YYYY YYYY (third entry)
+#
+# The fields have these meanings:
+# X...X &x: 16-bit signed x-coordinate value
+# Y...Y &y: 16-bit signed y-coordinate value
+# SMC &shift, &meta, and &control (modifier keys)
+# E...M &interval, interpreted as M * 16 ^ E
+# 0 currently unused; should be zero
+#
+#
+# pack_modkeys(s) encodes a set of modifier keys, returning an
+# integer with the corresponding bits set. The string s contains
+# any combination of the letters c, m, and s to specify the bits
+# desired.
+#
+# pack_intrvl(n) encodes an interval of n milliseconds and returns
+# a left-shifted integer suitable for combining with a y-coordinate.
+#
+# unpack_modkeys(n) returns a string containing 0 to 3 of the
+# letters c, m, and s, depending on which modifier key bits are
+# set in the argument n.
+#
+# unpack_intrvl(n) discards the rightmost 16 bits of the integer
+# n (the y-coordinate) and decodes the remainder to return an
+# integer millisecond count.
+#
+# Enqueue([window,] eventcode, x, y, modkeys, interval) synthesizes
+# and enqueues an event for a window, packing the interval and modifier
+# keys (specified as above) into the correct places. Default values
+# are:
+# eventcode = &null
+# x = 0
+# y = 0
+# interval = 0
+# modkeys = ""
+#
+############################################################################
+#
+# Requires: Version 9 graphics
+#
+############################################################################
+
+
+# pack_intrvl(n) -- encode event interval
+
+procedure pack_intrvl(n) #: encode event interval
+ local e
+
+ n := integer(n) | runerr(101, n) # ensure integer
+ n <:= 0 # ensure nonnegative
+ e := 0 # assume exponent of 0
+
+ while n >= 16r1000 do { # if too big
+ n := ishift(n, -4) # reduce significance
+ e +:= 16r1000 # increase exponent
+ }
+ return ishift(e + n, 16) # return shifted result
+end
+
+
+# unpack_intrvl(n) -- decode event interval
+
+procedure unpack_intrvl(n) #: decode event interval
+ local e
+
+ n := integer(n) | runerr(101, n) # ensure integer
+ e := iand(ishift(n, -28), 7) # exponent
+ n := iand(ishift(n, -16), 16rFFF) # mantissa
+ return ishift(n, 4 * e)
+end
+
+
+# pack_modkeys(s) -- encode modifier keys
+
+procedure pack_modkeys(s) #: encode modifier keys
+ local b, c
+
+ b := 0
+ s := string(s) | runerr(103, s) # ensure string value
+ every c := !s do case c of { # set bit for each flag
+ "c": b := ior(b, 16r10000)
+ "m": b := ior(b, 16r20000)
+ "s": b := ior(b, 16r40000)
+ default: runerr(205, s) # diagnose bad flag
+ }
+ return b # return result
+end
+
+
+# unpack_modkeys(n) -- decode modifier keys
+
+procedure unpack_modkeys(n) #: decode modifier keys
+ local s
+
+ n := integer(n) | runerr(101, n) # ensure integer
+ s := ""
+ if iand(n, 16r10000) ~= 0 then s ||:= "c" # check each bit
+ if iand(n, 16r20000) ~= 0 then s ||:= "m"
+ if iand(n, 16r40000) ~= 0 then s ||:= "s"
+ return s # return result string
+end
+
+
+# Enqueue(window, eventcode, x, y, modkeys, interval) -- enqueue event
+
+procedure Enqueue(win, eventcode, x, y, modkeys, interval) #: enqueue event
+ static type
+
+ initial type := proc("type", 0) # protect attractive name
+
+ if type(win) ~== "window" then {
+ win :=: eventcode :=: x :=: y :=: modkeys :=: interval
+ win := &window
+ }
+ /x := 0
+ /y := 0
+ x +:= WAttrib(win, "dx")
+ y +:= WAttrib(win, "dy")
+ return put(Pending(win),
+ eventcode,
+ ior(pack_modkeys(\modkeys | ""), iand(x, 16rFFFF)),
+ ior(pack_intrvl(\interval | 0), iand(y, 16rFFFF)))
+end
diff --git a/ipl/gprocs/event.icn b/ipl/gprocs/event.icn
new file mode 100644
index 0000000..37b46ca
--- /dev/null
+++ b/ipl/gprocs/event.icn
@@ -0,0 +1,43 @@
+############################################################################
+#
+# File: event.icn
+#
+# Subject: Procedure to produces events from a window event history
+#
+# Author: Ralph E. Griswold
+#
+# Date: April 30, 1996
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# Event(win) overloads the built-in function Event() and produces
+# events using evplay().
+#
+############################################################################
+#
+# Requires: Version 9 graphics
+#
+############################################################################
+#
+# Links: evplay
+#
+############################################################################
+
+link evplay
+
+procedure Event(win)
+ static Event_
+
+ initial {
+ Event_ := proc("Event", 0) | stop("*** cannot get built-in Event()")
+ }
+
+ evplay(win) | exit()
+
+ return Event_(win)
+
+end
diff --git a/ipl/gprocs/evmux.icn b/ipl/gprocs/evmux.icn
new file mode 100644
index 0000000..da93237
--- /dev/null
+++ b/ipl/gprocs/evmux.icn
@@ -0,0 +1,236 @@
+############################################################################
+#
+# File: evmux.icn
+#
+# Subject: Procedures for window event multiplexor
+#
+# Author: Gregg M. Townsend
+#
+# Date: August 14, 1996
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# These procedures implement a simple event handling package. This
+# package has more recently been superseded by the vidget library.
+#
+# The event multiplexor is configured by registering *sensors*, which
+# respond to events that occur when the mouse cursor is within a
+# particular region. When a sensor fires, it calls a user procedure
+# that was registered when the sensor was created.
+#
+# These routines interpret window events and invoke callbacks:
+#
+# sensor() registers the events of interest.
+#
+# evhandle() reads and responds to the next event.
+#
+# evmux() loops forever, handling events.
+#
+# Two other small procedures help build event-driven programs:
+#
+# quitsensor() registers a standardized response to Q or q.
+#
+# argless() is a "glue" procedure usable as a callback.
+#
+############################################################################
+#
+# sensor(win, ev, proc, arg, x, y, w, h) -- register an event responder.
+#
+# registers *proc* as the procedure to be called when the event[s]
+# *ev* occur within the given bounds inside window *win* and returns
+# a handle. The default bounds encompass the entire window.
+#
+# The event set *ev* can be either:
+# -- a cset or string specifying particular keypresses of interest
+# -- one of the event keywords (&lpress, &rdrag, &resize, etc.)
+#
+# When a matching event occurs, proc(win, arg, x, y, e) is called. proc,
+# win, and arg are as recorded from the sensor call. x and y give the
+# current mouse position and e the event; for a keypress, this is the
+# character.
+#
+# No event generates more than one procedure call.
+# In the case of conflicting entries, the later registrant wins.
+#
+# delsensor(win, x) deletes sensor x from the specified window.
+# If x is null, all sensors are deleted.
+#
+#
+# evmux(win) -- loop forever, calling event handlers as appropriate.
+# evhandle(win) -- wait for the next event, and handle it.
+#
+# evmux(win) is an infinite loop that calls user routines in response
+# to window events. It is for programs that don't need to do other
+# work while waiting for window input.
+#
+# evhandle(win) processes one event and then returns to its caller,
+# allowing external loop control. evhandle returns the outcome of
+# the handler proc, or fails if there is no handler for the event.
+#
+# quitsensor(win, wait) -- standardized "quit" sensor
+#
+# quitsensor() registers a sensor that calls exit() when either
+# "q" or "Q" is typed in the window.
+#
+# If wait is non-null, quitsensor does not return but just waits for
+# the signal (useful in non-interactive display programs).
+#
+#
+# argless(win, proc) -- call proc with no arguments.
+#
+# Useful for registering argless procedures as in quitsensor() above.
+#
+############################################################################
+#
+# Requires: Version 9 graphics
+#
+############################################################################
+#
+# See also: button.icn, slider.icn
+#
+############################################################################
+
+record EvMux_Rec(ev, proc, arg, x, y, w, h)
+global EvMux_Windows
+
+
+## sensor(win, ev, proc, arg, x, y, w, h) -- register an event responder.
+
+procedure sensor(win, ev, proc, arg, x, y, w, h)
+ local evlist, r, e
+
+ /EvMux_Windows := table()
+ /EvMux_Windows[win] := list()
+ evlist := EvMux_Windows[win]
+
+ /x := -WAttrib(win, "dx")
+ /y := -WAttrib(win, "dy")
+ /w := WAttrib(win, "width") - (x + WAttrib(win, "dx"))
+ /h := WAttrib(win, "height") - (y + WAttrib(win, "dy"))
+
+ if w < 0 then
+ x -:= (w := -w)
+ if h < 0 then
+ y -:= (h := -h)
+
+ if type(ev) == ("cset" | "string") then
+ ev := cset(ev)
+ else
+ ev := cset(evchar(ev)) | stop("invalid event specification: ", image(ev))
+ push(evlist, r := EvMux_Rec(ev, proc, arg, x, y, w, h))
+ return r
+end
+
+
+## delsensor(win, x) -- delete sensor x, or all sensors, from window.
+
+procedure delsensor(win, x)
+ local t
+
+ t := \EvMux_Windows[win] | fail
+
+ if /x then {
+ delete(EvMux_Windows, win) # delete whole set of sensors
+ return
+ }
+
+ if not (x === !t) then
+ fail # not registered in this window
+
+ # Sensor is registered for this window. Disable it.
+ x.ev := ''
+
+ # Remove disabled sensors from list, if possible.
+ while *t[1].ev = 0 do
+ pop(t)
+ while *t[-1].ev = 0 do
+ pull(t)
+
+ # If nothing is left on list, delete from table.
+ if *t = 0 then
+ delete(EvMux_Windows, win)
+ return
+end
+
+
+## evchar(e) -- map mouse event to character code.
+#
+# Internally, *all* events are single-character strings, and mouse & resizing
+# events are mapped into characters that are never returned as keypress events.
+
+procedure evchar(s)
+ return case s of {
+ &lpress: "\237" # mouse button 1 down
+ &mpress: "\236" # mouse button 2 down
+ &rpress: "\235" # mouse button 3 down
+ &lrelease: "\234" # mouse button 1 up
+ &mrelease: "\233" # mouse button 2 up
+ &rrelease: "\232" # mouse button 3 up
+ &ldrag: "\231" # mouse button 1 is dragging
+ &mdrag: "\230" # mouse button 2 is dragging
+ &rdrag: "\227" # mouse button 3 is dragging
+ &resize: "\226" # window has resized
+ }
+ fail
+end
+
+
+## evmux(win) -- loop forever, calling event handlers as appropriate.
+## evhandle(win) -- wait for the next event, and handle it.
+# produce result of the handler proc; fail if nobody handles.
+
+procedure evmux(win)
+ repeat
+ evhandle(win)
+end
+
+procedure evhandle(win)
+ local x, y, ev, e, r, t
+
+ t := (\EvMux_Windows)[win] | stop("no events registered for window")
+ ev := Event(win)
+ x := &x
+ y := &y
+
+ # convert event code to single character
+ if type(ev) == "integer" then
+ e := evchar(ev) | ""
+ else
+ e := ev
+
+ # find and call the first (most recent) matching handler
+ # (just a simple serial search)
+ every r := !t do
+ if any(r.ev, e) & ontarget(r, x, y) then
+ return r.proc(win, r.arg, x, y, ev)
+ fail
+end
+
+
+## ontarget(r, x, y) -- check if an event is within bounds
+#
+# checks that (x, y) are within the bounds of (r.x, r.y, r.w, r.h).
+
+procedure ontarget(r, x, y)
+ return (x -:= r.x) >= 0 & x < r.w & (y -:= r.y) >= 0 & y < r.h
+end
+
+
+## quitsensor(win, wait) -- standardized "quit" sensor
+
+procedure quitsensor(win, wait)
+ sensor(win, 'qQ', argless, exit)
+ if \wait then evmux(win)
+ return
+end
+
+
+## argless(win, proc) -- call proc with no arguments.
+
+procedure argless(win, proc)
+ return proc()
+end
diff --git a/ipl/gprocs/evplay.icn b/ipl/gprocs/evplay.icn
new file mode 100644
index 0000000..9eb1eeb
--- /dev/null
+++ b/ipl/gprocs/evplay.icn
@@ -0,0 +1,49 @@
+############################################################################
+#
+# File: evplay.icn
+#
+# Subject: Procedure to "play back" recorded window events
+#
+# Author: Ralph E. Griswold
+#
+# Date: July 15, 1995
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# evplay(win) reads a window event history file (such as produced by
+# evrecord()), and puts an event on the event queue for the given window.
+# If the global identifier EventFile is nonnull, it is used as the
+# event history; otherwise standard input is used.
+#
+############################################################################
+#
+# Requires: Version 9 graphics
+#
+############################################################################
+#
+# Links: ivalue
+#
+############################################################################
+
+link ivalue
+
+global EventFile
+
+procedure evplay(win)
+ local event1, event2, event3
+
+ /EventFile := &input
+
+ event1 := ivalue(read(EventFile)) | fail
+ event2 := ivalue(read(EventFile)) | stop("*** short event history")
+ event3 := ivalue(read(EventFile)) | stop("*** short event history")
+
+ put(Pending(win), event1, event2, event3)
+
+ return
+
+end
diff --git a/ipl/gprocs/evrecord.icn b/ipl/gprocs/evrecord.icn
new file mode 100644
index 0000000..6eaadf2
--- /dev/null
+++ b/ipl/gprocs/evrecord.icn
@@ -0,0 +1,51 @@
+############################################################################
+#
+# File: evrecord.icn
+#
+# Subject: Procedure to record window events
+#
+# Author: Ralph E. Griswold
+#
+# Date: April 25, 1996
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# This procedure writes a file of graphics events. The file can be
+# converted to "pseudo events" by evplay.icn.
+#
+# When used with a vidget interface, evrecord can be passed as an
+# argument to, say, GetEvents(), as in
+#
+# GetEvents(root, , evrecord)
+#
+############################################################################
+#
+# Requires: Version 9 graphics
+#
+############################################################################
+#
+# Links: enqueue
+#
+############################################################################
+
+link enqueue
+
+procedure evrecord(event)
+ local modkeys
+
+ modkeys := ""
+ modkeys ||:= (&shift & "s")
+ modkeys ||:= (&meta & "m")
+ modkeys ||:= (&control & "c")
+
+ write(image(event))
+ write(ior(pack_modkeys(modkeys), iand(&x, 16rFFFF)))
+ write(ior(pack_intrvl(&interval), iand(&y, 16rFFFF)))
+
+ return
+
+end
diff --git a/ipl/gprocs/fetchpat.icn b/ipl/gprocs/fetchpat.icn
new file mode 100644
index 0000000..b2358d6
--- /dev/null
+++ b/ipl/gprocs/fetchpat.icn
@@ -0,0 +1,45 @@
+############################################################################
+#
+# File: fetchpat.icn
+#
+# Subject: Procedure to fetch a pattern specification
+#
+# Author: Ralph E. Griswold
+#
+# Date: October 21, 1993
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# This procedure fetches a pattern by number from a file of pattern
+# specifications. It fails if the file does not exist or does not
+# contain that many pattern specifications.
+#
+# The file is searched for in the current directory first, then using
+# DPATH.
+#
+############################################################################
+#
+# Links: io, patutils
+#
+############################################################################
+
+link io
+link patutils
+
+procedure fetchpat(file, n)
+ local input, pattern
+
+ input := dopen(file) | fail
+
+ every 1 to n do
+ pattern := readpatt(input)
+
+ close(file)
+
+ return \pattern
+
+end
diff --git a/ipl/gprocs/fstars.icn b/ipl/gprocs/fstars.icn
new file mode 100644
index 0000000..3f129c8
--- /dev/null
+++ b/ipl/gprocs/fstars.icn
@@ -0,0 +1,94 @@
+############################################################################
+#
+# File: fstars.icn
+#
+# Subject: Procedure to produce traces of fractal stars
+#
+# Author: Ralph E. Griswold
+#
+# Date: May 23, 1996
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# This procedure produces traces of fractal "stars". For a discussion of
+# fractal stars, see
+#
+# Fractals; Endlessly Repeated Geometrical Figures, Hans Lauwerier,
+# Princeton University Press, 1991, pp. 72-77.
+#
+# and
+#
+# Geometric and Artistic Graphics; Design Generation with
+# Microcomputers, Jean-Paul Delahaye, Macmillan, 1987, pp. 55-63.
+#
+# The arguments are:
+#
+# x, y, n, p, r, incr, extent
+#
+# x x coordinate of the initial point, default 0
+# y y coordinate of the initial point, default 0.5
+# n number of vertices, default 5
+# p number of phases, default 5
+# r reduction factor, default 0.35
+# incr angular increment factor, default 0.8
+# extent extent of drawing, 1.0
+#
+# Chosing values for these arguments that produce interesting results and
+# centering the star in the window is somewhat of an art. See fstartbl.icn
+# for some good values.
+#
+############################################################################
+#
+# Links: gobject
+#
+############################################################################
+
+link gobject
+
+global size
+
+procedure fstar(x, y, n, p, r, incr, extent, xinit, yinit) #: fractal stars
+ local angle, i, h, m, dist, xloc, yloc
+
+ /size := 500
+ /x := 0
+ /y := 0.5 * size
+ /n := 5 # defaults
+ /p := 5
+ /r := 0.35
+ /incr := 0.8
+ /extent := 1.0
+ /xinit := 0
+ /yinit := 0.5
+
+ incr *:= &pi # scaling
+ extent *:= size
+ xloc := xinit * size
+ yloc := yinit * size
+
+ n -:= 1 # computational convenience
+ p -:= 1
+
+# suspend Point(x + xloc, y + yloc) # initial point
+
+ angle := 0
+
+ every i := 0 to ((n + 1) * n ^ p) do {
+ m := i
+ h := 0
+ until (m % n ~= 0) | (h >= p) do {
+ m /:= n
+ h +:= 1
+ }
+ dist := extent * r ^ (p - h)
+ xloc +:= dist * cos(angle)
+ yloc +:= dist * sin(angle)
+ suspend Point(x + xloc, y + yloc)
+ angle +:= incr
+ }
+
+end
diff --git a/ipl/gprocs/fstartbl.icn b/ipl/gprocs/fstartbl.icn
new file mode 100644
index 0000000..5fa1f4d
--- /dev/null
+++ b/ipl/gprocs/fstartbl.icn
@@ -0,0 +1,67 @@
+############################################################################
+#
+# File: fstartbl.icn
+#
+# Subject: Procedure to produce calls for fractal stars
+#
+# Author: Ralph E. Griswold
+#
+# Date: April 8, 1993
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# This procedure produces a table of calls from which fractal stars
+# can be produced.
+#
+############################################################################
+#
+# See also: fstars.icn
+#
+############################################################################
+#
+# Links: calls, fstars, numbers
+#
+############################################################################
+
+link calls
+link fstars
+link numbers
+
+procedure fstartbl()
+ local fstars
+
+ fstars := table()
+ fstars["fstar01"] := call(fstar,
+ [0, 0, 5, 5, 0.350, 0.80000, 1.00000, 0.000, 0.450])
+ fstars["fstar02"] := call(fstar,
+ [0, 0, 7, 4, 0.320, div(6, 7), 1.00000, 0.000, 0.570])
+ fstars["fstar03"] := call(fstar,
+ [0, 0, 12, 3, 0.500, div(1, 6), div(11, 48), 0.400, 0.300])
+ fstars["fstar04"] := call(fstar,
+ [0, 0, 5, 2, 0.500, 0.40000, 0.50000, 0.300, 0.500])
+ fstars["fstar05"] := call(fstar,
+ [0, 0, 8, 2, 0.500, 0.25000, div(1, 3), 0.350, 0.500])
+ fstars["fstar06"] := call(fstar,
+ [0, 0, 20, 2, 0.500, 0.10000, div(13, 96), 0.400, 0.500])
+ fstars["fstar07"] := call(fstar,
+ [0, 0, 15, 2, 0.900, div(14, 15), div(43, 48), 0.050, 0.470])
+ fstars["fstar08"] := call(fstar,
+ [0, 0, 16, 3, 0.270, 0.12500, div(1, 6), 0.400, 0.270])
+ fstars["fstar09"] := call(fstar,
+ [0, 0, 8, 4, 0.500, 0.25000, div(17, 48), 0.300, 0.600])
+ fstars["fstar10"] := call(fstar,
+ [0, 0, 7, 5, 0.383, 0.40000, div(7, 12), 0.200, 0.050])
+ fstars["fstar11"] := call(fstar,
+ [0, 0, 4, 8, 0.470, 0.50000, 1.00000, 0.000, 0.680])
+ fstars["fstar12"] := call(fstar,
+ [0, 0, 15, 3, 0.300, div(14, 15), 1.00000, 0.000, 0.470])
+ fstars["fstar13"] := call(fstar,
+ [0, 0, 3, 11, 0.620, div(2, 3), 1.00000, 0.000, 0.450])
+
+ return fstars
+
+end
diff --git a/ipl/gprocs/gdisable.icn b/ipl/gprocs/gdisable.icn
new file mode 100644
index 0000000..4a1df66
--- /dev/null
+++ b/ipl/gprocs/gdisable.icn
@@ -0,0 +1,81 @@
+############################################################################
+#
+# File: gdisable.icn
+#
+# Subject: Procedure to disable graphics functions
+#
+# Author: Ralph E. Griswold
+#
+# Date: August 14, 1996
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# This procedure effectively disables the graphics functions. Care should
+# be taken in the way the disabled functions are used, since in their
+# disabled forms, they return their first argument (if any).
+#
+############################################################################
+#
+# Requires: Version 9 graphics
+#
+############################################################################
+
+procedure gdisable()
+
+ every (
+ Active |
+ Alert |
+ Bg |
+ Clip |
+ Clone |
+ Color |
+ ColorValue |
+ CopyArea |
+ Couple |
+ DrawArc |
+ DrawCircle |
+ DrawCurve |
+ DrawImage |
+ DrawLine |
+ DrawPoint |
+ DrawPolygon |
+ DrawRectangle |
+ DrawSegment |
+ DrawString |
+ EraseArea |
+ Event |
+ Fg |
+ FillArc |
+ FillCircle |
+ FillPolygon |
+ FillRectangle |
+ Font |
+ FreeColor |
+ GotoRC |
+ GotoXY |
+ Lower |
+ NewColor |
+ PaletteChars |
+ PaletteColor |
+ PaletteKey |
+ Pattern |
+ Pending |
+ Pixel |
+ QueryPointer |
+ Raise |
+ ReadImage |
+ TextWidth |
+ Uncouple |
+ WAttrib |
+ WDefault |
+ WFlush |
+ WSync |
+ WriteImage) := 1
+
+ return
+
+end
diff --git a/ipl/gprocs/getcolrs.icn b/ipl/gprocs/getcolrs.icn
new file mode 100644
index 0000000..2fafb69
--- /dev/null
+++ b/ipl/gprocs/getcolrs.icn
@@ -0,0 +1,377 @@
+############################################################################
+#
+# File: getcolrs.icn
+#
+# Subject: Procedures for getting color palette
+#
+# Author: Ralph E. Griswold
+#
+# Date: May 2, 2001
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# These procedures support the interactive selection of colors.
+#
+############################################################################
+#
+# Requires: Version 9 graphics
+#
+############################################################################
+#
+# Links: colrlist, dsetup, interact
+#
+############################################################################
+
+link colrlist, dsetup, interact
+
+global save_colortbl_name
+
+$define CellSize 16
+$define ColorCols 16
+$define ColorRows 16
+$define ColorField 20
+$define NumberField 3
+$define WPad 20
+$define HPad 45
+
+global colors
+global colortbl
+global palette
+
+record colorspec(palette, colors)
+
+procedure color_palette()
+ local pal_win, e, number, color_win, x, y, c, i
+ static windows, attribs, colors_tmp, clist, palettes
+
+ initial {
+
+ windows := table()
+ attribs := table()
+
+ attribs["palette"] := "c3"
+
+ palettes := table() # set up palette colors
+
+ every clist := ("c" || (1 to 6)) | ("g" || (16 | 64)) do
+ palettes[clist] := colrplte(clist) | {
+ Notice("Internal error")
+ exit()
+ }
+
+ }
+
+ if colors_dl(attribs) == "Cancel" then fail
+
+ clist := palettes[attribs["palette"]]
+
+ color_win := palette_win("color list", WAttrib("width") + WPad, 0) | fail
+
+ pal_win := palette_win("palette", WAttrib("width") + WPad,
+ WAttrib(color_win, "height") + HPad) | fail
+
+ i := 0
+ every y := 1 + (0 to ColorCols) * CellSize do
+ every x := 1 + (0 to ColorRows) * CellSize do {
+ Fg(pal_win, clist[i +:= 1]) | break break
+ FillRectangle(pal_win, x, y, CellSize - 1, CellSize - 1)
+ }
+
+ colors_tmp := []
+
+ x := y := 1
+
+ repeat {
+ e := Event(pal_win)
+ if &meta & (map(e) == "q") then break
+ if e === (&lpress | &rpress | &mpress) then {
+ if ((&x % CellSize) | (&y % CellSize)) = 0 then next # on border
+ put(colors_tmp, c := Pixel(pal_win, &x, &y, 1, 1))
+ Fg(color_win, c)
+ FillRectangle(color_win, x, y, CellSize - 1, CellSize - 1)
+ x +:= CellSize
+ if (x > ColorCols * CellSize) then {
+ x := 1
+ y +:= CellSize
+ if y > (ColorRows * CellSize) then break
+ }
+ }
+ }
+
+ WAttrib(pal_win, "canvas=hidden")
+ EraseArea(pal_win)
+ WClose(color_win)
+
+ if *colors_tmp = 0 then return Notice("Empty palette")
+
+ colors := colors_tmp
+
+ if OpenDialog("Palette name:") == "Cancel" then fail
+
+ palette := dialog_value
+
+ colortbl[palette] := colors
+
+ return colors_tmp
+
+end
+
+procedure edit_colors(colors)
+ local color_win, x, y
+
+ color_win := palette_win("color list", WAttrib("width") + WPad, 0) | fail
+ x := y := 1
+
+ every Fg(color_win, !colors) do {
+ FillRectangle(color_win, x, y, CellSize - 1, CellSize - 1)
+ x +:= CellSize
+ if (x > ColorCols * CellSize) then {
+ x := 1
+ y +:= CellSize
+ if y > (ColorRows * CellSize) then break
+ }
+ }
+
+ Event(color_win)
+
+ WClose(color_win)
+
+end
+
+procedure palette_win(label, xoff, yoff)
+ local win, x, y
+
+ win := WOpen("width=" || (ColorCols * CellSize), "height=" || (ColorRows *
+ CellSize), "label=" || label, "bg=black", "fg=white",
+ "posx=" || (WAttrib("posx") + xoff),
+ "posy=" || (WAttrib("posy") + yoff)) |
+ return Notice("Cannot open window for palette selection")
+
+ WAttrib(win, "fillstyle=textured")
+ Pattern(win, "checkers")
+ Bg(win, "very dark gray")
+
+ every x := 1 + (0 to ColorRows) * CellSize do
+ every y := 1 + (0 to ColorCols) * CellSize do
+ FillRectangle(win, x, y, CellSize - 1, CellSize - 1)
+
+ WAttrib(win, "fillstyle=solid")
+ Bg(win, "black")
+
+ return win
+
+end
+
+# This procedure allows the users to provide lists of colors, widths, and
+# blend information.
+#
+# If i = 0 then only integers are allowed.
+# If i = 1 then only color specifications are allowed.
+# If i = 2 then both integers and color specifications are allowed. This
+# is for blend information.
+
+procedure get_list(i)
+ local n, list_tmp, x
+
+ if Dialog("Number of entries", , 2, NumberField, ["Okay", "Cancel"]) ==
+ "Cancel" then fail
+
+ n := (0 < integer(dialog_value[1])) |
+ return Notice("Invalid number specification")
+
+ if Dialog("Values", , list(n, ""), ColorField, ["Okay", "Cancel"]) ==
+ "Cancel" then fail
+
+ list_tmp := []
+
+ every x := !dialog_value do {
+ if *x = 0 then next # skip empty fields
+ case i of {
+ 0: put(list_tmp, integer(x)) | return Notice("Invalid width")
+ 1: put(list_tmp, ColorValue(x)) | return Notice("Invalid color")
+ 2: put(list_tmp, ColorValue(x) | (\x & integer(x))) |
+ return Notice("Invalid blend value:", x)
+ }
+ }
+
+ if *list_tmp = 0 then return Notice("Empty list")
+
+ return list_tmp
+
+end
+
+procedure color_blend()
+ local colors_tmp
+
+ colors_tmp := []
+
+ every put(colors_tmp, Blend ! get_list(2)) | fail # accept counts
+
+ return colors_tmp
+
+end
+
+procedure get_colors(s)
+
+ return case s of {
+ "palette": color_palette()
+ "file": unsupported()
+ "list": get_list(1)
+ "blend": color_blend()
+ default: unsupported()
+ }
+
+end
+
+procedure select_color(palette)
+ local clist,k
+
+ clist := []
+ every k := key(colortbl) do
+ if \colortbl[k] then put(clist, k)
+
+ if *clist = 0 then {
+ Notice("No colors are available")
+ fail
+ }
+
+ SelectDialog("Select color list:", sort(clist), palette) == "Okay" | fail
+
+ palette := dialog_value
+ colors := colortbl[palette]
+
+ return
+
+end
+
+procedure save_colortbl()
+ local output, temp, n, clist
+
+ if /save_colortbl_name then return save_colortbl_as()
+
+ output := open(save_colortbl_name, "w") | {
+ Notice("Can't open save file for writing")
+ fail
+ }
+
+ temp := sort(colortbl, 3)
+
+ while n := get(temp) do {
+ clist := \get(temp) | next
+ writes(output, n, ":")
+ every writes(output, !clist, " ")
+ write(output)
+ }
+
+ close(output)
+
+ return
+
+end
+
+procedure load_colortbl()
+ local line, clist, tbl, name
+
+ load_file("Load color table:") == "Okay" | fail
+
+ tbl := table()
+
+ while line := read(dialog_value) do {
+ line ? {
+ name := tab(upto(':')) | {
+ Notice("Invalid color table.")
+ fail
+ }
+ move(1)
+ clist := []
+ while put(clist, tab(upto(' '))) do
+ move(1)
+ tbl[name] := clist
+ }
+ }
+
+ colortbl := tbl
+ palette := name
+ colors := clist
+
+ close(dialog_value)
+
+ return
+
+end
+
+procedure save_colortbl_as()
+ local n, clist, temp
+
+ save_as("Save color table:") == "Yes" | fail
+
+ temp := sort(colortbl, 3)
+
+ while n := get(temp) do {
+ clist := \get(temp) | next
+ writes(dialog_value, n, ":")
+ every writes(dialog_value, !clist, " ")
+ write(dialog_value)
+ }
+
+ image(dialog_value) ? {
+ ="file("
+ save_colortbl_name := tab(upto(')'))
+ }
+ close(dialog_value)
+
+ return
+
+end
+
+procedure delete_color()
+ local clist, k
+
+ if *colortbl = 0 then {
+ Notice("No colors are available")
+ fail
+ }
+
+ clist := []
+ every k := key(colortbl) do
+ if \colortbl[k] then put(clist, k)
+
+ SelectDialog("Delete color:", sort(clist), palette) == "Okay" | fail
+
+ TextDialog("Delete " || dialog_value || "?") == "Okay" | fail
+
+ colortbl[dialog_value] := &null
+
+ return
+
+end
+
+procedure delete_colortbl()
+
+ TextDialog("Delete entire color table?") == "Okay" | fail
+
+ colortbl := table()
+
+ return
+
+end
+
+#===<<vib:begin>>=== modify using vib; do not remove this marker line
+procedure colors_dl(win, deftbl)
+static dstate
+initial dstate := dsetup(win,
+ ["colors_dl:Sizer::1:0,0,161,249:colors",],
+ ["cancel:Button:regular::83,214,50,20:Cancel",],
+ ["label1:Label:::11,19,56,13:Palette:",],
+ ["okay:Button:regular:-1:15,213,50,20:Okay",],
+ ["palette:Choice::8:83,16,50,168:",,
+ ["c1","c2","c3","c4","c5",
+ "c6","g16","g64"]],
+ )
+return dpopup(win, deftbl, dstate)
+end
+#===<<vib:end>>=== end of section maintained by vib
diff --git a/ipl/gprocs/gifsize.icn b/ipl/gprocs/gifsize.icn
new file mode 100644
index 0000000..b6dd9a3
--- /dev/null
+++ b/ipl/gprocs/gifsize.icn
@@ -0,0 +1,51 @@
+############################################################################
+#
+# File: gifsize.icn
+#
+# Subject: Procedure to return size of GIF file
+#
+# Author: Ralph E. Griswold
+#
+# Date: May 2, 2001
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# This procedure returns the size of a GIF file in the form
+# width,height. It fails if the file does not exist or is
+# not a valid GIF file.
+#
+############################################################################
+#
+# Links: bincvt
+#
+############################################################################
+
+link bincvt
+
+procedure gifsize(name) #: size of GIF file
+ local gif, width, height
+
+ gif := open(name) | fail
+
+ repeat { # only to provide a loop to break out of ...
+ read(gif) ? {
+ =("GIF87a" | "GIF89a") | break
+ width := move(1)
+ width := move(1) || width
+ width := unsigned(width) | break
+ height := move(1)
+ height := move(1) || height
+ height := unsigned(height) | break
+ close(gif)
+ return width || "," || height
+ } | break
+ }
+
+ close(gif)
+ fail
+
+end
diff --git a/ipl/gprocs/glabels.icn b/ipl/gprocs/glabels.icn
new file mode 100644
index 0000000..28e41ab
--- /dev/null
+++ b/ipl/gprocs/glabels.icn
@@ -0,0 +1,68 @@
+############################################################################
+#
+# File: glabels.icn
+#
+# Subject: Procedure to produce graph ticks
+#
+# Author: Ralph E. Griswold
+#
+# Date: May 2, 2001
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# glabels(min, max, nticks) produces a list of aesthetically pleasing labels
+# for graph ticks to cover a given range. It is based on the algorithm
+# given by Paul S. Heckert in "Graphic Gems", Andrew S Glassner, ed.,
+# Academic Press, 1990.
+#
+############################################################################
+#
+# Links: numbers
+#
+############################################################################
+
+link numbers
+
+procedure glabels(min, max, ntick)
+ local d, graphmin, graphmax, nfrac, llist, x, nf, range
+
+ if min = max then fail # no can do
+
+ range := nicenum(max - min)
+ d := nicenum(range / (ntick - 1), 1)
+ graphmin := floor(min / d) * d
+ graphmax := ceil(max / d) * d
+ nfrac := max(-floor(log(d, 10)), 0)
+ llist := []
+ every x := graphmin to graphmax + 0.5 * d by d do
+ put(llist, x)
+
+ return llist
+
+end
+
+procedure nicenum(x, round)
+ local exp, f, nf
+
+ exp := floor(log(x, 10))
+ f := x / (10 ^ exp)
+ if \round then {
+ if f < 1.5 then nf := 1
+ else if f < 3.0 then nf := 2
+ else if f < 7 then nf := 5
+ else nf := 10
+ }
+ else {
+ if f <= 1 then nf := 1
+ else if f <= 2 then nf := 2
+ else if f <= 5 then nf := 5
+ else nf := 10
+ }
+
+ return nf * (10 ^ exp)
+
+end
diff --git a/ipl/gprocs/glib.icn b/ipl/gprocs/glib.icn
new file mode 100644
index 0000000..09e749a
--- /dev/null
+++ b/ipl/gprocs/glib.icn
@@ -0,0 +1,789 @@
+############################################################################
+#
+# File: glib.icn
+#
+# Subject: Procedures for graphics
+#
+# Author: Stephen B. Wampler
+#
+# Date: May 2, 2001
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# Version: 1.0
+#
+############################################################################
+#
+#
+# Comments: This package is the collection of routines
+# developed to facilitate traditional 2D graphics.
+# It is incomplete, but still provides
+# a reasonable amount of support. There is some
+# support for 3D graphics here, but that is not so
+# well developed. People are encouraged to improve
+# these routines and add new routines.
+#
+# All routines use list-based subscripting. This allows
+# programs to describe points as lists OR records.
+#
+# In the turtle graphics code, the use gives angles in
+# degrees.
+#
+############################################################################
+#
+# Requires: Version 9 graphics, co-expressions
+#
+############################################################################
+
+record point(x,y)
+
+############################################################################
+# Clipping algorithms...
+#
+global DO_CLIPPING
+
+
+# Set the state of clipping: "on" or "off"
+#
+procedure set_clip(state)
+ if map(state) == "on" then
+ DO_CLIPPING := "yes"
+ else
+ DO_CLIPPING := &null
+end
+
+# Either clip a line or leave it alone
+#
+procedure Clip_Line(line,box)
+ if \DO_CLIPPING then
+ return LB_line_clip(line, box)
+ return line
+end
+
+# Note: Liang-Barsky algorithms (or variants) are used. If you
+# have fast FP hardware, they are faster than Cohen-Sutherland
+# (and *much* slower if you *don't*!). Anyway, they're more
+# fun to code and easier to extend to 3-D.
+
+#
+# LB_line_clip -- takes a 2-D line (two points) and returns it clipped to
+# a box (normally the viewport).
+procedure LB_line_clip(line, box)
+ local nline, u, dx, dy
+
+ # initialize important parametric values
+ dx := line[2][1] - line[1][1]
+ dy := line[2][2] - line[2][2]
+ u := [0.0, 1.0]
+
+ # do the clipping
+ if clipcheck(-dx, line[1][1] - box[1][1], u) &
+ clipcheck( dx, box[2][1] - line[1][1], u) &
+ clipcheck(-dy, line[1][2] - box[1][2], u) &
+ clipcheck( dy, box[2][2] - line[1][1], u) then {
+ # return a modified copy of original line
+ nline := copy(line)
+ nline[1] := copy(line[1])
+ nline[2] := copy(line[2])
+
+ if u[2] < 1.0 then {
+ nline[2][1] := line[1][1] + (u[2]*dx)
+ nline[2][2] := line[1][2] + (u[2]*dy)
+ }
+ if u[1] < 1.0 then {
+ nline[1][1] := line[1][1] + (u[1]*dx)
+ nline[1][2] := line[1][2] + (u[1]*dy)
+ }
+ return nline
+ }
+ # no need to clip
+ fail
+end
+
+procedure clipcheck(p,q,u)
+ local r
+
+ if p < 0.0 then {
+ r := real(q)/p
+ if r > u[2] then fail
+ else if r > u[1] then u[1] := r
+ }
+ else if p > 0.0 then {
+ r := real(q)/p
+ if r > u[1] then fail
+ else if r > u[2] then u[2] := r
+ }
+ else if q >= 0.0 then return
+
+end
+
+#
+# Clip a line to a convex polygon (2-D)
+#
+procedure Convex_clip(poly, line[])
+ # Cyrus-Beck line clipping against a convex polygon
+ # (assumes poly is a convex polygon!)
+ local D, nc, E, cline
+ local n, p # point normal of polygon edge
+ local c, p1 # point slope of line
+ local t_in, t_out # current endpoints
+ local t, i
+
+ c := make_vector(line[1],line[2])
+ p1 := line[1]
+ t_in := 0
+ t_out := 1
+
+ every i := 2 to *poly+1 do { # for each edge
+ p := poly[i-1]
+ if i > *poly then
+ n := normal_line(poly[i-1],poly[1])
+ else
+ n := normal_line(poly[i-1],poly[i])
+ D := dot(n,p)
+
+ if (nc := dot(n,c)) = 0 then { # parallel to edge
+ if not inside_line(p1,p,n) then {fail}
+ else next
+
+ }
+
+ t := (D - dot(n,p1))/nc
+
+ if nc > 0 then # entering polygon
+ t_in <:= t
+ else # exiting polygon
+ t_out >:= t
+
+ if t_in >= t_out then {fail}
+ }
+
+ # if we get here, part of the line is visible, return that part
+
+ cline := copy(line)
+ cline[1] := vpara(line[1],line[2],t_in)
+ cline[2] := vpara(line[1],line[2],t_out)
+
+ return cline
+end
+
+
+
+# - some interesting curves
+###
+
+############################################################################
+# Draw a fractal snowflake or order N between two points
+############################################################################
+#
+# Draw a fractal snowflake between two points
+#
+procedure fract_flake(win,A,C,n,lr,cp)
+ local direction, t
+
+ /lr := 1
+ direction := Rel_angle(A,C)
+ t := turtle(win, A, direction)
+ f_flake(t, distance(A,C), n, lr, cp)
+ return
+end
+
+procedure f_flake(t, len, n, lr, cp)
+ local angle, p, nextcolor
+
+ if n > 0 then {
+ # if nextcolor is available, change the foreground color
+ Fg ! ([t.win.vp.screen] ||| @\nextcolor)
+ Left(t,lr*60)
+ f_flake(t, len*0.333333, n-1, -lr, cp)
+ f_flake(t, len*0.333333, n-1, lr, cp)
+ Right(t,lr*60)
+ f_flake(t, len*0.333333, n-1, lr, cp)
+ Right(t,lr*60)
+ f_flake(t, len*0.333333, n-1, lr, cp)
+ Right(t,lr*150)
+ f_flake(t, len*0.19244, n-1, lr, cp)
+ f_flake(t, len*0.192498, n-1, -lr, cp)
+ Left(t,lr*60)
+ f_flake(t, len*0.192498, n-1, -lr, cp)
+ Left(t,lr*60)
+ f_flake(t, len*0.19244, n-1, -lr, cp)
+ Left(t,lr*90)
+ f_flake(t, len*0.333333, n-1, lr, cp)
+ Right(t,lr*150)
+ f_flake(t, len*0.19247, n-1, lr, cp)
+ f_flake(t, len*0.19247, n-1, -lr, cp)
+ Left(t,lr*150)
+ f_flake(t, len*0.333333, n-1, -lr, cp)
+ f_flake(t, len*0.333333, n-1, lr, cp)
+ }
+ else {
+ if \cp then {
+ angle := dtor(t.direction)
+ p := [t.pos[1]+len*cos(angle), t.pos[2]+len*sin(angle)]
+ DrawConvexClipped(t.win, cp, t.pos, p)
+ t.pos := p
+ }
+ else {
+ Line_Forward(t, len)
+ }
+ }
+
+ return
+end
+
+############################################################################
+# Draw a koch curve of order N between two points
+############################################################################
+#
+# Draw a koch curve from A to B
+#
+procedure koch_line(win,A,B,n)
+ local t, direction
+
+ direction := Rel_angle(A,B)
+ t := turtle(win, A, direction)
+ koch(t, direction, distance(A,B), n)
+ return
+end
+#
+# turtle graphics version
+#
+procedure koch(t, dir, len, n)
+
+ if n > 0 then {
+ koch(t, dir, len/3.0, n-1)
+ Left(t,60)
+ koch(t, dir, len/3.0, n-1)
+ Right(t, 120)
+ koch(t, dir, len/3.0, n-1)
+ Left(t,60)
+ koch(t, dir, len/3.0, n-1)
+ }
+ else
+ Line_Forward(t, len)
+
+ return
+end
+
+
+############################################################################
+# Draw a fractal curve between two points
+############################################################################
+#
+#
+# The parameter 'H' is a 'roughness' factor. At H=0.5,
+# you get roughly brownian motion.
+#
+procedure fract_line(win,A,B,H,min_len,std_dev)
+ local len_sq, direction, t, N, f, r, pt, len
+
+ /H := 0.5
+ /min_len := 0.01
+ /std_dev := 0.12
+ len := distance(A,B)
+ direction := Rel_angle(A,B)
+ t := turtle(win, A, direction)
+
+ if len <= min_len then
+ Line_Forward(t, len)
+ else {
+ f := exp((0.5-H)*log(2.0))
+ r := gauss() * std_dev * f
+ N := point()
+ N.x := 0.5*(A[1] + B[1]) - r*(B[2]-A[2]);
+ N.y := 0.5*(A[2] + B[2]) + r*(B[1]-A[1]);
+ fract_line(win, A, N, H, min_len, std_dev)
+ fract_line(win, N, B, H, min_len, std_dev)
+ }
+
+ return
+end
+
+
+
+# Simple drawing primitives
+############################################################################
+
+procedure DrwLine(w,pnts[]) # draw a polyline
+
+ if *pnts < 2 then fail # ... not enough points
+
+ return DrawLine ! ([w.vp.screen]|||transform_points(pnts,w.xform_mat[1]))
+end
+
+procedure DrawConvexClipped(w,poly,pnts[]) # clip to polygon
+ local i
+
+ if (*pnts < 2) | (*poly < 3) then fail
+
+ every i := 2 to *pnts do {
+ DrwLine ! ([w]|||Convex_clip(poly,pnts[i-1],pnts[i]))
+ }
+
+ return
+end
+
+procedure DrawPolygon(args[]) # draw a polygon
+
+ return DrwLine ! (args|||[args[2]])
+
+end
+
+procedure FillPolygon(w,pnts[]) # draw a filled polygon
+
+ if *pnts < 2 then fail # ... not enough points
+
+ return FillPolygon ! ([w.vp.screen]|||
+ transform_points(pnts|||[pnts[1]],w.xform_mat[1]))
+end
+
+
+
+# Matrix operations
+############################################################################
+
+# All matrices are stored as lists of lists, and all
+# operations determine the size of the matrix directly
+# from the matrix itself
+
+procedure mwrite(m) # output a matrix (usually for debugging)
+ local r, c, row, col
+
+ r := *m
+ c := *m[1]
+
+ writes("[")
+ every row := 1 to r do {
+ writes("[")
+ every col := 1 to c do {
+ writes(right(m[row][col],6),", ")
+ }
+ write("]")
+ }
+ write("]")
+end
+
+procedure newmat(n,m) # create a matrix
+ local M
+
+ M := list(n)
+ every !M := list(m)
+
+ return M
+end
+
+procedure Imatrix(n,m) # Identity matrix
+ local M, r, c
+
+ M := newmat(n,m)
+ every r := 1 to n do {
+ every c := 1 to m do {
+ M[r][c] := if r = c then 1.0 else 0.0
+ }
+ }
+ return M
+end
+
+procedure mmult(m1,m2) # matrix multiply
+ local m3, r, c, nk, k
+
+ if (nk := *m1[1]) ~= *m2 then stop("Matrices are wrong size to multiply")
+
+ m3 := newmat(*m1,*m2[1])
+ every r := 1 to *m1 do {
+ every c := 1 to *m2[1] do {
+ m3[r][c] := 0.0
+ every k := 1 to nk do {
+ m3[r][c] +:= m1[r][k] * m2[k][c]
+ }
+ }
+ }
+
+ return m3
+end
+
+
+# low-level screen activity
+############################################################################
+
+record viewport(ul, lr, screen)
+record window(ll, ur, vp, xform_mat)
+
+procedure set_window(win, ll, ur, vp) # construct new graphics window
+ local x_scale, y_scale, x_trans, y_trans, xfrm
+
+ if /vp then { # make vp the entire 'screen'
+ vp := viewport()
+ vp.ul := [0,0]
+ vp.lr := [numeric(WAttrib(win,"width")), numeric(WAttrib(win,"height"))]
+ vp.screen := win
+ }
+
+ # determine scale and translate factors ...
+ # (note the strange viewpoint references to get lower left corner)
+ x_scale := real(vp.lr[1]-vp.ul[1]) / (ur[1]-ll[1])
+ y_scale := real(vp.ul[2]-vp.lr[2]) / (ur[2]-ll[2])
+ x_trans := real(vp.ul[1])-(ll[1]*x_scale)
+ y_trans := real(vp.lr[2])-(ll[2]*y_scale)
+
+ # ... and set up the transformation matrix
+ xfrm := [mmult(set_scale(x_scale, y_scale), set_trans(x_trans, y_trans))]
+
+ return window(ll, ur, vp, xfrm)
+end
+
+procedure change_viewport(window, ul, lr)
+ local x_scale, y_scale, x_trans, y_trans, xfrm
+
+ # determine scale and translate factors ...
+ # (note the strange viewpoint references to get lower left corner)
+ x_scale := real(lr[1]-ul[1]) / (window.ur[1]-window.ll[1])
+ y_scale := real(ul[2]-lr[2]) / (window.ur[2]-window.ll[2])
+ x_trans := real(ul[1])-(window.ll[1]*x_scale)
+ y_trans := real(lr[2])-(window.ll[2]*y_scale)
+
+ # ... and set up the transformation matrix
+ xfrm := [mmult(set_scale(x_scale, y_scale), set_trans(x_trans, y_trans))]
+
+ window.xform_mat := xfrm
+ window.vp.ul := ul
+ window.vp.lr := lr
+
+ return
+end
+
+
+
+# support.icn -- miscellaneous support routines
+############################################################################
+
+# para -- parametric equation for coordinate between two others
+#
+procedure para(a,b,t)
+ return (1.0-t)*a + t*b
+end
+
+# vpara -- produce a vector that is parametrically between two others
+#
+procedure vpara(v1,v2,t)
+ local v, i
+
+ v := copy(v1)
+ every i := 1 to *v1 do
+ v[i] := para(v1[i],v2[i],t)
+
+ return v
+end
+
+# sleep -- 'sleep' of n seconds (n may be fractional)
+#
+procedure sleep(n)
+ local start
+
+ start := &time
+ while &time <= start+n*1000
+end
+
+procedure round(n,g)
+ return integer((n + g/2.0)/g) * g
+end
+
+# Some nice random functions
+
+# Do a Gaussian distribution about the value 'x'.
+# The value of 'f' can be used to alter the shape
+# of the Gaussian distribution (larger values flatten
+# the curve...)
+
+procedure Gauss_random(x,f)
+ # if 'f' not passed in, default to 1.0
+ /f := 1.0
+ return gauss()*f+x
+end
+
+# Produce a random value within a Gaussian distribution
+# about 0.0. (Sum 12 random numbers between 0 and 1,
+# (expected mean is 6.0) and subtract 6 to center on 0.0
+
+procedure gauss()
+ local v
+
+ v := 0.0
+ every 1 to 12 do v +:= ?0
+ return v-6.0
+end
+
+
+#
+# A simple implementation of 'turtle' graphics for multiple windows
+# one can have more than one turtle simultaneously active
+# In a turtle, the color field (if used) must be a co-expressions
+# that produces the color. This allows the turtle to change
+# color as it runs. In the simplest case, construct the
+# turtle with a co-expression the repeatedly supplies the
+# the same color: create |"red"
+############################################################################
+
+record turtle(win,pos,direction,color)
+
+procedure moveto(t,p)
+ return t.pos := p
+end
+
+procedure lineto(t,p)
+ Fg(t.win.vp.screen, \@\(t.color))
+ DrwLine(t.win, t.pos, p)
+ return t.pos := p
+end
+
+procedure moverel(t, displacement)
+ return moveto(t, add_vectors(t.pos, displacement))
+end
+
+procedure drawrel(t, displacement)
+ return lineto(t, add_vectors(t.pos, displacement))
+end
+
+procedure Line_Forward(t, dist)
+ local angle, p
+
+ angle := dtor(t.direction)
+ p := [t.pos[1]+dist*cos(angle), t.pos[2]+dist*sin(angle)]
+ return lineto(t, p)
+end
+
+procedure Move_Forward(t, dist)
+ local angle, p
+
+ angle := dtor(t.direction)
+ p := [t.pos[1]+dist*cos(angle), t.pos[2]+dist*sin(angle)]
+ return moveto(t, p)
+end
+
+procedure Right(t, angle)
+ return t.direction -:= angle
+end
+
+procedure Left(t, angle)
+ return t.direction +:= angle
+end
+
+
+
+# Some vector operations
+############################################################################
+
+procedure add_vectors(v1,v2)
+ local v3, i
+
+ if *v1 ~= *v2 then stop("cannot add vectors of differing sizes")
+
+ v3 := copy(v1)
+ every i := 1 to *v3 do
+ v3[i] := v1[i]+v2[i]
+
+ return v3
+end
+
+procedure sub_vectors(v1,v2)
+ local v3, i
+
+ if *v1 ~= *v2 then stop("cannot subtract vectors of differing sizes")
+
+ v3 := copy(v1)
+ every i := 1 to *v3 do
+ v3[i] := v1[i]-v2[i]
+
+ return v3
+end
+
+procedure scale_vector(s,a)
+ local v, i
+
+ v := copy(a)
+ every i := 1 to *v do
+ v[i] *:= s
+
+ return v
+end
+
+procedure len_vector(v)
+ local sum_sq
+
+ sum_sq := 0
+ every sum_sq +:= (!v)^2
+ return sqrt(sum_sq)
+end
+
+procedure unit_vector(v)
+ return scale_vector(1.0/len_vector(v), v)
+end
+
+procedure dot(v1,v2)
+ local sum, i
+
+ if *v1 ~= *v2 then stop("dot product: vectors of differing sizes")
+ sum := 0
+ every i := 1 to *v1 do
+ sum +:= v1[i]*v2[i]
+ return sum
+end
+
+procedure angle_vectors(v1,v2)
+ return rtod(acos(dot(unit_vector(v1),unit_vector(v2))))
+end
+
+procedure normal_vector(v)
+ local n
+
+ n := copy(v)
+ n[1] := v[2]
+ n[2] := -v[1]
+ return n
+end
+
+#
+# The following are special cases for points...
+#
+
+procedure make_vector(p1,p2)
+ return sub_vectors(p2,p1)
+end
+
+procedure distance(p1,p2)
+ return len_vector(sub_vectors(p2,p1))
+end
+
+procedure Rel_angle(A,B)
+ # get angle of line through points A and B (2D only!)
+ local rise, run
+
+ rise := B[2]-A[2]
+ run := B[1]-A[1]
+
+ return rtod(atan(rise, run))
+end
+
+procedure normal_line(p1,p2)
+ # return a normal to a line
+ return normal_vector(make_vector(p1,p2))
+end
+
+procedure inside_line(P,L,n)
+ # is P inside line passing through L with normal n?
+ return 0 <= dot(sub_vectors(P,L),n)
+end
+
+
+
+# Transformation operations
+############################################################################
+
+procedure transform(p,M)
+ local pl, i
+
+ # convert p to a matrix for matrix multiply...
+ every put((pl := [[]])[1], (!p)|1.0) # the 1.0 makes it homogeneous
+
+ # do the conversion...
+ pl := mmult(pl, M)
+
+ # convert list back to a point...
+ p := copy(p)
+ every i := 1 to *p do
+ p[i] := pl[1][i]
+
+ return p
+end
+
+procedure transform_points(pl,M)
+ local xformed
+
+ every put(xformed := [], !transform(!pl,M))
+ return xformed
+end
+
+procedure set_scale(x,y,z) # set up an Xform matrix for scaling
+ local M
+
+ M := if /z then Imatrix(3,3)
+ else Imatrix(4,4)
+
+ M[1][1] := x
+ M[2][2] := y
+ M[3][3] := \z
+
+ return M
+end
+
+procedure set_trans(x,y,z) # set up an Xform matrix for translation
+ local M
+
+ M := if /z then Imatrix(3,3)
+ else Imatrix(4,4)
+
+ M[*M][1] := x
+ M[*M][2] := y
+ M[*M][3] := \z
+
+ return M
+end
+
+procedure set_rotate(x,y,z) # set up an Xform matrix for rotation
+ local X, Y, Z
+
+ if /y & /z then { # 2-D rotation
+ X := Imatrix(3,3)
+ X[1][1] := cos(x)
+ X[2][2] := X[1][1]
+ X[1][2] := sin(x)
+ X[2][1] := -X[1][2]
+ return X
+ }
+
+ X := Imatrix(4,4)
+ X[2][2] := cos(x)
+ X[3][3] := X[2][2]
+ X[2][3] := sin(x)
+ X[3][2] := -X[2][3]
+
+ Y := Imatrix(4,4)
+ Y[1][1] := cos(y)
+ Y[3][3] := Y[1][1]
+ Y[3][1] := sin(y)
+ Y[1][3] := -Y[3][1]
+
+ Z := Imatrix(4,4)
+ Z[1][1] := cos(z)
+ Z[2][2] := Z[2][2]
+ Z[1][2] := sin(z)
+ Z[2][1] := -Z[1][2]
+
+ return mmult(X,mmult(Y,Z))
+end
+
+#
+# Generalized parametric curve drawing routine, using turtle t
+#
+procedure draw_curve(t,x,xa,y,ya,t1,t2,N)
+ local incr, t0
+
+ /t1 := 0.0
+ /t2 := 1.0
+ /N := 500
+
+ incr := (t2-t1)/(N-1)
+
+ t0 := t1
+ moveto(t, point( x!([t0]|||xa), y!([t0]|||ya)))
+ every 1 to N-1 do {
+ t0 +:= incr
+ lineto(t, point( x!([t0]|||xa), y!([t0]|||ya)))
+ }
+
+end
diff --git a/ipl/gprocs/gpxlib.icn b/ipl/gprocs/gpxlib.icn
new file mode 100644
index 0000000..7c994c9
--- /dev/null
+++ b/ipl/gprocs/gpxlib.icn
@@ -0,0 +1,130 @@
+############################################################################
+#
+# File: gpxlib.icn
+#
+# Subject: Procedures for graphics tasks
+#
+# Author: Gregg M. Townsend
+#
+# Date: August 21, 1998
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# This file contains a few eclectic graphics procedures.
+#
+# ScratchCanvas(w, h, id) creates a temporary, hidden window.
+#
+# PushWin(L) adds a default window to an argument list.
+#
+# Distance(x1, y1, x2, y2) computes the distance between two points.
+#
+# InBounds(x, y, w, h) succeeds if (&x,&y) is within (x,y,w,h).
+#
+############################################################################
+#
+# The following procedure allows an additional first argument
+# specifying a window to use instead of &window:
+#
+# ScratchCanvas(w, h, id) returns a hidden-canvas window for temporary
+# use. The same scratch window (per display) is returned by successive
+# calls with the same ID, avoiding the cost of creation. The size is
+# guaranteed to be at least (w, h), which default to the size of the
+# window. The scratch window must not be closed by the caller, but an
+# EraseArea can be done to reclaim any allocated colors.
+#
+############################################################################
+#
+# The following procedures do not accept a window argument:
+#
+# PushWin(L) pushes &window onto the front of list L if the first
+# element of the list is not a window. This aids in constructing
+# variable-argument procedures with an optional window argument.
+#
+# Distance(x1, y1, x2, y2) returns the distance between two points
+# as a real number.
+#
+# InBounds(x, y, w, h) checks whether &x and &y are within the given
+# region: it returns &null if x <= &x <= x+w and y <= &y <= y+h,
+# and fails otherwise.
+#
+############################################################################
+#
+# Links: wopen
+#
+############################################################################
+#
+# Requires: Version 9 graphics
+#
+############################################################################
+
+link wopen
+
+
+# PushWin(L) -- push &window on list if no window already there.
+
+procedure PushWin(a)
+ static type
+
+ initial type := proc("type", 0) # protect attractive name
+
+ if not (type(a[1]) == "window") then
+ push(a, &window)
+ return a
+end
+
+
+# Distance(x1, y1, x2, y2) -- compute distance between two points.
+
+procedure Distance(x1, y1, x2, y2) #: distance between two points
+ x1 -:= x2
+ y1 -:= y2
+ return sqrt(x1 * x1 + y1 * y1)
+end
+
+
+# InBounds(x, y, w, h) -- succeed if (&x,&y) is in a rectangular area.
+
+procedure InBounds(x, y, w, h) #: check point within rectangle
+ if w < 0 then
+ x -:= (w := -w)
+ if h < 0 then
+ y -:= (h := -h)
+ return (x <= &x <= x + w) & (y <= &y <= y + h) & &null
+end
+
+
+# ScratchCanvas([win,] w, h, id) -- return hidden window for temporary use.
+
+procedure ScratchCanvas(win, w, h, id) #: return scratch canvas
+ local d, s
+ static dpytab, type
+
+ initial {
+ dpytab := table()
+ type := proc("type", 0) # protect attractive name
+ }
+
+ if type(win) ~== "window" then {
+ win :=: w :=: h :=: id
+ win := &window
+ }
+ /w := WAttrib(win, "width")
+ /h := WAttrib(win, "height")
+ w <:= 100 # if too teeny, can't open
+ h <:= 100
+
+ d := WAttrib(win, "display")
+ s := d || "," || image(id)
+ /dpytab[s] := WOpen("width=" || w, "height=" || h, "canvas=hidden",
+ "display=" || d)
+ win := dpytab[s]
+ if /win then
+ fail
+ if WAttrib(win, "width") < w | WAttrib(win, "height") < h then
+ WAttrib(win, "width=" || w, "height=" || h)
+ return win
+end
diff --git a/ipl/gprocs/gpxop.icn b/ipl/gprocs/gpxop.icn
new file mode 100644
index 0000000..5767868
--- /dev/null
+++ b/ipl/gprocs/gpxop.icn
@@ -0,0 +1,314 @@
+############################################################################
+#
+# File: gpxop.icn
+#
+# Subject: Procedures for graphics operations
+#
+# Author: Gregg M. Townsend
+#
+# Date: May 26, 1999
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# This file contains some graphics procedures.
+#
+# LeftString(x, y, s) draws a string left-aligned at (x, y).
+#
+# CenterString(x, y, s) draws a string centered at (x, y).
+#
+# RightString(x, y, s) draws a string right-aligned at (x, y).
+#
+# ClearOutline(x, y, w, h) draws a rectangle, erasing its interior.
+#
+# Translate(dx, dy, w, h) moves the window origin and optionally
+# sets the clipping region.
+#
+# Zoom(x1, y1, w1, h1, x2, y2, w2, h2)
+# copies and distorts a rectangle.
+#
+# Capture(p, x, y, w, h) converts a window area to an image string.
+#
+# Sweep() lets the user select a rectangular area.
+#
+############################################################################
+#
+# LeftString(x, y, s), CenterString(x, y, s), and RightString(x, y, s)
+# draw a string centered vertically about y and left-justified,
+# centered, or right-justified about x.
+#
+# ClearOutline(x, y, w, h) draws a rectangle in the foreground color
+# and fills it with the background color.
+#
+# Translate(dx, dy, w, h) adjusts a window's dx and dy attributes by
+# the values given. Note that the resulting attribute values are the
+# sums of the existing values with the parameters, so that successive
+# translations accumulate. If w and h are supplied, the clipping
+# region is set to a rectangle of size (w, h) at the new origin.
+#
+# Zoom(x1, y1, w1, h1, x2, y2, w2, h2) is a distorting variation of
+# CopyArea that can be used to shrink or enlarge a rectangular area.
+# Zero, one, or two window arguments can be supplied. Rectangle 1 is
+# copied to fill rectangle 2 using simple pixel sampling and replication.
+# The rectangles can overlap. The usual defaults apply for both rectangles.
+#
+# Sweep() lets the user select a rectangular area using the mouse.
+# Called when a mouse button is pressed, Sweep handles all subsequent
+# events until a mouse button is released. As the mouse moves, a
+# reverse-mode outline rectangle indicates the selected area. The
+# pixels underneath the rectangle outline are considered part of this
+# rectangle, implying a minimum width/height of 1, and the rectangle
+# is clipped to the window boundary. Sweep returns a list of four
+# integers [x,y,w,h] giving the rectangle bounds in canonical form
+# (w and h always positive). Note that w and h give the width as
+# measured in FillRectangle terms (number of pixels included) rather
+# than DrawRectangle terms (coordinate difference).
+#
+# Capture(palette, x, y, w, h) converts a window region into an
+# image string using the specified palette, and returns the string.
+#
+# These procedures all accept an optional initial window argument.
+#
+############################################################################
+#
+# Links: gpxlib
+#
+############################################################################
+#
+# Requires: Version 9 graphics
+#
+############################################################################
+
+link gpxlib
+
+
+# LeftString(x, y, s) -- draw string left-justified at (x,y).
+
+procedure LeftString(win, x, y, s) #: draw left-justified string
+ static type
+
+ initial type := proc("type", 0) # protect attractive name
+ if type(win) ~== "window" then {
+ win :=: x :=: y :=: s
+ win := &window
+ }
+ y +:= (WAttrib(win, "ascent") - WAttrib(win, "descent")) / 2 + 1
+ return DrawString(win, x, y, s)
+end
+
+
+# CenterString(x, y, s) -- draw string centered about (x,y).
+
+procedure CenterString(win, x, y, s) #: draw centered string
+ static type
+
+ initial type := proc("type", 0) # protect attractive name
+ if type(win) ~== "window" then {
+ win :=: x :=: y :=: s
+ win := &window
+ }
+ x -:= TextWidth(win, s) / 2
+ y +:= (WAttrib(win, "ascent") - WAttrib(win, "descent")) / 2 + 1
+ return DrawString(win, x, y, s)
+end
+
+
+# RightString(x, y, s) -- draw string right-justified at (x,y).
+
+procedure RightString(win, x, y, s) #: draw right-justified string
+ static type
+
+ initial type := proc("type", 0) # protect attractive name
+ if type(win) ~== "window" then {
+ win :=: x :=: y :=: s
+ win := &window
+ }
+ x -:= TextWidth(win, s)
+ y +:= (WAttrib(win, "ascent") - WAttrib(win, "descent")) / 2 + 1
+ return DrawString(win, x, y, s)
+end
+
+
+# ClearOutline(x, y, w, h) -- draw rectangle and fill background.
+
+procedure ClearOutline(win, x, y, w, h) #: draw and clear rectangle
+ static type
+
+ initial type := proc("type", 0) # protect attractive name
+ if type(win) ~== "window" then {
+ win :=: x :=: y :=: w :=: h
+ win := &window
+ }
+
+ /x := -WAttrib(win, "dx")
+ /y := -WAttrib(win, "dy")
+ /w := WAttrib(win, "width") - (x + WAttrib(win, "dx"))
+ /h := WAttrib(win, "height") - (y + WAttrib(win, "dy"))
+
+ if w < 0 then
+ x -:= (w := -w)
+ if h < 0 then
+ y -:= (h := -h)
+
+ DrawRectangle(win, x, y, w, h)
+ EraseArea(win, x+1, y+1, w-1, h-1)
+ return win
+end
+
+
+# Translate(dx, dy, w, h) -- add translation and possibly clipping.
+
+procedure Translate(win, dx, dy, w, h) #: add translation
+ static type
+
+ initial type := proc("type", 0) # protect attractive name
+ if type(win) ~== "window" then {
+ win :=: dx :=: dy :=: w :=: h
+ win := &window
+ }
+ WAttrib(win, "dx=" || WAttrib(win,"dx")+dx, "dy=" || WAttrib(win,"dy")+dy)
+ Clip(win, 0, 0, \w, \h)
+ return win
+end
+
+
+# Sweep() -- sweep out area with mouse, return bounds
+
+procedure Sweep(win) #: sweep area with mouse
+ local x, y, w, h, wmin, wmax, hmin, hmax
+
+ /win := &window
+ win := Clone(win, "drawop=reverse")
+
+ x := &x # set initial rect bounds
+ y := &y
+ w := h := 0
+
+ wmin := -WAttrib(win, "dx") - x # calc coordinate limits
+ hmin := -WAttrib(win, "dy") - y
+ wmax := wmin + WAttrib(win, "width") - 1
+ hmax := hmin + WAttrib(win, "height") - 1
+
+ DrawRectangle(win, x, y, w, h) # draw initial bounding rect
+ until Event(win) === (&lrelease | &mrelease | &rrelease) do {
+ DrawRectangle(win, x, y, w, h) # erase old bounds
+ w := &x - x # calc new width & height
+ h := &y - y
+ w <:= wmin # clip to stay on window
+ w >:= wmax
+ h <:= hmin
+ h >:= hmax
+ DrawRectangle(win, x, y, w, h) # draw new bounds
+ }
+ DrawRectangle(win, x, y, w, h) # erase bounding rectangle
+
+ if w < 0 then x -:= (w := -w) # ensure nonnegative sizes
+ if h < 0 then y -:= (h := -h)
+
+ Uncouple(win)
+ return [x, y, w + 1, h + 1] # return FillRectangle bounds
+end
+
+
+# Zoom(win1, win2, x1, y1, w1, h1, x2, y2, w2, h2) -- copy and distort.
+
+procedure Zoom(args[]) #: zoom image
+ local win1, x1, y1, w1, h1
+ local win2, x2, y2, w2, h2
+ local x, y, scr
+ static type
+
+ initial type := proc("type", 0) # protect attractive name
+
+ if type(args[1]) == "window" then
+ win1 := get(args)
+ else
+ win1 := \&window | runerr(140, &window)
+ if type(args[1]) == "window" then
+ win2 := get(args)
+ else
+ win2 := win1
+
+ x1 := \get(args) | -WAttrib(win1, "dx")
+ y1 := \get(args) | -WAttrib(win1, "dy")
+ w1 := \get(args) | WAttrib(win1, "width") - (x1 + WAttrib(win1, "dx"))
+ h1 := \get(args) | WAttrib(win1, "height") - (y1 + WAttrib(win1, "dy"))
+ if w1 < 0 then
+ x1 -:= (w1 := -w1)
+ if h1 < 0 then
+ y1 -:= (h1 := -h1)
+
+ x2 := \get(args) | -WAttrib(win2, "dx")
+ y2 := \get(args) | -WAttrib(win2, "dy")
+ w2 := \get(args) | WAttrib(win2, "width") - (x2 + WAttrib(win2, "dx"))
+ h2 := \get(args) | WAttrib(win2, "height") - (y2 + WAttrib(win2, "dy"))
+ if w2 < 0 then
+ x2 -:= (w2 := -w2)
+ if h2 < 0 then
+ y2 -:= (h2 := -h2)
+
+ if w1 = 0 | w2 = 0 | h1 = 0 | h2 = 0 then
+ return
+
+ scr := ScratchCanvas(win2, w2, h1, "__Zoom__") | fail
+ every x := 0 to w2 - 1 do
+ CopyArea(win1, scr, x1 + w1 * ((x + 0.5) / w2), y1, 1, h1, x, 0)
+ every y := 0 to h2 - 1 do
+ CopyArea(scr, win2, 0, h1 * ((y + 0.5) / h2), w2, 1, x2, y2 + y)
+
+ EraseArea(scr) # release colors
+ return win1
+end
+
+
+# Capture(win, pal, x, y, w, h) -- capture screen region as image string
+
+$define CaptureChunk 100
+
+procedure Capture(win, pal, x, y, w, h) #: capture image as string
+ local a, c, k, s, t, cmap
+ static type
+
+ initial type := proc("type", 0) # protect attractive name
+
+ if type(win) ~== "window" then {
+ win :=: pal :=: x :=: y :=: w :=: h
+ win := \&window | runerr(140, &window)
+ }
+
+ /pal := "c1"
+
+ /x := -WAttrib(win, "dx")
+ /y := -WAttrib(win, "dy")
+ /w := WAttrib(win, "width") - (x + WAttrib(win, "dx"))
+ /h := WAttrib(win, "height") - (y + WAttrib(win, "dy"))
+
+ if w < 0 then
+ x -:= (w := -w)
+ if h < 0 then
+ y -:= (h := -h)
+
+ PaletteChars(win, pal) | runerr(205, pal)
+
+ cmap := table()
+
+ # accumulate the image in chunks and then concatenate
+ # (much faster than concatenating single chars on a very long string)
+ s := ""
+ a := []
+ every k := Pixel(win, x, y, w, h) do {
+ c := \cmap[k] | (cmap[k] := PaletteKey(win, pal, k))
+ if *(s ||:= c) >= CaptureChunk then {
+ put(a, s)
+ s := ""
+ }
+ }
+ put(a, s)
+
+ s := w || "," || pal || ","
+ while s ||:= get(a)
+ return s
+end
diff --git a/ipl/gprocs/graphics.icn b/ipl/gprocs/graphics.icn
new file mode 100644
index 0000000..66cd20d
--- /dev/null
+++ b/ipl/gprocs/graphics.icn
@@ -0,0 +1,34 @@
+############################################################################
+#
+# File: graphics.icn
+#
+# Subject: Procedures for graphics
+#
+# Author: Gregg M. Townsend
+#
+# Date: August 4, 2000
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# Links to core subset of graphics procedures.
+#
+############################################################################
+#
+# Links: bevel, color, dialog, enqueue, gpxop, gpxlib,
+# vidgets, window, wopen
+#
+############################################################################
+
+link bevel
+link color
+link dialog
+link enqueue
+link gpxop
+link gpxlib
+link vidgets # basic set needed by Dialog() and Vset()
+link window
+link wopen
diff --git a/ipl/gprocs/grecords.icn b/ipl/gprocs/grecords.icn
new file mode 100644
index 0000000..612c1fe
--- /dev/null
+++ b/ipl/gprocs/grecords.icn
@@ -0,0 +1,36 @@
+############################################################################
+#
+# File: grecords.icn
+#
+# Subject: Declarations for graphics
+#
+# Author: Ralph E. Griswold
+#
+# Date: July 27, 1996
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# These declarations are used in procedures that manipulate objects
+# in two- and three-dimensional space.
+#
+############################################################################
+
+record point2(x, y)
+
+record vector2(x, y)
+
+record box2(p2min, p2max)
+
+record point3(x, y, z)
+
+record vector3(x, y, z)
+
+record box3(p3min, p3max)
+
+record rect(x, y, w, h)
+
+record line(x1, y1, x2, y2)
diff --git a/ipl/gprocs/gtrace.icn b/ipl/gprocs/gtrace.icn
new file mode 100644
index 0000000..7e10c85
--- /dev/null
+++ b/ipl/gprocs/gtrace.icn
@@ -0,0 +1,203 @@
+############################################################################
+#
+# File: gtrace.icn
+#
+# Subject: Procedures to process graphic traces
+#
+# Author: Ralph E. Griswold
+#
+# Date: November 19, 1997
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# As used here, the term "trace" refers to a sequence of points that
+# generally consists of locations on a curve or other geometrical object.
+# These procedures process such traces in various ways.
+#
+############################################################################
+#
+# See also: gtraces.doc
+#
+############################################################################
+#
+# Links: calls, numbers, gobject
+#
+############################################################################
+
+link calls
+link numbers
+link gobject
+
+# list_coords(call) lists the coordinates of the trace produced by
+# invoke(call)
+
+procedure list_coords(call, p, w)
+ local point
+
+ /p := 6
+ /w := 20
+
+ every point := invoke(call) do
+ write(decipos(point.x, p, w), decipos(point.y, p, w))
+
+end
+
+#
+# point_list(call, i) returns a list of the points in the trace produced
+# by invoke(call). If i is nonnull, the list is limited to i points.
+
+procedure point_list(call, i)
+ local plist
+
+ plist := []
+
+ if \i then {
+ every put(plist, invoke(call)) \ i
+ }
+ else {
+ every put(plist, invoke(call))
+ }
+
+ return plist
+
+end
+
+#
+# coord_list(call, i) returns a list of the x,y coordinates in the trace
+# produced by invoke(call). If i is nonnull, the list is limited
+# to i points.
+
+procedure coord_list(call, limit)
+ local clist
+
+ clist := []
+
+ if \limit then {
+ every put(clist, !(invoke(call))) \ (limit * 2)
+ }
+ else {
+ every put(clist, !(invoke(call)))
+ }
+
+ return clist
+
+end
+
+# read_trace(f) produces a trace from the coordinate file f
+
+procedure read_trace(f)
+ local line
+ static schar
+
+ initial schar := &digits ++ '.'
+
+ while line := read(f) do
+ line ? {
+ suspend Point(
+ tab(upto(schar)) & tab(many(schar)),
+ tab(upto(schar)) & tab(many(schar))
+ )
+ }
+
+end
+
+# write_trace(header, call) writes a trace file from the trace of call.
+
+procedure write_trace(header, call)
+ local point
+
+ write(header, ":")
+
+ every point := invoke(call) do
+ write(point.x, " ", point.y)
+
+end
+
+# compose_trace(call_1, call_2) composes the trace for call_1 with the
+# trace for call_2; that is, the trace for call_1 is passed through
+# call_2. For example, if call_1 traces a circle and call_2 draws a
+# star, the result is a star on each point of the circle.
+#
+# The procedure assumes that the first two arguments to call_2 are
+# the x and y coordinates of the point in which it is interested
+# (standard trace format).
+
+procedure compose_trace(trace, call_1, call_2)
+ local point
+
+ every point := invoke(call_1) do {
+ call_2.args[1] := point.x # set the origin for call_2
+ call_2.args[2] := point.y
+ suspend invoke(call_2)
+ }
+
+end
+
+# tcompress(call, i) discards all but the ith points on the trace
+# produced by call. The first point of the trace is the first
+# point of the trace produced by calls.
+
+procedure tcompress(call, i)
+ local j, point
+
+ j := 0
+
+ every point := invoke(call) do {
+ if j % i = 0 then suspend point
+ i +:= 1
+ }
+
+end
+
+# interp_call(call) inserts a point midway on a line between every two points
+# on the trace produced by call.
+
+procedure interp_trace(call)
+ local point, last_point
+
+ every point := invoke(call) do {
+ if \last_point then {
+ suspend last_point
+ suspend Point(
+ (point.x - last_point.x) / 2,
+ (point.y - last_point.y) / 2
+ )
+ }
+ last_point := point
+ }
+
+ suspend last_point
+
+end
+
+# coord2point(cl) creates a list of points from a list of coordinates.
+# It destroys cl.
+
+procedure coord2point(cl)
+ local pl
+
+ pl := []
+
+ while put(pl, Point(get(cl), get(cl)))
+
+ return pl
+
+end
+
+# point2coord(pl) creates a list of coordinates from a list of points.
+# It does not destroy pl.
+
+procedure point2coord(pl)
+ local cl
+
+ cl := []
+
+ every put(cl, !!pl)
+
+ return cl
+
+end
diff --git a/ipl/gprocs/ifg.icn b/ipl/gprocs/ifg.icn
new file mode 100644
index 0000000..433f68f
--- /dev/null
+++ b/ipl/gprocs/ifg.icn
@@ -0,0 +1,33 @@
+############################################################################
+#
+# File: ifg.icn
+#
+# Subject: Procedure to tell if graphics are running
+#
+# Author: Ralph E. Griswold
+#
+# Date: June 14 1994
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# ifg() fails if (a) the running version of Icon does not support
+# graphics, or (b) if it is, the graphics system is not running.
+#
+############################################################################
+
+procedure ifg()
+ local win
+
+ if (&features == "graphics") &
+ win := open("", "x", "canvas=hidden") then {
+ close(win)
+ return
+ }
+
+ else fail
+
+end
diff --git a/ipl/gprocs/imagedim.icn b/ipl/gprocs/imagedim.icn
new file mode 100644
index 0000000..3b5a718
--- /dev/null
+++ b/ipl/gprocs/imagedim.icn
@@ -0,0 +1,64 @@
+############################################################################
+#
+# File: imagedim.icn
+#
+# Subject: Procedures for getting image dimensions
+#
+# Author: Ralph E. Griswold
+#
+# Date: May 2, 2001
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# imagedim(s) returns a record that contains the type and dimensions of an
+# image named s.
+#
+# The assumptions about image formats are naive.
+#
+############################################################################
+#
+# Requires: Version 9 graphics
+#
+############################################################################
+
+record idim(type, w, h)
+
+procedure imagedim(s)
+ local Image, line, dim
+
+ Image := open(s) | stop("*** cannot open ", s)
+
+ line := read(Image) | idim_bad()
+ line ? {
+ if tab(find("width") + 6) then {
+ dim := idim("xbm")
+ dim.w := integer(tab(0)) | idim_bad()
+ read(Image) ? {
+ tab(find("height") + 7) | idim_bad()
+ dim.h := integer(tab(0)) | idim_bad()
+ } | idim_bad()
+ }
+ else if find("XPM") then {
+ dim := idim("xpm")
+ read(Image) | idim_bad()
+
+ read(Image) ? {
+ ="\"" & dim.w := integer(tab(many(&digits))) &
+ =" " & dim.h := integer(tab(many(&digits)))
+ } | idim_bad()
+ }
+ }
+
+# close(Image)
+
+ return dim
+
+end
+
+procedure idim_bad()
+ stop("*** bad image data")
+end
diff --git a/ipl/gprocs/imageseq.icn b/ipl/gprocs/imageseq.icn
new file mode 100644
index 0000000..ba42ff6
--- /dev/null
+++ b/ipl/gprocs/imageseq.icn
@@ -0,0 +1,60 @@
+############################################################################
+#
+# File: imageseq.icn
+#
+# Subject: Procedure to write sequences of images
+#
+# Author: Ralph E. Griswold
+#
+# Date: December 26, 1994
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# These procedures provide help for applications that write sequences
+# of images.
+#
+# seq_init(opts) initializes the naming parameters from the table opts.
+# opts["n"] is the name, opts["f"] is the first number, and opts["c"]
+# is the number of columns for the serial number.
+#
+# save_image(win, x, y, w, h) write the specified area of win using the
+# next name in sequence. There is no check for duplicate names if the
+# numbering wraps around.
+#
+############################################################################
+#
+# Requires: Version 9 graphics
+#
+############################################################################
+
+global prefix__ # hope for no collisions
+global count__
+global width__
+
+procedure seq_init(opts)
+
+ prefix__ := if /opts | /opts["n"] then "image" else opts["n"]
+ count__ := if /opts | /opts["f"] then 0 else opts["f"] - 1
+ width__ := if /opts | /opts["c"] then 3 else opts["c"]
+
+ return
+
+end
+
+procedure save_image(win, x, y, w, h)
+
+ initial seq_init(/prefix__) # initialize if prefix__ null.
+
+ if type(win) ~== "window" then {
+ win :=: x :=: y :=: w :=: h
+ win := &window
+ }
+
+ return WriteImage(win, prefix__ || right(count__ +:= 1, width__, "0") ||
+ ".gif", x, y, w, h)
+
+end
diff --git a/ipl/gprocs/imgcolor.icn b/ipl/gprocs/imgcolor.icn
new file mode 100644
index 0000000..39bba90
--- /dev/null
+++ b/ipl/gprocs/imgcolor.icn
@@ -0,0 +1,36 @@
+############################################################################
+#
+# File: imgcolor.icn
+#
+# Subject: Procedure to produce table of colors in area
+#
+# Author: Ralph E. Griswold
+#
+# Date: January 5, 1995
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# This procedure produces a table of all the colors in a specified
+# area of a window. The value corresponding to a color key is
+# the number of pixels with that color
+#
+############################################################################
+#
+# Requires: Version 9 graphics
+#
+############################################################################
+
+procedure imgcolor(win, x, y, w, h)
+ local colors
+
+ colors := table(0)
+
+ every colors[Pixel(win, x, y, w, h)] +:= 1
+
+ return colors
+
+end
diff --git a/ipl/gprocs/imrutils.icn b/ipl/gprocs/imrutils.icn
new file mode 100644
index 0000000..97b8c34
--- /dev/null
+++ b/ipl/gprocs/imrutils.icn
@@ -0,0 +1,332 @@
+############################################################################
+#
+# File: imrutils.icn
+#
+# Subject: Procedures to deal with image records
+#
+# Author: Ralph E. Griswold
+#
+# Date: January 23, 2002
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# Procedures to manipulate image strings as records.
+#
+# imrcath(imr1, imr2)
+# concatenates imr1 and imr2 horizontally
+#
+# imrcatv(imr1, imr2)
+# concatenates imr1 and imr2 vertically
+#
+# imrcopy(imr) create copy of imr
+#
+# imrdraw(win, x, y, imr)
+# draws an image record
+#
+# imrfliph(imr) flips an image record horizontally
+#
+# imrflipv(imr) flips an image record vertically
+#
+# imrnegative(imr)
+# produces "negative" of image; intended for
+# grayscale palettes
+#
+# imropen(imr) opens a hidden window with an image record
+#
+# imror(imr) forms inclusive "or" of two images
+#
+# imrpshift(imr, ir)
+# shifts colors by mapping rotated palette
+#
+# imrrot180(imr)
+# rotates an image record 180 degrees
+#
+# imrrot90cw(imr)
+# rotates an image record 90 degrees clockwise
+#
+# imrshifth(imr, i)
+# shifts an image record horizontally by i pixels
+# with wrap-around; positive i to the right,
+# negative to the left.
+#
+# imrshiftv(imr, i)
+# shifts an image record vertically by i pixels
+# with wrap-around; positive i to the top,
+# negative to the bottom.
+#
+# imstoimr(s) converts an image string to an image record
+#
+# imrtoims(imr) converts an image record to an image string
+#
+# Note: All the procedures that produce image records modify their
+# argument records; they do not return modified copies.
+#
+############################################################################
+#
+# Possible additions:
+#
+# Make stripes from one (or more) rows/columns.
+#
+# Convert from one palette to another.
+#
+############################################################################
+#
+# Requires: Version 9 graphics
+#
+############################################################################
+#
+# Links: strings, wopen
+#
+############################################################################
+
+link strings
+link wopen
+
+record ImageRecord(width, palette, pixels)
+
+procedure imrcath(imr1, imr2) #: horizontally concatenate image records
+ local imr, i, rows1, rows2
+
+ if *imr1.pixels / imr1.width ~= *imr2.pixels / imr2.width then fail
+ if imr1.palette ~== imr2.palette then fail
+
+ imr := ImageRecord()
+ imr.width := imr1.width + imr2.width
+ imr.palette := imr1.palette
+
+ rows1 := []
+
+ imr1.pixels ? {
+ while put(rows1, move(imr1.width))
+ }
+
+ rows2 := []
+
+ imr2.pixels ? {
+ while put(rows2, move(imr2.width))
+ }
+
+ imr.pixels := ""
+
+ every i := 1 to *rows1 do
+ imr.pixels ||:= rows1[i] || rows2[i]
+
+ return imr
+
+end
+
+procedure imrcatv(imr1, imr2) #: vertically concatenate image records
+ local imr
+
+ if imr1.width ~= imr2.width then fail
+ if imr1.palette ~== imr2.palette then fail
+
+ imr := ImageRecord()
+ imr.width := imr1.width
+ imr.palette := imr1.palette # CHECK
+ imr.pixels := imr1.pixels || imr2.pixels
+
+ return imr
+
+end
+
+procedure imrcopy(imr)
+
+ return copy(imr)
+
+end
+
+procedure imrdraw(win, x, y, imr) #: draw image record
+
+ if type(win) ~== "window" then {
+ win :=: x :=: y :=: imr
+ win := \&window | runerr(140, &window)
+ }
+
+ /x := 0
+ /y := 0
+
+ return DrawImage(win, x, y, imrtoims(imr))
+
+end
+
+procedure imrflipd(imr) #: flip image record diagonally
+ local height, columns, i, row
+
+ height := *imr.pixels / imr.width
+ columns := list(height, "")
+
+ imr.pixels ? {
+ while row := move(imr.width) do
+ every i := 1 to imr.width do
+ columns[i] ||:= row[i]
+ }
+
+ imr.pixels := ""
+
+ every imr.pixels ||:= !columns
+
+ imr.width := height
+
+ return imr
+
+end
+
+procedure imrfliph(imr) #: flip image record horizontally
+ local pixels
+
+ pixels := ""
+
+ imr.pixels ? {
+ while pixels ||:= reverse(move(imr.width))
+ }
+
+ imr.pixels := pixels
+
+ return imr
+
+end
+
+procedure imrflipv(imr) #: flip image record vertically
+ local pixels
+
+ pixels := ""
+
+ imr.pixels ? {
+ while pixels := move(imr.width) || pixels
+ }
+
+ imr.pixels := pixels
+
+ return imr
+
+end
+
+procedure imrnegative(imr) #: form negative of image record
+ local chars
+
+ chars := PaletteChars(imr.palette)
+
+ imr.pixels := map(imr.pixels, chars, reverse(chars))
+
+ return imr
+
+end
+
+procedure imropen(imr) #: open window with image record
+ local win
+
+ win := WOpen("canvas=hidden","size=" || imr.width || "," ||
+ *imr.pixels / imr.width)
+
+ imrdraw(win, 0, 0, imr) | {
+ WClose(win)
+ fail
+ }
+
+ return win
+
+end
+
+procedure imrpshift(imr, i) #: map shifted palette
+ local chars
+
+ chars := PaletteChars(imr.palette)
+
+ imr.pixels := map(imr.pixels, chars, rotate(chars, i))
+
+ return imr
+
+end
+
+procedure imrrot180(imr) #: rotate image record 180 degrees
+
+ imr.pixels := reverse(imr.pixels)
+
+ return imr
+
+end
+
+procedure imrrot90cw(imr) #: rotate image record 90 deg. clockwise
+ local height, columns, i, row
+
+ height := *imr.pixels / imr.width
+ columns := list(imr.width, "")
+
+ imr.pixels ? {
+ while row := move(imr.width) do
+ every i := 1 to imr.width do
+ columns[i] := row[i] || columns[i]
+ }
+
+ imr.pixels := ""
+
+ every imr.pixels ||:= !columns
+
+ imr.width := height
+
+ return imr
+
+end
+
+# Note: Since shifted out pixels enter in the top or bottom row, depending
+# on the direction of the shift, one full pass over the width raises the
+# image one pixel.
+
+procedure imrshifth(imr, i) #: shift image record horizontally
+
+ imr.pixels := rotate(imr.pixels, i)
+
+ return imr
+
+end
+
+# See note on imrshifth()
+
+procedure imrshiftv(imr, i) #: shift image record vertically
+
+ /i := 1
+
+ imr.pixels := rotate(imr.pixels, i * imr.width)
+
+ return imr
+
+end
+
+procedure imrtoims(imr) #: convert image record to image string
+
+ return imr.width || "," || imr.palette || "," || imr.pixels
+
+end
+
+procedure imstoimr(s) #: convert image string to image record
+ local imr
+
+ imr := ImageRecord()
+
+ s ? {
+ imr.width := tab(upto(',')) | fail
+ move(1)
+ imr.palette := tab(upto(',')) | fail
+ move(1)
+ imr.pixels := tab(0)
+ }
+
+ return imr
+
+end
+
+procedure imror(imr) #: form inclusive "or" of two images
+ local chars
+
+ chars := PaletteChars(imr.palette)
+
+ imr.pixels := map(imr.pixels, chars, reverse(chars))
+
+ return imr
+
+end
diff --git a/ipl/gprocs/imscanon.icn b/ipl/gprocs/imscanon.icn
new file mode 100644
index 0000000..2c1c16f
--- /dev/null
+++ b/ipl/gprocs/imscanon.icn
@@ -0,0 +1,61 @@
+############################################################################
+#
+# File: imscanon.icn
+#
+# Subject: Procedure to put bi-level image string in canonical form
+#
+# Author: Ralph E. Griswold
+#
+# Date: August 6, 1994
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# This procedure puts a bi-level image string in canonical form so
+# that duplicates up to shifting can be eliminated. It is intended to
+# be used in imlreduc.icn, which handles the rotational case.
+#
+# It presently only handles widths that are a multiple of four.
+#
+############################################################################
+#
+# Requires: Large integers
+#
+############################################################################
+#
+# Links: strings
+#
+############################################################################
+
+link strings
+
+procedure imscanon(ims)
+ local head, spec, dspec, max, val, imax, i, width
+
+ ims ? {
+ head := tab(upto('#~') + 1)
+ spec := tab(0)
+ }
+
+ head ? {
+ width := tab(many(&digits))
+ }
+
+ if (width % 4) ~= 0 then return ims # one digit for 4 columns
+ width /:= 4
+ if (*spec % width) ~= 0 then return ims # must be even number of digits
+
+ dspec := spec || spec
+ max := -1
+ every i := 1 to (*spec / width) do {
+ val := integer("16r" || dspec[1 +: *spec])
+ if max <:= val then imax := (((i - 1) * width) + 1)
+ dspec := rotate(dspec, width)
+ }
+
+ return head || dspec[imax +: *spec]
+
+end
diff --git a/ipl/gprocs/imscolor.icn b/ipl/gprocs/imscolor.icn
new file mode 100644
index 0000000..8910d32
--- /dev/null
+++ b/ipl/gprocs/imscolor.icn
@@ -0,0 +1,423 @@
+############################################################################
+#
+# File: imscolor.icn
+#
+# Subject: Procedures for manipulating images
+#
+# Author: Gregg M. Townsend
+#
+# Date: December 25, 1999
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# These procedures manipulate image strings.
+#
+# imswidth(im) returns the width of an image.
+# imsheight(im) returns the height of an image.
+# imspalette(im) returns the palette used by an image.
+#
+# imsmap(s1, s2, s3) applies map() to the image data.
+#
+# imswrite(f, s, n) writes an image string to a file.
+#
+# drawpalette(W, p, x, y, w, h, f, n) draws the color palette p.
+#
+# pickpalette(W, p, dx, dy, w, h, n) maps window coordinates
+# to a palette drawn by drawpalette().
+#
+# XPMImage(W, f, p) reads an XPM file, returning an image string.
+#
+############################################################################
+#
+# imswidth(im) returns the width of an image.
+# imsheight(im) returns the height of an image.
+# imspalette(im) returns the palette used by an image.
+#
+# imsmap(s1, s2, s3) returns an image produced by mapping the data (only)
+# of image s1 and replacing characters found in s2 with corresponding
+# characters from s3.
+#
+# imswrite(f, s, n) writes image string s to file f, limiting the line
+# length to n characters. Defaults are f = &output, n = 79. Extra
+# punctuation in s makes the lines break at nonsensical places, but
+# the output is still legal.
+#
+# drawpalette([win,] p, x, y, w, h, f, n) draws the colors of palette
+# p in the given rectangular region. n columns are used; if n is
+# omitted, a layout is chosen based on the palette name and size. The
+# layout algorithm works best when the height is two to four times
+# the width. Characters in the flag string f have these meanings:
+# l label each color with its key
+# o outline each color in black
+# u unframed use: don't hash unused cells at end
+#
+# pickpalette([win,] p, dx, dy, w, h, n) returns the character at (dx,dy)
+# within a region drawn by drawpalette(win, p, x, y, w, h, f, n).
+#
+# XPMImage([win,] f, palette) reads an XPM (X Pixmap) format image from
+# the open file f and returns an Icon image specification that uses the
+# specified palette. XPMImage() fails if it cannot decode the file.
+# If f is omitted, &input is used; if palette is omitted, "c1" is used.
+# Not all variants of XPM format are handled; in particular, images that
+# use more than one significant input character per pixel, or that use
+# the old XPM Version 1 format, cause XPMImage() to fail. No window
+# is required, but X-specific color names like "papayawhip" will not
+# be recognized without a window.
+#
+############################################################################
+#
+# Links: graphics
+#
+############################################################################
+#
+# Requires: Version 9 graphics
+#
+############################################################################
+
+link graphics
+
+
+# imspalette(im) -- return palette used by image
+
+procedure imspalette(im) #: palette for image
+ im ? {tab(upto(',') + 1) & return ((="#" & &null) | tab(upto(',')))}
+end
+
+
+# imswidth(im) -- return width of image
+
+procedure imswidth(im) #: width of image
+ im ? return integer(tab(upto(',')))
+end
+
+
+# imsheight(im) -- return height of image
+
+procedure imsheight(im) #: height of image
+ local pal, w, n, d, c
+
+ im ? {
+ w := integer(tab(upto(','))) | fail
+ move(1)
+ if ="#" then {
+ n := IMH_Count('0123456789ABCDEFabcdef')
+ d := (w + 3) / 4
+ return (n + d - 1) / d
+ }
+ pal := tab(upto(',')) | fail
+ move(1)
+ c := cset(PaletteChars(pal)) | fail
+ n := IMH_Count(c ++ '~\xFF')
+ return (n + w - 1) / w
+ }
+end
+
+procedure IMH_Count(c) # count remaining chars that are in cset c
+ local n
+
+ n := 0
+ while tab(upto(c)) do
+ n +:= *tab(many(c))
+ return n
+end
+
+
+# imsmap(s1, s2, s3) -- map the data (only) of an image string
+
+procedure imsmap(s1, s2, s3) #: map data of image string
+ s1 ? return tab(upto(',')+1) || tab(upto(',')+1) || map(tab(0), s2, s3)
+end
+
+
+# imswrite(f, s, n) -- write image string s to file f, max linelength of n.
+
+procedure imswrite(f, s, n) #: write image string
+ local w, h, p, d, ll
+
+ w := imswidth(s) | fail
+ h := imsheight(s) | fail
+ p := imspalette(s) | fail
+
+ if /p then # if bilevel image
+ d := (w + 3) / 4 # number of digits per row
+ else
+ d := w
+
+ /f := &output
+ /n := 79
+
+ # Figure out a reasonable line length for output, with n as maximum
+ n -:= 1 # allow for underscore
+ if upto('\0', PaletteChars(\p)) then
+ n /:= 4 # allow for escapes
+ ll := 1 + (n > (d - 1) / seq(1)) # divide line as equally as possible
+
+ # Write the image as a multiline string constant.
+ s ? {
+ tab(upto(',') + 1)
+ ="#" | tab(upto(',') + 1)
+ write(f, "\"", w, ",", (\p || ",") | "#", "_")
+ while not pos(0) do IWR_Row(f, move(d) | tab(0), ll)
+ write(f, "\"")
+ }
+ return
+end
+
+procedure IWR_Row(f, s, n) # write one row, max n bytes per line
+ s ? while not pos(0) do
+ write(f, image(move(n) | tab(0)) [2:-1], "_")
+ return
+end
+
+
+# drawpalette(win, p, x, y, w, h, f, n) -- draw palette in region
+
+procedure drawpalette(win, p, x, y, w, h, f, n) #: draw palette
+ local nh, c, s, colr, x1, x2, y1, y2, i, j, ret
+ static cs
+ initial cs := &ascii[33+:95] -- '\\'
+
+ if type(win) ~== "window" then {
+ win :=: p :=: x :=: y :=: w :=: h :=: f :=: n
+ win := \&window | runerr(140, &window)
+ }
+ win := Clone(win, "fg=black")
+ ret := win
+
+ /p := "c1"
+ /f := ""
+
+ /x := -WAttrib(win, "dx")
+ /y := -WAttrib(win, "dy")
+ /w := WAttrib(win, "width") - (x + WAttrib(win, "dx"))
+ /h := WAttrib(win, "height") - (y + WAttrib(win, "dy"))
+
+ if w < 0 then
+ x -:= (w := -w)
+ if h < 0 then
+ y -:= (h := -h)
+
+ s := PAL_Order(p) | fail
+ /n := PAL_Columns(p, s, w, h)
+ nh := (*s + n - 1) / n
+
+ EraseArea(win, x, y, w, h)
+ if f ? upto('o') then {
+ w -:= 1
+ h -:= 1
+ }
+
+ i := j := 0
+ every c := !s do {
+ x1 := x + j * w / n
+ x2 := x + (j + 1) * w / n
+ y1 := y + i * h / nh
+ y2 := y + (i + 1) * h / nh
+ Fg(win, colr := PaletteColor(p, c)) | (ret := &null)
+ FillRectangle(win, x1, y1, x2 - x1, y2 - y1)
+ if upto('l', f) then {
+ Fg(win, Contrast(win, colr))
+ if not upto(cs, c) then
+ c := image(c)[-3:-1]
+ CenterString(win, (x1 + x2) / 2, (y1 + y2) / 2, c)
+ }
+ if upto('o', f) then {
+ Fg(win, "black")
+ DrawRectangle(win, x1, y1, x2 - x1, y2 - y1)
+ }
+ if (j +:= 1) >= n then {
+ j := 0
+ i +:= 1
+ }
+ }
+
+ # if some cells are unfilled, and the 'u' flag is not given,
+ # hash the unfilled cells with a diagonal pattern.
+ if j > 0 & not upto('u', f) then {
+ x1 := x + j * w / n
+ y1 := y + i * h / nh
+ x2 := x + w
+ y2 := y + h
+ WAttrib(win, "fg=black", "pattern=diagonal", "fillstyle=textured")
+ FillRectangle(win, x1, y1, x2 - x1, y2 - y1)
+ if upto('o', f) then {
+ WAttrib(win, "fillstyle=solid")
+ DrawRectangle(win, x1, y1, x2 - x1, y2 - y1)
+ }
+ }
+
+ Uncouple(win)
+ return \ret
+end
+
+
+# pickpalette(win, p, dx, dy, w, h, n) -- return key picked from drawn palette
+
+procedure pickpalette(win, p, dx, dy, w, h, n) #: key from drawn palette
+ local s, nw, nh
+
+ if type(win) ~== "window" then {
+ win :=: p :=: dx :=: dy :=: w :=: h :=: n
+ win := \&window | runerr(140, &window)
+ }
+ /w := WAttrib(win, "width")
+ /h := WAttrib(win, "height")
+ if dx < 0 | dy < 0 | dx >= w | dy >= h then
+ fail
+
+ s := PAL_Order(p) | fail
+ /n := PAL_Columns(p, s, w, h)
+ nh := (*s + n - 1) / n
+
+ dx := ((dx + 1) * n - 1) / w
+ dy := ((dy + 1) * nh - 1) / h
+ return s[1 + n * dy + dx]
+end
+
+
+# PAL_Columns(p, s, w, h) -- calc columns for auto-layout (internal routine)
+#
+# p is palette name; s is character string; w,h are available dimensions
+
+procedure PAL_Columns(p, s, w, h)
+ local nw, nh
+
+ return case p of {
+ "c1": return 6
+ "c2": return 2
+ "c3": return 3
+ "c4": return 4
+ "c5": return 5
+ "c6": return 6
+ default: {
+ nw := integer(w / sqrt(w * h / *s))
+ nw <:= 1
+ nh := (*s + nw - 1) / nw
+ nh <:= 1
+ return (*s + nh - 1) / nh
+ }
+ }
+end
+
+
+# PAL_Order(p) -- return reordered palette chars (internal routine)
+#
+# Normal order for color cube is sorted r/g/b, then extra grays.
+# Reorder by g/r/b followed by full set of grays, including duplicates,
+# back to black. Returns unmodified list of characters for c1 and
+# grayscale palettes.
+
+procedure PAL_Order(p)
+ local palchars, s, t, n, n3, i, l
+
+ palchars := PaletteChars(p) | fail
+
+ p ? {
+ if not (="c" & any('23456')) then return palchars
+ n := integer(move(1))
+ }
+
+ palchars ? {
+
+ l := list(n, "")
+ n3 := n * n * n
+ while &pos <= n3 do
+ every !l ||:= (move(n) \ 1)
+ s := ""
+ every s ||:= !l # build g/r/b cube portion
+
+ t := ""
+ every i := 1 to (n3 - 1) by (n * (n + 1) + 1) do
+ t ||:= palchars[i] || move(n - 1)
+ }
+
+ return s || reverse(t)
+end
+
+
+# XPMImage(win, f, palette) -- read XPM file and return Icon image spec
+
+procedure XPMImage(win, f, pal) #: image string for XPM file
+ local w, h, nc, cpp, i, im, c, k, s1, s2
+
+ if type(win) ~== "window" then {
+ win :=: f :=: pal
+ win := &window # okay if null
+ }
+ /f := &input
+ /pal := "c1"
+ type(f) == "file" | runerr(105, f)
+ PaletteChars(pal) | runerr(205, f)
+
+ (read(f) ? find("XPM")) | fail
+ (XPM_RdStr(f) | fail) ? {
+ tab(many(' \t')); w := tab(many(&digits)) | fail
+ tab(many(' \t')); h := tab(many(&digits)) | fail
+ tab(many(' \t')); nc := tab(many(&digits)) | fail
+ tab(many(' \t')); cpp := tab(many(&digits)) | fail
+ }
+ if w = 0 | h = 0 then
+ fail
+
+ # read colors and figure out translation
+ s1 := s2 := ""
+ every i := 1 to nc do (XPM_RdStr(f) | fail) ? {
+ s1 ||:= move(1)
+ if cpp > 1 then
+ =" " | fail # if not blank, we can't handle it
+ k := &null
+ # find a color key we can decipher; try color, then grayscale, then mono
+ (c := !"cgm") & tab(upto(' \t') + 1) & =c & tab(many(' \t')) &
+ (k := XPM_Key(win, pal, (tab(upto(' \t') | 0))))
+ # use first color found, or default if none
+ s2 ||:= \k | PaletteKey(pal, "gray")
+ }
+
+ # construct image
+ im := w || "," || pal || ","
+ if cpp = 1 then
+ while im ||:= map(XPM_RdStr(f), s1, s2)
+ else
+ while im ||:= map(XPM_Nth(XPM_RdStr(f), cpp), s1, s2)
+ return im
+end
+
+procedure XPM_Key(win, pal, s) # return key corresponding to color s
+
+ if s == "None" then { # if transparent
+ if PaletteColor(pal, "~") then # if "~" is in palette
+ return "\xFF" # then use "\xFF" for transparent
+ else
+ return "~" # but use "~" if possible
+ }
+
+ if \win then
+ return PaletteKey(win, pal, s) # return key from palette, or fail
+ else
+ return PaletteKey(pal, s) # return key from palette, or fail
+end
+
+procedure XPM_RdStr(f) # read next C string from file f
+ local line, s
+
+ while line := read(f) do line ? {
+ tab(many(' \t'))
+ ="\"" | next
+ if s := tab(upto('"')) then
+ return s
+ }
+ fail
+end
+
+procedure XPM_Nth(s, n) # concatenate every nth character from s
+ local t
+ n -:= 1
+ t := ""
+ s ? while t ||:= move(1) do
+ move(n)
+ return t
+end
diff --git a/ipl/gprocs/imsutils.icn b/ipl/gprocs/imsutils.icn
new file mode 100644
index 0000000..2f45db1
--- /dev/null
+++ b/ipl/gprocs/imsutils.icn
@@ -0,0 +1,607 @@
+############################################################################
+#
+# File: imsutils.icn
+#
+# Subject: Procedures to manipulate image specifications
+#
+# Author: Ralph E. Griswold
+#
+# Date: May 2, 2001
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# This file contains procedures that manipulate string representations for
+# images.
+#
+# patident(imx1, imx2)
+# XDrawTile(win, xoff, yoff, pattern, magnif, mode)
+# XDrawRows(win, xoff, yoff, imx, magnif, mode)
+# bits2hex(s)
+# decspec(pattern)
+# getpatt(line)
+# getpattnote(line)
+# hex2bits(s)
+# hexspec(pattern)
+# legalpat(tile)
+# legaltile(tile)
+# pat2xbm(pattern, name)
+# tilebits(imx)
+# pdensity(pattern)
+# pix2pat(window, x, y, cols, rows)
+# readims(input)
+# readimsline(input)
+# rowbits(pattern)
+# imstoimx(ims)
+# imxtoims(imx)
+# showbits(pattern)
+# tiledim(pattern)
+# pheight(pattern)
+# pwidth(pattern)
+# xbm2rows(input)
+#
+############################################################################
+#
+# Requires: Version 8.11 graphics
+#
+############################################################################
+#
+# Links: convert
+#
+############################################################################
+
+$include "xnames.icn"
+
+link convert
+
+record tdim(w, h)
+
+#
+# Test whether two image matrices are equivalent
+
+procedure patident(imx1, imx2)
+ local i
+
+ if *imx1 ~= *imx2 then fail
+ if **imx1 ~= **imx2 then fail
+
+ every i := 1 to *imx1 do
+ if imx1[i] ~== imx2[1] then fail
+
+ return imx2
+
+end
+#
+# Draw a tile at a given location. If mode is nonnull, the
+# area on which the tile is drawn is erased.
+
+procedure XDrawTile(win, xoff, yoff, pattern, magnif, mode)
+ local x, y, row, pixel, dims, arglist
+
+ if type(win) ~== "window" then {
+ win :=: xoff :=: yoff :=: pattern :=: mode
+ win := &window
+ }
+
+ if magnif = 1 then XDrawImage(win, xoff, yoff, pattern, mode)
+ else {
+ if \mode then {
+ dims := tiledim(pattern)
+ XEraseArea(xoff, yoff, dims.w * magnif, dims.h * magnif)
+ }
+ y := yoff
+ every row := rowbits(pattern) do { # draw a row
+ x := xoff
+ arglist := []
+ every pixel := !row do {
+ if pixel = "1" then put(arglist, x, y, magnif, magnif)
+ x +:= magnif
+ }
+ y +:= magnif
+ if *arglist = 0 then next
+ XFillRectangle ! arglist
+ }
+ }
+
+ return
+
+end
+#
+# Draw image matrix at a given location. If mode is nonnull, the
+# area on which the tile is drawn is erased.
+
+procedure XDrawRows(win, xoff, yoff, imx, magnif, mode)
+ local x, y, row, pixel, arglist
+
+ if type(win) ~== "window" then {
+ win :=: xoff :=: yoff :=: imx :=: magnif :=: mode
+ win := &window
+ }
+
+ /magnif := 1
+
+ y := yoff
+
+ if \mode then
+ XEraseArea(xoff, yoff, *imx[1] * magnif, *imx * magnif)
+
+ every row := !imx do { # draw a row
+ x := xoff
+ arglist := []
+
+ if magnif = 1 then {
+ every pixel := !row do {
+ if pixel == "1" then put(arglist, x, y)
+ x +:= 1
+ }
+ y +:= 1
+ }
+ else {
+ every pixel := !row do {
+ if pixel = "1" then put(arglist, x, y, magnif, magnif)
+ x +:= magnif
+ }
+ y +:= magnif
+ }
+ if *arglist = 0 then next
+ if magnif = 1 then XDrawPoint ! arglist else XFillRectangle ! arglist
+ }
+
+ return
+
+end
+
+#
+# Convert bit string to hex pattern string
+
+procedure bits2hex(s)
+ static bittab
+ local hex
+
+ initial {
+ bittab := table()
+ bittab["0000"] := "0"
+ bittab["1000"] := "1"
+ bittab["0100"] := "2"
+ bittab["1100"] := "3"
+ bittab["0010"] := "4"
+ bittab["1010"] := "5"
+ bittab["0110"] := "6"
+ bittab["1110"] := "7"
+ bittab["0001"] := "8"
+ bittab["1001"] := "9"
+ bittab["0101"] := "a"
+ bittab["1101"] := "b"
+ bittab["0011"] := "c"
+ bittab["1011"] := "d"
+ bittab["0111"] := "e"
+ bittab["1111"] := "f"
+ }
+
+ hex := ""
+
+ s ? {
+ while hex := bittab[move(4)] || hex
+ if not pos(0) then hex := bittab[left(tab(0), 4, "0")] || hex
+ }
+
+ return hex
+
+end
+
+#
+# Convert pattern specification to decimal form
+
+procedure decspec(pattern)
+ local cols, chunk, dec
+
+ pattern ? {
+ if not upto("#") then return pattern
+ cols := tab(upto(','))
+ move(2)
+ chunk := (cols + 3) / 4
+ dec := cols || ","
+ while dec ||:= integer("16r" || move(chunk)) || ","
+ }
+
+ return dec[1:-1]
+
+end
+
+#
+# Get pattern from line. It trims off leading and trailing whitespace
+# and removes any annotation (beginning with a # after the first whitespace
+
+procedure getpatt(line)
+
+ line ? {
+ tab(many(' \t'))
+ return tab(upto(' \t') | 0)
+ }
+
+end
+
+#
+# Get pattern annotation. It returns an empty string if there is
+# no annotation.
+
+procedure getpattnote(line)
+
+ line ? {
+ tab(many(' \t')) # remove leading whitespace
+ tab(upto(' \t')) | return "" # skip pattern
+ tab(upto('#')) | return "" # get to annotation
+ tab(many('# \t')) # get rid of leading junk
+ return tab(0) # annotation
+ }
+
+end
+
+# Convert hexadecimal string to bits
+
+procedure hex2bits(s)
+ static hextab
+ local bits
+
+ initial {
+ hextab := table()
+ hextab["0"] := "0000"
+ hextab["1"] := "0001"
+ hextab["2"] := "0010"
+ hextab["3"] := "0011"
+ hextab["4"] := "0100"
+ hextab["5"] := "0101"
+ hextab["6"] := "0110"
+ hextab["7"] := "0111"
+ hextab["8"] := "1000"
+ hextab["9"] := "1001"
+ hextab["a"] := "1010"
+ hextab["b"] := "1011"
+ hextab["c"] := "1100"
+ hextab["d"] := "1101"
+ hextab["e"] := "1110"
+ hextab["f"] := "1111"
+ }
+
+ bits := ""
+
+ map(s) ? {
+ while bits ||:= hextab[move(1)]
+ }
+
+ return bits
+
+end
+
+#
+# Convert pattern to hexadecimal form
+
+procedure hexspec(pattern)
+ local cols, chunk, hex
+
+ pattern ? {
+ if find("#") then return pattern
+ cols := tab(upto(','))
+ move(1)
+ chunk := (cols + 3) / 4
+ hex := cols || ",#"
+ while hex ||:= right(exbase10(tab(upto(',') | 0), 16), chunk, "0") do
+ move(1) | break
+ }
+
+ return hex
+
+end
+
+#
+# Succeed if tile is legal and small enough for (X) pattern. Other
+# windows systems may be more restrictive.
+
+procedure legalpat(tile)
+
+ if not legaltile(tile) then fail
+
+ tile ? {
+ if 0 < integer(tab(upto(','))) <= 32 then return tile
+ else fail
+ }
+
+end
+
+#
+# Succeed if tile is legal. Accepts tiles that are too big for
+# patterns.
+
+procedure legaltile(tile)
+
+ map(tile) ? { # first check syntax
+ (tab(many(&digits)) & =",") | fail
+ if ="#" then (tab(many('0123456789abcdef')) & pos(0)) | fail
+ else {
+ while tab(many(&digits)) do {
+ if pos(0) then break # okay; end of string
+ else ="," | fail
+ }
+ if not pos(0) then fail # non-digit
+ }
+ }
+
+ return hexspec(decspec(tile)) == tile
+
+end
+
+#
+# Convert pattern specification to an XBM image file.
+
+procedure pat2xbm(pattern, name)
+ local dims, chunk, row
+
+ /name := "noname"
+
+ dims := tiledim(pattern)
+
+
+ write("#define ", name, "_width ", dims.w)
+ write("#define ", name, "_height ", dims.h)
+ write("static char ", name, "_bits[] = {")
+
+ chunk := (dims.w + 3) / 4
+
+ pattern ? {
+ tab(upto('#') + 1)
+ while row := move(chunk) do {
+ if *row % 2 ~= 0 then row := "0" || row
+ row ? {
+ tab(0)
+ while writes("0x", move(-2), ",")
+ }
+ write()
+ }
+ }
+
+ write("};")
+
+end
+
+#
+# Count the number of bits set in a tile
+
+procedure tilebits(imx)
+ local bits
+
+ bits := 0
+
+ every bits +:= !!imx
+
+ return bits
+
+end
+
+#
+# Compute density (percentage of black bits) of pattern
+
+procedure pdensity(pattern)
+
+ local dark, dims
+
+ dims := tiledim(pattern)
+
+ hexspec(pattern) ? {
+ dark := 0
+ every rowbits(pattern) ? {
+ every upto('1') do
+ dark +:= 1
+ }
+ return dark / real(dims.w * dims.h)
+ }
+
+end
+
+#
+# Procedure to produce pattern specification from a section of a window.
+
+procedure pix2pat(window, x, y, cols, rows)
+ local c, tile, pattern, pixels, y0
+
+ pattern := ""
+
+ every y0 := 0 to rows - 1 do {
+ pixels := ""
+ every c := Pixel(window, x, y0 + y, cols, 1) do
+ pixels ||:= (if c == "0,0,0" then "1" else "0")
+ pattern ||:= bits2hex(pixels)
+ }
+
+ if *pattern = 0 then fail # out of bounds specification
+ else return cols || ",#" || pattern
+
+end
+
+#
+# Read pattern. It skips lines starting with a #,
+# empty lines, and trims off any trailing characters after the
+# first whitespace of a pattern.
+
+procedure readims(input)
+ local line
+
+ while line := read(input) do
+ line ? {
+ if pos(0) | ="#" then next
+ return tab(upto(' \t') | 0)
+ }
+
+ fail
+
+end
+
+#
+# Read pattern line. It skips lines starting with a # and empty lines but
+# does not trim off any trailing characters after the first whitespace of
+# a pattern.
+
+procedure readimsline(input)
+ local line
+
+ while line := read(input) do
+ line ? {
+ if pos(0) | ="#" then next
+ return tab(0)
+ }
+
+ fail
+
+end
+
+#
+# Generate rows of bits in a pattern. Doesn't work correctly for small
+# patterns. (Why?)
+
+procedure rowbits(pattern)
+ local row, dims, chunk, hex
+
+ dims := tiledim(pattern)
+
+ hexspec(pattern) ? {
+ tab(upto(',') + 2)
+ hex := tab(0)
+ chunk := *hex / dims.h
+ hex ? {
+ while row := right(hex2bits(move(chunk)), dims.w, "0") do
+ suspend reverse(row)
+ }
+ }
+
+end
+
+#
+# Produce an image matrix from a image string
+
+procedure imstoimx(ims)
+ local imx
+
+ imx := []
+
+ every put(imx, rowbits(ims))
+
+ return imx
+
+end
+
+#
+# Convert row list to pattern specification
+
+procedure imxtoims(imx)
+ local pattern
+
+ pattern := *imx[1] || ",#"
+
+ every pattern ||:= bits2hex(!imx)
+
+ return pattern
+
+end
+
+# Show bits of a pattern
+
+procedure showbits(pattern)
+
+ every write(rowbits(pattern))
+
+ write()
+
+ return
+
+end
+
+
+#
+# Produce dimensions of the tile for a pattern
+
+procedure tiledim(pattern)
+ local cols
+
+ hexspec(pattern) ? {
+ cols := integer(tab(upto(',')))
+ move(2)
+ return tdim(cols, *tab(0) / ((cols + 3) / 4))
+ }
+
+end
+
+#
+# Produce height of a pattern specification
+
+procedure pheight(pattern)
+ local cols
+
+ hexspec(pattern) ? {
+ cols := integer(tab(upto(',')))
+ move(2)
+ return *tab(0) / ((cols + 3) / 4)
+ }
+
+end
+
+#
+# Produce width of a pattern specification
+
+procedure pwidth(pattern)
+
+ hexspec(pattern) ? {
+ return integer(tab(upto(',')))
+ }
+
+end
+
+#
+# Generate rows of bits from an XBM file. Note: This apparently
+# is not quite right if there are more than 2 hex digits per
+# literal.
+
+procedure xbm2rows(input)
+ local imagex, bits, row, hex, width, height, chunks
+ static hexdigit
+
+ initial hexdigit := &digits ++ 'abcdef'
+
+ imagex := ""
+
+ read(input) ? {
+ tab(find("width") + 6)
+ tab(upto(&digits))
+ width := integer(tab(many(&digits)))
+ }
+
+ read(input) ? {
+ tab(find("height") + 6)
+ tab(upto(&digits))
+ height := integer(tab(many(&digits)))
+ }
+
+ chunks := (width / 8) + if (width % 8) > 0 then 1 else 0
+
+ while imagex ||:= reads(input, 500000) # Boo! -- can do better
+
+ imagex ? {
+ every 1 to height do {
+ row := ""
+ every 1 to chunks do {
+ (hex := tab(any(hexdigit)) || tab(any(hexdigit))) | {
+ tab(find("0x") + 2)
+ hex := move(2)
+ }
+ row ||:= case hex of {
+ "00": "00000000"
+ "ff": "11111111"
+ default: reverse(right(hex2bits(hex), 8, "0"))
+ }
+ }
+ suspend left(row, width)
+ }
+ }
+
+end
diff --git a/ipl/gprocs/imutils.icn b/ipl/gprocs/imutils.icn
new file mode 100644
index 0000000..e638bf0
--- /dev/null
+++ b/ipl/gprocs/imutils.icn
@@ -0,0 +1,21 @@
+############################################################################
+#
+# File: imutils.icn
+#
+# Subject: Declarations to link graphics utilities
+#
+# Author: Gregg M. Townsend
+#
+# Date: October 11, 1994
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+
+link imscolor
+link color
+link gpxop
+link gpxlib
+link wopen
diff --git a/ipl/gprocs/imxform.icn b/ipl/gprocs/imxform.icn
new file mode 100644
index 0000000..80df6ca
--- /dev/null
+++ b/ipl/gprocs/imxform.icn
@@ -0,0 +1,488 @@
+############################################################################
+#
+# File: imxform.icn
+#
+# Subject: Procedures to transform image matrices
+#
+# Author: Ralph E. Griswold
+#
+# Date: June 10, 2001
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# This file contains procedures that manipulate matrices that represent
+# images.
+#
+############################################################################
+#
+# Requires: Version 8.11, graphics
+#
+############################################################################
+#
+# Links: factors, imsutils, random, strings
+#
+############################################################################
+
+link factors
+link imsutils
+link random
+link strings
+
+#
+# Reduces a image matrix to the smallest equivalent one.
+
+procedure imxreduce(rows)
+
+ rows := imxcollap(rows)
+ rows := imxrotate(rows, 90)
+ rows := imxcollap(rows)
+ rows := imxrotate(rows, -90)
+
+ return rows
+
+end
+
+procedure imxcollap(rows)
+ local size, fact
+
+ size := *rows
+ every fact := !pfactors(size) do {
+ while rowdupl(rows, fact) do {
+ size /:= fact
+ rows := rows[1+:size]
+ }
+ }
+
+ return rows
+
+end
+
+procedure rowdupl(rows, n)
+ local span, i, j
+
+ if *rows % n ~= 0 then fail
+
+ span := *rows / n
+
+ every i := 1 to n - 1 do
+ every j := 1 to span do
+ if rows[j] ~== rows[i * span + j] then fail
+
+ return
+
+end
+
+#
+# Produces the inclusive "or" of two image matrices.
+
+procedure imxor(rows1, rows2)
+ local i, j
+
+ if (*rows1 ~= *rows2) | (**rows1 ~= **rows2) then fail
+
+ rows1 := copy(rows1)
+
+ every i := 1 to *rows1 do
+ every j := upto('1', rows2[i]) do
+ rows1[i][j] := "1"
+
+ return rows1
+
+end
+
+#
+# Produces the "and" of two image matrices.
+
+procedure imxand(rows1, rows2)
+ local i, j
+
+ if (*rows1 ~= *rows2) | (**rows1 ~= **rows2) then fail
+
+ rows1 := copy(rows1)
+
+ every i := 1 to *rows1 do
+ every j := upto('0', rows2[i]) do
+ rows1[i][j] := "0"
+
+ return rows1
+
+end
+
+#
+# Produces the exclusive "or" of two image matrices.
+
+procedure imxxor(rows1, rows2)
+ local i, j
+
+ if (*rows1 ~= *rows2) | (**rows1 ~= **rows2) then fail
+
+ rows1 := copy(rows1)
+
+ every i := 1 to *rows1 do
+ every j := 1 to **rows1 do
+ rows1[i][j] := if rows1[i][j] == rows2[i][j] then "0" else "1"
+
+ return rows1
+
+end
+
+#
+# Scrambles a image matrix by shuffling it. If dir is "h", the columns of each row
+# are scrambled; if "v", the the rows are scrambled. If "b", bits are
+# scrambled throughout the image matrix.
+
+procedure imxscramb(rows, dir)
+ local i, all
+
+ case dir of {
+ "h": {
+ every i := 1 to *rows do
+ rows[i] := shuffle(rows[i])
+ }
+ "v": rows := shuffle(rows)
+ "b" | &null: {
+ all := ""
+ every all ||:= !rows
+ all := shuffle(all)
+ every i := 1 to *rows do {
+ rows[i] := left(all, *rows[1])
+ all[1 +: *rows[1]] := ""
+ }
+ }
+ default: stop("*** illegal specification in scramble()")
+ }
+
+ return rows
+
+end
+
+#
+# Create bit-shifted copy of an image matrix. If dir is "h", then the
+# shift is horizontal; if "v", vertical. The default is horizontal.
+# Positive shift is to the right for horizontal shifts, downward for vertical
+# shifts. The default shift is 0 and the default direction is horizontal.
+
+procedure imxshift(rows, shift, dir)
+ local i
+
+ /shift := 0
+
+ rows := copy(rows)
+
+ case dir of {
+ "h" | &null: { # horizontal shift
+ every i := 1 to *rows do
+ rows[i] := rotate(rows[i], -shift)
+ }
+ "v": { # vertical shift
+ if shift > 0 then
+ every 1 to shift do
+ push(rows, pull(rows))
+ else if shift < 0 then
+ every 1 to -shift do
+ put(rows, pop(rows))
+ }
+ default: stop("*** illegal specification in imxshift()")
+ }
+
+ return rows
+
+end
+
+#
+# Place a border around a image matrix. l, r, t, and b specify the number of bits
+# to add at the left, right, top, and bottom, respectively. c specifies
+# the color of the border, "0" for white, "1" for black.
+
+procedure imxborder(rows, l, r, t, b, c)
+ local i, row, left, right
+
+ /l := 1
+ /r := 1
+ /t := 1
+ /b := 1
+ /c := "0"
+
+ if l = r = t = b = 0 then return rows
+
+ row := repl(c, *rows[1] + l + r)
+ left := repl(c, l)
+ right := repl(c, r)
+
+ every i := 1 to *rows do
+ rows[i] := left || rows[i] || right
+
+ every 1 to t do
+ push(rows, row)
+
+ every 1 to b do
+ put(rows, row)
+
+ return rows
+
+end
+
+#
+# Crop a image matrix. l, r, t, and b specify the number of bits
+# to crop at the left, right, top, and bottom, respectively.
+
+procedure imxcrop(rows, l, r, t, b)
+ local i
+
+ /l := 0
+ /r := 0
+ /t := 0
+ /b := 0
+
+ if l = r = t = b = 0 then return rows
+
+ if ((*rows[1] - l - r) | (*rows - t - b)) < 4 then fail
+
+ every 1 to t do
+ get(rows)
+
+ every 1 to b do
+ pull(rows)
+
+ every i := 1 to *rows do
+ rows[i] := rows[i][l + 1 : -r]
+
+ return rows
+
+end
+
+# Creates a tile in every other pixel is discarded. dir determines the
+# direction is which the halving is done. If dir is "b" or null, it's
+# done both vertically and horizontally. If dir is "v", it's only done
+# vertically, while if dir is "v", it's done only vertically.
+# If choice is "o" or null, odd-numbered rows or columns are kept;
+# if "e", the even-numbered ones.
+
+procedure imxhalve(rows, dir, choice)
+ local newrows, i
+
+ choice := if choice === ("o" | &null) then 1 else 0
+ newrows := []
+
+ case dir of {
+ "v": {
+ every i := choice to *rows by 2 do
+ put(newrows, rows[i])
+ }
+ "h": every put(newrows, decollate(!rows, choice))
+ "b" | &null: return imxhalve(imxhalve(rows, "v", choice), "h", choice)
+ }
+
+ return newrows
+
+end
+
+#
+# Creates a tile in which each pixel doubled. dir determines the
+# direction in which the doubling is done. If dir is "b" or null, it's
+# done both horizontally and vertically. If dir is "v", it's only done
+# vertically, while if dir is "h", it's done only horizontally.
+
+procedure imxdouble(rows, dir)
+ local row, newrows
+
+ newrows := []
+
+ case dir of {
+ "v": {
+ every row := !rows do
+ put(newrows, row, row)
+ }
+ "h": {
+ every row := !rows do
+ put(newrows, collate(row, row))
+ }
+ "b" | &null: return imxdouble(imxdouble(rows, "v"), "h")
+ }
+
+ return newrows
+
+end
+
+#
+# Flip image matrix. The possible values of dir are "h" (horizontal flip),
+# "v" (vertical flip), "l" (left diagonal), and "r" (right diagonal).
+# (The left diagonal extends from the upper left corner to the bottom
+# right corner; the right diagonal from the upper right to the lower
+# left.
+
+procedure imxflip(rows, dir)
+ local newrows, x, y, i
+
+ case dir of {
+ "l": {
+ newrows := imxrotate(rows)
+ every y := 1 to *rows do
+ every x := 1 to *rows[1] do
+ newrows[x, y] := rows[y, x]
+ }
+ "r": {
+ newrows := list(*rows[1], repl("0", *rows))
+ every y := 1 to *rows do
+ every x := 1 to *rows[1] do
+ if rows[y, x] == "1" then
+ newrows[x, y] := "1"
+ }
+ "h": {
+ newrows := copy(rows)
+ every i := 1 to *rows do
+ newrows[i] := reverse(newrows[i])
+ }
+ "v": {
+ newrows := copy(rows)
+ every i := 1 to *rows / 2 do
+ newrows[i] :=: newrows[-i]
+ }
+ default: stop("*** illegal flip specification in imxflip()")
+ }
+
+ return newrows
+
+end
+
+#
+# Invert white and black bits in image matrix specification
+
+procedure imxinvert(rows)
+ local i
+
+ every i := 1 to *rows do
+ rows[i] := map(rows[i], "10", "01")
+
+ return rows
+
+end
+
+#
+# Reduce image matrix to its smallest equivalent form (with at least 4 columns).
+# Limited to square image matrices for portability -- other possibilities exist
+# for operating on and/or producing image matrices that are not square.
+
+
+procedure imxminim(rows)
+ local halfw, halfh, i
+
+ if (*rows ~= *rows[1]) | (*rows % 2 ~= 0) then return rows
+
+ repeat {
+
+ if *rows[1] < 8 then break # can't reduce to < 4 columns
+
+ halfw := *rows[1] / 2
+ halfh := *rows / 2
+
+ every i := 1 to halfh do # check rows in top and bottom
+ if (rows[i] ~== rows[i + halfh]) |
+ (rows[i][1+:halfw] ~== rows[i][0-:halfw]) then break break
+
+ every 1 to halfh do # reducible; remove rows
+ pop(rows)
+
+ every i := 1 to halfh do # truncate rows
+ rows[i] := rows[i][1+:halfw]
+
+ }
+
+ return rows
+
+end
+
+# Create rotated copy of an image matrix. If dir is "cw" or "90", rotation is
+# 90 degrees clockwise; if "ccw" or "-90", 90 degrees counter-clockwise.
+# If dir is "180", rotation is 180 degrees. The default is "cw".
+
+procedure imxrotate(rows, dir)
+ local newrows, i, row, pix
+
+ /dir := "cw"
+
+ case string(dir) of {
+ "ccw" | "-90": { # counter-clockwise
+ newrows := list(*rows[1], "")
+ every row := !rows do {
+ i := 0
+ every pix := !row do
+ newrows[i -:= 1] ||:= pix
+ }
+ }
+ "cw" | "90" | &null: { # clockwise
+ newrows := list(*rows[1], "")
+ every row := !rows do {
+ i := 0
+ every pix := !row do
+ newrows[i +:= 1] := pix || newrows[i]
+ }
+ }
+ "180": {
+ newrows := []
+ every push(newrows, reverse(!rows))
+ }
+ default: stop("*** illegal rotation specification in imxrotate()")
+ }
+
+ return newrows
+
+end
+
+#
+# Trim border whitespace from image matrix
+
+procedure imxtrim(rows)
+
+ while (*rows > 4) & not(upto('1', rows[1])) do
+ get(rows)
+
+ while (*rows > 4) & not(upto('1', rows[-1])) do
+ pull(rows)
+
+ rows := imxrotate(rows, "cw")
+
+ while (*rows > 4) & not(upto('1', rows[1])) do
+ get(rows)
+
+ while (*rows > 4) & not(upto('1', rows[-1])) do
+ pull(rows)
+
+ return imxrotate(rows, "ccw")
+
+end
+
+#
+# Centers non-white portion of image matrix
+
+procedure imxcenter(rows, w, h)
+ local rw, rh, vert, horz, t, l
+
+ rows := imxtrim(rows)
+
+ rw := *rows[1]
+ rh := *rows
+
+ if (rh = h) & (rw = w) then return rows
+ if (rh > h) | (rw > w) then fail
+
+ horz := w - rw
+ vert := h - rh
+ l := horz / 2
+ t := vert / 2
+
+ return imxborder(rows, l, horz - l, t, vert - t)
+
+end
+
+# Create a blank i-by-j image matrix
+
+procedure imxcreate(i, j)
+
+ return list(i, repl("0", j))
+
+end
diff --git a/ipl/gprocs/interact.icn b/ipl/gprocs/interact.icn
new file mode 100644
index 0000000..442f434
--- /dev/null
+++ b/ipl/gprocs/interact.icn
@@ -0,0 +1,409 @@
+############################################################################
+#
+# File: interact.icn
+#
+# Subject: Procedures to support interactive applications
+#
+# Author: Ralph E. Griswold
+#
+# Date: August 7, 2001
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# edit_file(s) launches an editor, default vi, for the file named
+# s.
+#
+# edit_list(L) provides edit dialog for the strings in the list L.
+#
+# error_notice(i, x, s)
+# produces a notice dialog noting a run-time
+# error. It can be used to handle procedure
+# errors by runerr := error_notice.
+#
+# execute() provides a dialog for specifying a command.
+#
+# expose(win) attempt to make win the active window for the
+# window manager.
+#
+# load_file(s, n) presents a standard open dialog with the caption s.
+# and suggest name n.
+#
+# If the user specifies a file that can be opened,
+# dialog_value is set to it. Otherwise, the dialog
+# is presented again. The name of the selected
+# button is returned.
+#
+# open_image(s) presents a standard open dialog with the caption s.
+# If the user specifies a file that can be opened as
+# an image in a window, the window is opened. Otherwise
+# the dialog is presented again.
+#
+# ExitNotice(s[]) Notice() that exits.
+#
+# FailNotice(s[]) Notice() that fails.
+#
+# save_as(s, n) presents a standard save dialog with the caption s
+# and suggested name n. If the user specifies a file
+# that can be written, the file is assigned to
+# dialog_value. Otherwise the dialog is presented
+# again. save_as() fails if the user cancels.
+#
+# save_file(s, n) presents a standard save dialog with the caption s
+# and suggested name n. If the user specifies a file
+# that can be written, the file is returned.
+# Otherwise, save_as() is called. The name of
+# the selected button is returned.
+#
+# save_list(s, L) provides dialog for saving list items in a file.
+#
+# select_dialog(s, L, d)
+# provides a dialog for selecting from a list of
+# items. d is the default selection.
+#
+# snapshot(win, x, y, w, h, n)
+# writes an image file for the specified portion of
+# the window. The name for the file is requested from
+# the user via a dialog box. If there already is a
+# file by the specified name, the user is given the
+# option of overwriting it or selecting another name.
+# The procedure fails if the user cancels. n sets
+# the width of the text-entry field.
+#
+# unsupported() provides Notice() for unsupported feature.
+#
+############################################################################
+#
+# Links: dsetup, exists, lists, strings
+#
+############################################################################
+#
+# Requires: Version 9 graphics
+#
+############################################################################
+
+link dsetup
+link io
+link lists
+
+procedure edit_file(name) #: editor launch
+ local editor
+
+ TextDialog("Edit:", , name, 30) == "Okay" | fail
+
+ editor := getenv("EDITOR") | "vi"
+
+ return system(editor || " " || dialog_value[1])
+
+end
+
+procedure edit_list(lines) #: edit lines dialog
+ local insert, number, location, bounds, n
+ static add_tbl, labels, buttons
+
+ initial {
+ add_tbl := table("")
+ add_tbl["number"] := 1
+ add_tbl["position"] := "after"
+
+ labels := []
+ every put(labels, right(1 to 50, 2))
+
+ buttons := ["Okay", "Cancel", "Add", "Delete"]
+ }
+
+ repeat {
+ case TextDialog("", labels[1 +: *lines], lines, 60, buttons) of {
+ "Cancel": fail
+ "Okay": return dialog_value
+ "Delete": {
+ repeat {
+ case TextDialog("Delete lines:", , , 60) of {
+ "Cancel": break next
+ "Okay": {
+ lines := ldelete(lines, dialog_value[1])
+ if *lines = 0 then {
+ Notice("List empty; creating one line")
+ lines := list(1)
+ }
+ break next
+ }
+ }
+ }
+ }
+ "Add": {
+ repeat {
+ add_tbl["location"] :=
+ if add_tbl["position"] == "after" then *lines else 0
+ case add_dialog(add_tbl) of {
+ "Cancel": break next
+ "Okay": {
+ bounds := (if add_tbl["position"] == "after" then 0 else 1)
+ (0 <= (n := integer(add_tbl["location"] - bounds)) <=
+ (*lines)) | {
+ Notice("Invalid location")
+ add_tbl["location"] := if add_tbl["position"] ==
+ "after" then *lines else 0
+ next
+ }
+ (number := (0 <= integer(add_tbl["number"]))) | {
+ Notice("Invalid number")
+ add_tbl["number"] := 1
+ next
+ }
+ insert := list(number, add_tbl["value"])
+ if n = 0 then lines := insert ||| lines
+ else if n = *lines then lines |||:= insert
+ else lines := lines[1:n] ||| insert ||| lines[n:0]
+ break next
+ }
+ }
+ }
+ }
+ }
+ }
+
+end
+
+procedure error_notice(i, x, s) #: error alert
+
+ return Notice("Error " || i || " " || s,
+ "Offending value: " || image(x))
+
+end
+
+procedure execute() #: command-line launch
+ local pipe, win, olist
+
+ OpenDialog("Command line:") == "Okay" | fail
+
+ olist := []
+ pipe := open(dialog_value, "p")
+
+ every put(olist, !pipe)
+
+ close(pipe)
+
+ win := list_win(olist, "command") | fail
+
+ Event(win)
+
+ WClose(win)
+
+ return
+
+end
+
+procedure list_win(lst, label) #: window for list of strings
+ local win
+
+ win := WOpen("canvas=hidden", "label=" || label, "lines=" || *lst + 2,
+ "columns=" || maxlen(lst) + 2) | fail
+
+ WWrite(win)
+ every WWrite(win, " ", !lst)
+ WAttrib(win, "canvas=normal")
+
+ return win
+
+end
+
+procedure expose(win) #: expose window
+
+# For some window managers, this can be use to make a window active
+
+# WAttrib(\win, "canvas=hidden") | fail
+# WAttrib(win, "canvas=normal")
+
+# However, this should work without the fidgets:
+
+ Raise(win)
+
+ return
+
+end
+
+procedure load_file(caption, n) #: load dialog
+ local button
+
+ repeat {
+ (button := OpenDialog(caption, n)) == "Okay" | return button
+ dialog_value := open(dialog_value) | {
+ Notice("Can't open " || dialog_value)
+ next
+ }
+ return button
+ }
+
+end
+
+procedure open_image(caption, atts[]) #: open image
+ local button, win
+
+ repeat {
+ (button := OpenDialog(caption)) == "Okay" | fail
+ put(atts, "image=" || dialog_value)
+ win := (WOpen ! atts) | {
+ Notice("Can't open " || dialog_value)
+ pull(atts)
+ next
+ }
+ return win
+ }
+
+end
+
+procedure ExitNotice(s[]) #: notice dialog that fails
+
+ Notice ! s
+
+ exit()
+
+end
+
+procedure FailNotice(s[]) #: notice dialog that fails
+
+ Notice ! s
+
+ fail
+
+end
+
+procedure save_as(caption, name, n) #: save-as dialog
+ local button, file
+
+ repeat {
+ if (button := SaveDialog(caption, name, n)) == "Yes" then {
+ file := dialog_value
+ if exists(file) then {
+ if TextDialog("Overwrite existing file?") == "Cancel" then next
+ }
+ dialog_value := open(file, "w") | {
+ Notice("Can't write " || dialog_value)
+ next
+ }
+ }
+ return button
+ }
+
+end
+
+procedure save_file(caption, name, n) #: save dialog
+ local button
+
+ (button := SaveDialog(caption, name, n)) == "Yes" | return button
+ dialog_value := open(dialog_value, "w") | {
+ Notice("Can't write file")
+ return save_as("Save:", dialog_value, n)
+ }
+
+ return button
+
+end
+
+procedure save_list(caption, lst) #: save list dialog
+ local output
+
+ OpenDialog(caption, , 30) == "Okay" | fail
+ if dialog_value == "-" then output := &output # "-" means &output
+ else output := open(dialog_value, "w") |
+ return FailNotice("Cannot open " || dialog_value)
+
+ every write(output, !lst)
+
+ close(output)
+
+ return
+
+end
+
+# This procedure handles selection from long lists by producing
+# a succession of dialogs to the user's choice of "More".
+
+$define Choices 30 # maximum choices per dialog
+
+procedure select_dialog(caption, lst, dflt) #: select dialog for many items
+ static buttons
+
+ initial buttons := ["Okay", "More", "Cancel"]
+
+ if *lst = 0 then {
+ Notice("No selections available")
+ fail
+ }
+ until *lst <= Choices do {
+ case SelectDialog(caption, lst[1+:Choices], dflt, buttons) of {
+ "Cancel": fail
+ "Okay": return
+ "More": lst := lst[Choices + 1:0]
+ }
+ }
+
+ if *lst > 0 then {
+ SelectDialog(caption, lst, dflt) == "Okay" | fail
+ return dialog_value
+ }
+
+ else fail
+
+end
+
+procedure snapshot(win, x, y, w, h, n) #: snapshot dialog
+ local name, fg, bg
+
+ if type(win) ~== "window" then {
+ win :=: x :=: y :=: w :=: h
+ win := &window
+ }
+
+ fg := Fg(win)
+ bg := Bg(win)
+ Fg(win, "black")
+ Bg(win, "light gray")
+
+ repeat {
+ if OpenDialog(win, "Image file name", , n) == "Okay" then {
+ name := dialog_value
+ if exists(dialog_value) then {
+ if TextDialog("Overwrite existing file?") == "Cancel"
+ then next
+ }
+ Fg(win, fg)
+ Bg(win, bg)
+ WriteImage(win, name, x, y, w, h) | {
+ Notice("Cannot write image")
+ next
+ }
+ return
+ }
+ else fail
+ }
+
+end
+
+procedure unsupported() #: unsupported feature alert
+
+ return FailNotice("Unsupported feature")
+
+end
+
+#===<<vib:begin>>=== modify using vib; do not remove this marker line
+procedure add_dialog(win, deftbl)
+static dstate
+initial dstate := dsetup(win,
+ ["add_dialog:Sizer::1:0,0,531,182:add",],
+ ["add:Label:::12,14,70,13:Add lines:",],
+ ["cancel:Button:regular::76,150,49,20:Cancel",],
+ ["location:Text::2:12,43,87,19:location:\\=",],
+ ["number:Text::2:12,72,87,19:number: \\=",],
+ ["okay:Button:regular:-1:12,150,49,20:Okay",],
+ ["position:Choice::2:117,50,71,42:",,
+ ["after","before"]],
+ ["value:Text::60:12,103,493,19:value: \\=",],
+ )
+return dpopup(win, deftbl, dstate)
+end
+#===<<vib:end>>=== end of section maintained by vib
diff --git a/ipl/gprocs/isdplot.icn b/ipl/gprocs/isdplot.icn
new file mode 100644
index 0000000..4bd8008
--- /dev/null
+++ b/ipl/gprocs/isdplot.icn
@@ -0,0 +1,259 @@
+############################################################################
+#
+# File: isdplot.icn
+#
+# Subject: Procedures to create grid plots for ISDs
+#
+# Author: Ralph E. Griswold
+#
+# Date: May 26, 2001
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# NOTE: The drawdown code is patched in from code in pfd2ill.icn and
+# uses a different method than the others. One way or another, the
+# methods should be made consonant.
+#
+############################################################################
+#
+# Requires: Version 9 graphics and large integers
+#
+############################################################################
+#
+# Links: cells, convert, expander, weaving, weavutil, lists, mirror,
+# tieutils, wopen, numbers, xcode, palettes, patxform
+#
+############################################################################
+
+link convert
+link expander
+link weaving
+link weavutil
+link lists
+link mirror
+link numbers
+link palettes
+link patxform
+link tieutils
+link wopen
+
+global X_ # x position for copying
+global Y_ # y position for copying
+
+$define CellSize 5
+$define g_w 10
+
+# Create draft.
+
+procedure plot(draft, clip)
+ local threading_pane, treadling_pane, tieup_pane
+ local tr_w, th_w, tr_h, th_h, i, j, weft_colors_pane
+ local x, y, k, width, height, warp_colors_pane
+ local drawdown_win, treadle, treadle_list, win, b_w
+ local threading_colors_pane, treadling_colors_pane, colors
+ local trc_w, trc_h, thc_w, thc_h, matrix
+
+ X_ := Y_ := 0
+
+ if /draft.warp_colors | /draft.weft_colors then fail
+
+ colors := *draft.color_list # NEEDS FIXING
+
+ warp_colors_pane := makepanel(*draft.threading, 1, CellSize)
+ weft_colors_pane := makepanel(1, *draft.treadling, CellSize)
+
+ b_w := WAttrib(weft_colors_pane.window, "width")
+
+ every i := 1 to *draft.warp_colors do
+ colorcell(warp_colors_pane, i, 1,
+ draft.color_list[integer(draft.warp_colors[i])]) | fail
+
+ every j := 1 to *draft.weft_colors do
+ colorcell(weft_colors_pane, 1, j,
+ draft.color_list[integer(draft.weft_colors[j])]) | fail
+
+ threading_pane := makepanel(*draft.threading, draft.shafts, CellSize)
+
+ every i := 1 to *draft.threading do
+ colorcell(threading_pane, i, draft.shafts - \draft.threading[i] + 1,
+ "black") | fail
+
+ th_w := WAttrib(threading_pane.window, "width")
+ th_h := WAttrib(threading_pane.window, "height")
+
+ treadling_pane := makepanel(draft.treadles, *draft.treadling, CellSize)
+
+ tr_w := WAttrib(treadling_pane.window, "width")
+ tr_h := WAttrib(treadling_pane.window, "height")
+
+ every i := 1 to *draft.treadling do
+ colorcell(treadling_pane, draft.treadles - draft.treadling[i] + 1, i,
+ "black")
+
+ threading_colors_pane := makepanel(*draft.threading, colors, CellSize)
+
+ every i := 1 to *draft.threading do
+ colorcell(threading_colors_pane, i,
+ colors - draft.warp_colors[i] + 1, "black")
+
+ thc_w := WAttrib(threading_colors_pane.window, "width")
+ thc_h := WAttrib(threading_colors_pane.window, "height")
+
+ treadling_colors_pane := makepanel(colors, *draft.treadling, CellSize)
+
+ every i := 1 to *draft.treadling do
+ colorcell(treadling_colors_pane,
+ colors - draft.weft_colors[i] + 1, i, "black")
+
+ trc_w := WAttrib(treadling_colors_pane.window, "width")
+ trc_h := WAttrib(treadling_colors_pane.window, "height")
+
+ tieup_pane := makepanel(draft.treadles, draft.shafts, CellSize)
+
+ matrix := pflip(pflip(draft.tieup, "h"), "v")
+
+ every i := 1 to draft.shafts do # rows
+ every j := 1 to draft.treadles do # columns
+ if matrix[i, j] == "1" then
+ colorcell(tieup_pane, j, i, "black")
+
+ drawdown_win := WOpen(
+ "canvas=hidden",
+ "width=" || (CellSize * *draft.threading + 1),
+ "height=" || (CellSize * *draft.treadling + 1)
+ )
+
+ treadle_list := list(draft.treadles)
+ every !treadle_list := []
+
+ every i := 1 to draft.shafts do
+ every j := 1 to draft.treadles do
+ if draft.tieup[i, j] == "1" then
+ every k := 1 to *draft.threading do
+ if draft.threading[k] == i then
+ put(treadle_list[j], k)
+
+ every j := 1 to *draft.treadling do {
+ treadle := draft.treadling[j]
+ if *treadle_list[treadle] = 0 then next # blank pick
+ every i := 1 to *(treadle_list[treadle]) do
+ fillcell(drawdown_win, treadle_list[treadle][i], j, "black")
+ }
+
+ every x := 0 to WAttrib(drawdown_win, "width") by CellSize do
+ DrawLine(drawdown_win, x, 0, x, WAttrib(drawdown_win, "height"))
+ every y := 0 to WAttrib(drawdown_win, "height") by CellSize do
+ DrawLine(drawdown_win, 0, y, WAttrib(drawdown_win, "width"), y)
+
+ width := trc_w + tr_w + th_w + b_w + 5 * g_w
+ height := thc_h + th_h + tr_h + b_w + 5 * g_w
+
+ win := WOpen(
+ "canvas=hidden",
+ "width=" || width,
+ "height=" || height
+ ) | stop("cannot open comp window")
+
+ incr_offset(g_w, 4 * g_w + b_w + thc_h + th_h)
+
+ CopyArea(weft_colors_pane.window, win, , , , , X_, Y_)
+
+ incr_offset(b_w + g_w, 0)
+
+ CopyArea(treadling_colors_pane.window, win, , , , , X_, Y_)
+
+ incr_offset(trc_w + g_w, 0)
+
+ CopyArea(treadling_pane.window, win, , , , , X_, Y_)
+
+ incr_offset(tr_w + g_w, 0)
+
+ CopyArea(drawdown_win, win, , , , , X_, Y_)
+
+ incr_offset(0, -(th_h + g_w))
+
+ CopyArea(threading_pane.window, win, , , , , X_, Y_)
+
+ incr_offset(0, -(thc_h + g_w))
+
+ CopyArea(threading_colors_pane.window, win, , , , , X_, Y_)
+
+ incr_offset(0, -(b_w + g_w))
+
+ CopyArea(warp_colors_pane.window, win, , , , , X_, Y_)
+
+ incr_offset(-(tr_w + g_w), b_w + thc_h + 2 * g_w)
+
+ CopyArea(tieup_pane.window, win, , , , , X_, Y_)
+
+ if \clip then { # remove color portion
+ CopyArea(win, win, X_, Y_, , , 0, 0)
+ WAttrib(win, "width=" || (WAttrib(win, "width") - X_ - 2 * g_w))
+ WAttrib(win, "height=" || (WAttrib(win, "height") - Y_ - 2 * g_w))
+ }
+
+ every WClose(
+ weft_colors_pane.window |
+ treadling_colors_pane.window |
+ treadling_pane.window |
+ drawdown_win |
+ threading_pane.window |
+ threading_colors_pane.window |
+ warp_colors_pane.window |
+ tieup_pane.window |
+ drawdown_win
+ )
+
+ return win
+
+end
+
+procedure clear_pane(win, n, m, size)
+ local x, y, width, height, save_fg
+
+ width := n * size + 1
+ height := m * size + 1
+
+ save_fg := Fg(win)
+
+ Fg(win, "black")
+
+ every x := 0 to width by size do
+ DrawLine(win, x, 0, x, height)
+
+ every y := 0 to height by size do
+ DrawLine(win, 0, y, width, y)
+
+ Fg(win, save_fg)
+
+ return
+
+end
+
+procedure fillcell(win, n, m, color)
+ local save_fg
+
+ save_fg := Fg(win)
+ Fg(win, color)
+
+ FillRectangle(win, (n - 1) * CellSize, (m - 1) * CellSize, CellSize,
+ CellSize)
+
+ Fg(win, save_fg)
+
+ return
+
+end
+
+procedure incr_offset(x, y)
+
+ X_ +:= x
+ Y_ +:= y
+
+ return
+
+end
diff --git a/ipl/gprocs/isdxplot.icn b/ipl/gprocs/isdxplot.icn
new file mode 100644
index 0000000..52a7283
--- /dev/null
+++ b/ipl/gprocs/isdxplot.icn
@@ -0,0 +1,245 @@
+############################################################################
+#
+# File: isdxplot.icn
+#
+# Subject: Procedures to create grid plots for ISDs
+#
+# Author: Ralph E. Griswold
+#
+# Date: March 4, 2003
+#
+############################################################################
+#
+# NOTE: The drawdown code is patched in from code in pfd2ill.icn and
+# uses a different method than the others. One way or another, the
+# methods should be made consonant.
+#
+# This version is for ISDs without explicit thread-color information.
+#
+############################################################################
+#
+# Requires: Version 9 graphics and large integers
+#
+############################################################################
+#
+# Links: convert, expander, weaving, weavutil, lists, mirror,
+# tieutils, wopen, numbers, palettes, patxform
+#
+############################################################################
+
+link convert
+link expander
+link weaving
+link weavutil
+link lists
+link mirror
+link numbers
+link palettes
+link patxform
+link tieutils
+link wopen
+
+global X_ # x position for copying
+global Y_ # y position for copying
+
+$define CellSize 10
+$define g_w 10
+
+# Create draft.
+
+procedure plot(draft, clip)
+ local threading_pane, treadling_pane, tieup_pane
+ local tr_w, th_w, tr_h, th_h, i, j, weft_colors_pane
+ local x, y, k, width, height, warp_colors_pane
+ local drawdown_win, treadle, treadle_list, win, b_w
+ local threading_colors_pane, treadling_colors_pane, colors
+ local trc_w, trc_h, thc_w, thc_h, matrix
+
+ X_ := Y_ := 0
+
+ colors := *draft.color_list # NEEDS FIXING
+
+ warp_colors_pane := makepanel(*draft.threading, 1, CellSize)
+ weft_colors_pane := makepanel(1, *draft.treadling, CellSize)
+
+ b_w := WAttrib(weft_colors_pane.window, "width")
+
+ every i := 1 to *draft.threading do
+ colorcell(warp_colors_pane, i, 1, "black")
+
+ every j := 1 to *draft.treadling do
+ colorcell(weft_colors_pane, 1, j, "white")
+
+ threading_pane := makepanel(*draft.threading, draft.shafts, CellSize)
+
+ every i := 1 to *draft.threading do
+ colorcell(threading_pane, i, draft.shafts - draft.threading[i] + 1,
+ "black") | fail
+
+ th_w := WAttrib(threading_pane.window, "width")
+ th_h := WAttrib(threading_pane.window, "height")
+
+ treadling_pane := makepanel(draft.treadles, *draft.treadling, CellSize)
+
+ tr_w := WAttrib(treadling_pane.window, "width")
+ tr_h := WAttrib(treadling_pane.window, "height")
+
+ every i := 1 to *draft.treadling do
+ colorcell(treadling_pane, draft.treadles - draft.treadling[i] + 1, i,
+ "black")
+
+ threading_colors_pane := makepanel(*draft.threading, colors, CellSize)
+
+ thc_w := WAttrib(threading_colors_pane.window, "width")
+ thc_h := WAttrib(threading_colors_pane.window, "height")
+
+ treadling_colors_pane := makepanel(colors, *draft.treadling, CellSize)
+
+ trc_w := WAttrib(treadling_colors_pane.window, "width")
+ trc_h := WAttrib(treadling_colors_pane.window, "height")
+
+ tieup_pane := makepanel(draft.treadles, draft.shafts, CellSize)
+
+ matrix := pflip(pflip(draft.tieup, "h"), "v")
+
+ every i := 1 to draft.shafts do # rows
+ every j := 1 to draft.treadles do # columns
+ if matrix[i, j] == "1" then
+ colorcell(tieup_pane, j, i, "black")
+
+ drawdown_win := WOpen(
+ "canvas=hidden",
+ "width=" || (CellSize * *draft.threading + 1),
+ "height=" || (CellSize * *draft.treadling + 1)
+ )
+
+ treadle_list := list(draft.treadles)
+ every !treadle_list := []
+
+ every i := 1 to draft.shafts do
+ every j := 1 to draft.treadles do
+ if draft.tieup[i, j] == "1" then
+ every k := 1 to *draft.threading do
+ if draft.threading[k] == i then
+ put(treadle_list[j], k)
+
+ every j := 1 to *draft.treadling do {
+ treadle := draft.treadling[j]
+ if *treadle_list[treadle] = 0 then next # blank pick
+ every i := 1 to *(treadle_list[treadle]) do
+ fillcell(drawdown_win, treadle_list[treadle][i], j, "black")
+ }
+
+ every x := 0 to WAttrib(drawdown_win, "width") by CellSize do
+ DrawLine(drawdown_win, x, 0, x, WAttrib(drawdown_win, "height"))
+ every y := 0 to WAttrib(drawdown_win, "height") by CellSize do
+ DrawLine(drawdown_win, 0, y, WAttrib(drawdown_win, "width"), y)
+
+ width := trc_w + tr_w + th_w + b_w + 5 * g_w
+ height := thc_h + th_h + tr_h + b_w + 5 * g_w
+
+ win := WOpen(
+ "canvas=hidden",
+ "width=" || width,
+ "height=" || height
+ ) | stop("cannot open comp window")
+
+ incr_offset(g_w, 4 * g_w + b_w + thc_h + th_h)
+
+ CopyArea(weft_colors_pane.window, win, , , , , X_, Y_)
+
+ incr_offset(b_w + g_w, 0)
+
+ CopyArea(treadling_colors_pane.window, win, , , , , X_, Y_)
+
+ incr_offset(trc_w + g_w, 0)
+
+ CopyArea(treadling_pane.window, win, , , , , X_, Y_)
+
+ incr_offset(tr_w + g_w, 0)
+
+ CopyArea(drawdown_win, win, , , , , X_, Y_)
+
+ incr_offset(0, -(th_h + g_w))
+
+ CopyArea(threading_pane.window, win, , , , , X_, Y_)
+
+ incr_offset(0, -(thc_h + g_w))
+
+ CopyArea(threading_colors_pane.window, win, , , , , X_, Y_)
+
+ incr_offset(0, -(b_w + g_w))
+
+ CopyArea(warp_colors_pane.window, win, , , , , X_, Y_)
+
+ incr_offset(-(tr_w + g_w), b_w + thc_h + 2 * g_w)
+
+ CopyArea(tieup_pane.window, win, , , , , X_, Y_)
+
+ if \clip then { # remove color portion
+ CopyArea(win, win, X_ - 10, Y_ - 10, , , 0, 0)
+ WAttrib(win, "width=" || (WAttrib(win, "width") - X_ + g_w))
+ WAttrib(win, "height=" || (WAttrib(win, "height") - Y_ + g_w))
+ }
+
+ every WClose(
+ weft_colors_pane.window |
+ treadling_colors_pane.window |
+ treadling_pane.window |
+ drawdown_win |
+ threading_pane.window |
+ threading_colors_pane.window |
+ warp_colors_pane.window |
+ tieup_pane.window |
+ drawdown_win
+ )
+
+ return win
+
+end
+
+procedure clear_pane(win, n, m, size)
+ local x, y, width, height, save_fg
+
+ width := n * size + 1
+ height := m * size + 1
+
+ save_fg := Fg(win)
+
+ Fg(win, "black")
+
+ every x := 0 to width by size do
+ DrawLine(win, x, 0, x, height)
+
+ every y := 0 to height by size do
+ DrawLine(win, 0, y, width, y)
+
+ Fg(win, save_fg)
+
+ return
+
+end
+
+procedure fillcell(win, n, m, color)
+ local save_fg
+
+ save_fg := Fg(win)
+ Fg(win, color)
+
+ FillRectangle(win, (n - 1) * CellSize, (m - 1) * CellSize, CellSize,
+ CellSize)
+
+ Fg(win, save_fg)
+
+ return
+
+end
+
+procedure incr_offset(x, y)
+
+ X_ +:= x
+ Y_ +:= y
+
+ return
+
+end
diff --git a/ipl/gprocs/joinpair.icn b/ipl/gprocs/joinpair.icn
new file mode 100644
index 0000000..6fbdac2
--- /dev/null
+++ b/ipl/gprocs/joinpair.icn
@@ -0,0 +1,44 @@
+############################################################################
+#
+# File: joinpair.icn
+#
+# Subject: Procedure to connect pairs of points
+#
+# Author: Ralph E. Griswold
+#
+# Date: February 12, 1993
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# joinpair(points1, points2) draws lines between all pairs of points
+# in the lists of points.
+#
+############################################################################
+#
+# Requires: Version 9 graphics
+#
+############################################################################
+#
+# Links: gobject, turtle
+#
+############################################################################
+
+link gobject
+link turtle
+
+procedure joinpair(points1, points2)
+ local j, k, p1, p2
+
+ every p1 := !points1 do
+ every p2 := !points2 do {
+ TGoto(p1.x, p1.y)
+ TDrawto(p2.x, p2.y)
+ }
+
+ return
+
+end
diff --git a/ipl/gprocs/jolygs.icn b/ipl/gprocs/jolygs.icn
new file mode 100644
index 0000000..f776163
--- /dev/null
+++ b/ipl/gprocs/jolygs.icn
@@ -0,0 +1,55 @@
+############################################################################
+#
+# File: jolygs.icn
+#
+# Subject: Procedure to produce traces of "jolygons"
+#
+# Author: Ralph E. Griswold
+#
+# Date: May 2, 2001
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# This procedure produces traces of jolygons. See
+#
+# Geometric and Artistic Graphics; Design Generation with
+# Microcomputers, Jean-Paul Delahaye, Macmillan, 1987, pp. 20-24.
+#
+# The arguments specify the starting positions, the extent of the
+# drawing, the number of segments, the angle between consecutive
+# segments, the ratio of the lengths of consecutive segments,
+# a length factor, and a y scaling factor.
+#
+############################################################################
+#
+# Links: gobject
+#
+############################################################################
+
+link gobject
+
+procedure jolyg(x, y, extent, n, angle, ratio, lfact, yfact)
+ local xpos, ypos, i, offset, length
+
+ angle := dtor(angle)
+ offset := 0
+ length := extent * lfact
+
+ xpos := (extent - length) / 2
+ ypos := (extent - length) / 2
+
+ suspend Point(x + xpos, y + ypos) # initial point
+
+ every i := 0 to n do {
+ xpos +:= length * cos(offset)
+ ypos +:= length * sin(offset)
+ suspend Point(x + xpos, y + yfact * ypos)
+ offset +:= angle
+ length *:= ratio
+ }
+
+end
diff --git a/ipl/gprocs/linddefs.icn b/ipl/gprocs/linddefs.icn
new file mode 100644
index 0000000..793ecf2
--- /dev/null
+++ b/ipl/gprocs/linddefs.icn
@@ -0,0 +1,424 @@
+############################################################################
+#
+# File: linddefs.icn
+#
+# Subject: Procedure to produce table of L-systems
+#
+# Author: Ralph E. Griswold
+#
+# Date: November 22, 1997
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# This procedure produces a table of L-systems.
+#
+############################################################################
+#
+# Links: lindrec
+#
+############################################################################
+
+link lindrec
+
+procedure linddefs()
+ local linden
+
+ linden := table()
+
+ linden["fibbush"] := lsys_0l("", table(), 0, 90)
+ linden["fibbush"].rewrite["A"] := "[B/////'B///////'B]"
+ linden["fibbush"].rewrite["B"] := "[&IL!A]"
+ linden["fibbush"].rewrite["I"] := "FL"
+ linden["fibbush"].rewrite["F"] := "F/////I"
+ linden["fibbush"].rewrite["L"] := "['''^^{-F+F+F-|-F+F+F}]"
+ linden["fibbush"].gener := 3
+ linden["fibbush"].length := 3
+ linden["fibbush"].axiom := "A"
+ linden["fibbush"].angle := 22.5
+ linden["ebush"] := lsys_0l("", table(), 0, 90)
+ linden["ebush"].rewrite["P"] := "I+[P+O]--//[--L]I[++L]-[PO]++PO"
+ linden["ebush"].rewrite["I"] := "FS[//&&L][//^^L]FS"
+ linden["ebush"].rewrite["S"] := "SFS"
+ linden["ebush"].rewrite["L"] := "['{+f-ff-f+|+f-ff-f}]"
+ linden["ebush"].rewrite["O"] := "[&&&D`/W////W////W////W////W]"
+ linden["ebush"].rewrite["D"] := "FF"
+ linden["ebush"].rewrite["W"] := "[`^F][{&&&&-f+f|-f+f}]"
+ linden["ebush"].axiom := "P"
+ linden["ebush"].angle := 18.0
+ linden["ebush"].gener := 3
+ linden["ebush"].length := 3
+ linden["bush"] := lsys_0l("", table(), 0, 90)
+ linden["bush"].rewrite["F"] := "FF-[-F+F+F]+[+F-F-F]"
+ linden["bush"].axiom := "++++F"
+ linden["bush"].angle := 22.5
+ linden["cesaro"] := lsys_0l("", table(), 0, 90)
+ linden["cesaro"].rewrite["X"] := "----F!X!++++++++F!X!----"
+ linden["cesaro"].rewrite["F"] := ""
+ linden["cesaro"].gener := 10
+ linden["cesaro"].length := 3
+ linden["cesaro"].axiom := "FX"
+ linden["cesaro"].angle := 10.58823529
+ linden["curve1"] := lsys_0l("", table(), 0, 90)
+ linden["curve1"].rewrite["F"] := "FF-F-F-F-F-F+F"
+ linden["curve1"].axiom := "F-F-F-F-"
+ linden["curve1"].angle := 90.0
+ linden["curve2"] := lsys_0l("", table(), 0, 90)
+ linden["curve2"].rewrite["F"] := "FF-F+F-F-FF"
+ linden["curve2"].axiom := "F-F-F-F-"
+ linden["curve2"].angle := 90.0
+ linden["curve3"] := lsys_0l("", table(), 0, 90)
+ linden["curve3"].rewrite["F"] := "F-FF--F-F"
+ linden["curve3"].axiom := "F-F-F-F-"
+ linden["curve3"].angle := 90.0
+ linden["curve4"] := lsys_0l("", table(), 0, 90)
+ linden["curve4"].rewrite["X"] := "YF+XF+Y"
+ linden["curve4"].rewrite["Y"] := "XF-YF-X"
+ linden["curve4"].axiom := "YF"
+ linden["curve4"].angle := 60.0
+ linden["curve4"].gener := 5
+ linden["dragon"] := lsys_0l("", table(), 0, 90)
+ linden["dragon"].rewrite["X"] := "-FX++FY-"
+ linden["dragon"].rewrite["Y"] := "+FX--FY+"
+ linden["dragon"].rewrite["F"] := ""
+ linden["dragon"].axiom := "FX"
+ linden["dragon"].angle := 45.0
+ linden["dragon"].gener := 10
+ linden["dragon1"] := lsys_0l("", table(), 0, 90)
+ linden["dragon1"].rewrite["r"] := "-Fl-r"
+ linden["dragon1"].rewrite["l"] := "l+rF+"
+ linden["dragon1"].axiom := "Fl"
+ linden["dragon1"].gener := 14
+ linden["dragonc"] := lsys_0l("", table(), 0, 90)
+ linden["dragonc"].rewrite["X"] := "X-YF-"
+ linden["dragonc"].rewrite["Y"] := "+FX+Y"
+ linden["dragonc"].axiom := "X"
+ linden["dragonc"].angle := 90.0
+ linden["dragonc"].gener := 10
+ linden["fass1"] := lsys_0l("", table(), 0, 90)
+ linden["fass1"].rewrite["R"] := "-LFLF+RFRFR+F+RF-LFL-FR"
+ linden["fass1"].rewrite["L"] := "LF+RFR+FL-F-LFLFL-FRFR+"
+ linden["fass1"].axiom := "-L"
+ linden["fass1"].angle := 90.0
+ linden["fass2"] := lsys_0l("", table(), 0, 90)
+ linden["fass2"].rewrite["R"] := "-LFLFLF+RFR+FL-F-LF+RFR+FLF+RFRF-LFL-FRFR"
+ linden["fass2"].rewrite["L"] := "LFLF+RFR+FLFL-FRF-LFL-FR+F+RF-LFL-FRFRFR+"
+ linden["fass2"].axiom := "-L"
+ linden["fass2"].angle := 90.0
+ linden["flake3"] := lsys_0l("", table(), 0, 90)
+ linden["flake3"].rewrite["X"] := "++FXFY--FX--FY"
+ linden["flake3"].rewrite["Y"] := "FYFX+++FYFX++FX++FYFX|+FX--FY--FXFY++"
+ linden["flake3"].rewrite["F"] := ""
+ linden["flake3"].axiom := "FX"
+ linden["flake3"].angle := 30.0
+ linden["flake3"].gener := 10
+ linden["hilbert"] := lsys_0l("", table(), 0, 90)
+ linden["hilbert"].rewrite["X"] := "-YF+XFX+FY-"
+ linden["hilbert"].rewrite["Y"] := "+XF-YFY-FX+"
+ linden["hilbert"].axiom := "X"
+ linden["hilbert"].angle := 90.0
+ linden["hilbert"].gener := 10
+ linden["island1"] := lsys_0l("", table(), 0, 90)
+ linden["island1"].rewrite["F"] := "FFFF-F+F+F-F[-FF+F+FF+F]FF"
+ linden["island1"].axiom := "F+F+F+F"
+ linden["island1"].angle := 90.0
+ linden["island2"] := lsys_0l("", table(), 0, 90)
+ linden["island2"].rewrite["F"] := "F+F-FF-F-FF++FF-F+FF+F+FF--FFF"
+ linden["island2"].axiom := "F+F+F+F"
+ linden["island2"].angle := 90.0
+ linden["island2"].gener := 4
+ linden["island2"].length := 2
+ linden["koch1"] := lsys_0l("", table(), 0, 90)
+ linden["koch1"].rewrite["F"] := "F+F--F+F"
+ linden["koch1"].axiom := "F--F--F"
+ linden["koch1"].angle := 60.0
+ linden["koch1"].gener := 4
+ linden["koch1"].length := 4
+ linden["koch2"] := lsys_0l("", table(), 0, 90)
+ linden["koch2"].rewrite["F"] := "-F+++F---F+"
+ linden["koch2"].axiom := "F---F---F---F"
+ linden["koch2"].angle := 30.0
+ linden["koch2"].gener := 6
+ linden["koch2"].length := 4
+ linden["koch3"] := lsys_0l("", table(), 0, 90)
+ linden["koch3"].rewrite["F"] := "F-F+F+FF-F-F+F"
+ linden["koch3"].axiom := "F-F-F-F"
+ linden["koch3"].angle := 90.0
+ linden["koch3"].gener := 6
+ linden["koch3"].length := 4
+ linden["koch4"] := lsys_0l("", table(), 0, 90)
+ linden["koch4"].rewrite["F"] := "+F--F++F-"
+ linden["koch4"].axiom := "F++++F++++F"
+ linden["koch4"].angle := 30.0
+ linden["koch4"].gener := 5
+ linden["koch4"].length := 3
+ linden["koch5"] := lsys_0l("", table(), 0, 90)
+ linden["koch5"].rewrite["F"] := "F+F-F-FFF+F+F-F"
+ linden["koch5"].axiom := "F+F+F+F"
+ linden["koch5"].angle := 90.0
+ linden["koch6"] := lsys_0l("", table(), 0, 90)
+ linden["koch6"].rewrite["F"] := "F-FF+FF+F+F-F-FF+F+F-F-FF-FF+F"
+ linden["koch6"].axiom := "F+F+F+F"
+ linden["koch6"].angle := 90.0
+ linden["koch7"] := lsys_0l("", table(), 0, 90)
+ linden["koch7"].rewrite["F"] := "F+F-F+F+F"
+ linden["koch7"].axiom := "F+F+F+F"
+ linden["koch7"].gener := 4
+ linden["koch8"] := lsys_0l("", table(), 0, 90)
+ linden["koch8"].rewrite["F"] := "F+F--F+F"
+ linden["koch8"].axiom := "F"
+ linden["koch8"].angle := 60.0
+ linden["lakeisle"] := lsys_0l("", table(), 0, 90)
+ linden["lakeisle"].rewrite["F"] := "F-f+FF-F-FF-Ff-FF+f-FF+F+FF+Ff+FFF"
+ linden["lakeisle"].rewrite["f"] := "ffffff"
+ linden["lakeisle"].axiom := "F-F-F-F"
+ linden["lakeisle"].gener := 2
+ linden["leaf1"] := lsys_0l("", table(), 0, 90)
+ linden["leaf1"].rewrite["H"] := "J"
+ linden["leaf1"].rewrite["P"] := "X"
+ linden["leaf1"].rewrite["X"] := "F[+AAAA]FY"
+ linden["leaf1"].rewrite["E"] := "H"
+ linden["leaf1"].rewrite["B"] := "E"
+ linden["leaf1"].rewrite["J"] := "Y"
+ linden["leaf1"].rewrite["O"] := "P"
+ linden["leaf1"].rewrite["A"] := "N"
+ linden["leaf1"].rewrite["Y"] := "F[-BBBB]FX"
+ linden["leaf1"].rewrite["N"] := "O"
+ linden["leaf1"].axiom := "X"
+ linden["leaf1"].angle := 45.0
+ linden["leaf1"].gener := 10
+ linden["leaf2"] := lsys_0l("", table(), 0, 90)
+ linden["leaf2"].rewrite["X"] := "A"
+ linden["leaf2"].rewrite["B"] := "F[-Y]FA"
+ linden["leaf2"].rewrite["A"] := "F[+X]BF"
+ linden["leaf2"].rewrite["Y"] := "B"
+ linden["leaf2"].axiom := "A"
+ linden["leaf2"].angle := 45.0
+ linden["leaf2"].gener := 14
+ linden["peano1"] := lsys_0l("", table(), 0, 90)
+ linden["peano1"].rewrite["F"] := "F-F+F+F+F-F-F-F+F"
+ linden["peano1"].axiom := "F-F-F-F"
+ linden["peano1"].angle := 90.0
+ linden["peano2"] := lsys_0l("", table(), 0, 90)
+ linden["peano2"].rewrite["X"] := "XY-F-FXY++F++FXY"
+ linden["peano2"].rewrite["Y"] := "-F-FXY"
+ linden["peano2"].axiom := "FXY++F++FXY++F"
+ linden["peano2"].angle := 45.0
+ linden["peano2"].gener := 4
+ linden["peano2"].length := 7
+ linden["peano3"] := lsys_0l("", table(), 0, 90)
+ linden["peano3"].rewrite["X"] := "XFYFX+F+YFXFY-F-XFYFX"
+ linden["peano3"].rewrite["Y"] := "YFXFY-F-XFYFX+F+YFXFY"
+ linden["peano3"].axiom := "X"
+ linden["peano3"].angle := 90.0
+ linden["penrose1"] := lsys_0l("", table(), 0, 90)
+ linden["penrose1"].rewrite["X"] := "+YF--ZF[---WF--XF]+"
+ linden["penrose1"].rewrite["Z"] := "--YF++++WF[+ZF++++XF]--XF"
+ linden["penrose1"].rewrite["W"] := "YF++ZF----XF[-YF----WF]++"
+ linden["penrose1"].rewrite["Y"] := "-WF++XF[+++YF++ZF]-"
+ linden["penrose1"].rewrite["F"] := ""
+ linden["penrose1"].axiom := "+WF--XF---YF--ZF"
+ linden["penrose1"].angle := 36.0
+ linden["penrose2"] := lsys_0l("", table(), 0, 90)
+ linden["penrose2"].rewrite["X"] := "+YF--ZF[---WF--XF]+"
+ linden["penrose2"].rewrite["Z"] := "--YF++++WF[+ZF++++XF]--XF"
+ linden["penrose2"].rewrite["W"] := "YF++ZF----XF[-YF----WF]++"
+ linden["penrose2"].rewrite["Y"] := "-WF++XF[+++YF++ZF]-"
+ linden["penrose2"].rewrite["F"] := ""
+ linden["penrose2"].axiom := "++ZF----XF-YF----WF"
+ linden["penrose2"].angle := 36.0
+ linden["penrose2"].gener := 5
+ linden["penrose2"].length := 10
+ linden["penrose3"] := lsys_0l("", table(), 0, 90)
+ linden["penrose3"].rewrite["X"] := "+YF--ZF[---WF--XF]+"
+ linden["penrose3"].rewrite["Z"] := "--YF++++WF[+ZF++++XF]--XF"
+ linden["penrose3"].rewrite["W"] := "YF++ZF----XF[-YF----WF]++"
+ linden["penrose3"].rewrite["Y"] := "-WF++XF[+++YF++ZF]-"
+ linden["penrose3"].rewrite["F"] := ""
+ linden["penrose3"].axiom := "[X]++[X]++[X]++[X]++[X]"
+ linden["penrose3"].angle := 36.0
+ linden["penrose3"].gener := 5
+ linden["penrose3"].length := 10
+ linden["penrose4"] := lsys_0l("", table(), 0, 90)
+ linden["penrose4"].rewrite["X"] := "+YF--ZF[---WF--XF]+"
+ linden["penrose4"].rewrite["Z"] := "--YF++++WF[+ZF++++XF]--XF"
+ linden["penrose4"].rewrite["W"] := "YF++ZF----XF[-YF----WF]++"
+ linden["penrose4"].rewrite["Y"] := "-WF++XF[+++YF++ZF]-"
+ linden["penrose4"].rewrite["F"] := ""
+ linden["penrose4"].axiom := "[Y]++[Y]++[Y]++[Y]++[Y]"
+ linden["penrose4"].angle := 36.0
+ linden["penrose4"].gener := 5
+ linden["penrose4"].length := 10
+ linden["penrosed"] := lsys_0l("", table(), 0, 90)
+ linden["penrosed"].rewrite["X"] := "+YF--ZF[---WF--XF]+"
+ linden["penrosed"].rewrite["Z"] := "--YF++++WF[+ZF++++XF]--XF"
+ linden["penrosed"].rewrite["W"] := "YF++ZF----XF[-YF----WF]++"
+ linden["penrosed"].rewrite["Y"] := "-WF++XF[+++YF++ZF]-"
+ linden["penrosed"].rewrite["F"] := ""
+ linden["penrosed"].axiom := "[X][Y]++[X][Y]++[X][Y]++[X][Y]++[X][Y]"
+ linden["penrosed"].angle := 36.0
+ linden["penrosed"].length := 40
+ linden["plant01"] := lsys_0l("", table(), 0, 90)
+ linden["plant01"].rewrite["F"] := "F[+F]F[-F]F"
+ linden["plant01"].axiom := "F"
+ linden["plant01"].angle := 25.71428571
+ linden["plant01"].gener := 10
+ linden["plant02"] := lsys_0l("", table(), 0, 90)
+ linden["plant02"].rewrite["F"] := "F[+F]F[-F][F]"
+ linden["plant02"].axiom := "F"
+ linden["plant02"].angle := 20.0
+ linden["plant03"] := lsys_0l("", table(), 0, 90)
+ linden["plant03"].rewrite["F"] := "FF-[-F+F+F]+[+F-F-F]"
+ linden["plant03"].axiom := "F"
+ linden["plant03"].angle := 22.5
+ linden["plant03"].gener := 4
+ linden["plant04"] := lsys_0l("", table(), 0, 90)
+ linden["plant04"].rewrite["X"] := "F[+X]F[-X]+X"
+ linden["plant04"].rewrite["F"] := "FF"
+ linden["plant04"].axiom := "X"
+ linden["plant04"].angle := 20.0
+ linden["plant04"].gener := 5
+ linden["plant05"] := lsys_0l("", table(), 0, 90)
+ linden["plant05"].rewrite["X"] := "F[+X][-X]FX"
+ linden["plant05"].rewrite["F"] := "FF"
+ linden["plant05"].axiom := "X"
+ linden["plant05"].angle := 25.71428571
+ linden["plant05"].gener := 5
+ linden["plant06"] := lsys_0l("", table(), 0, 90)
+ linden["plant06"].rewrite["X"] := "F-[[X]+X]+F[+FX]-X"
+ linden["plant06"].rewrite["F"] := "FF"
+ linden["plant06"].axiom := "X"
+ linden["plant06"].angle := 22.5
+ linden["plant06"].gener := 5
+ linden["plant07"] := lsys_0l("", table(), 0, 90)
+ linden["plant07"].rewrite["X"] := "X[-FFF][+FFF]FX"
+ linden["plant07"].rewrite["Z"] := "ZFX[+Z][-Z]"
+ linden["plant07"].axiom := "Z"
+ linden["plant07"].angle := 25.71428571
+ linden["plant07"].gener := 5
+ linden["plant08"] := lsys_0l("", table(), 0, 90)
+ linden["plant08"].rewrite["S"] := "[+++Z][---Z]TS"
+ linden["plant08"].rewrite["H"] := "-Z[+H]L"
+ linden["plant08"].rewrite["Z"] := "+H[-Z]L"
+ linden["plant08"].rewrite["L"] := "[-FFF][+FFF]F"
+ linden["plant08"].rewrite["T"] := "TL"
+ linden["plant08"].axiom := "SLFFF"
+ linden["plant08"].angle := 18.0
+ linden["plant08"].gener := 6
+ linden["plant08"].length := 8
+ linden["plant09"] := lsys_0l("", table(), 0, 90)
+ linden["plant09"].rewrite["X"] := "X[-FFF][+FFF]FX"
+ linden["plant09"].rewrite["Y"] := "YFX[+Y][-Y]"
+ linden["plant09"].axiom := "Y"
+ linden["plant09"].angle := 25.71428571
+ linden["plant09"].gener := 5
+ linden["plant10"] := lsys_0l("", table(), 0, 90)
+ linden["plant10"].rewrite["F"] := "F[+FF][-FF]F[+FF][-FF]F"
+ linden["plant10"].axiom := "F"
+ linden["plant10"].angle := 36.0
+ linden["plant10"].gener := 3
+ linden["plant11"] := lsys_0l("", table(), 0, 90)
+ linden["plant11"].rewrite["F"] := "F[+F[+F][-F]F][-F[+F][-F]F]F[+F][-F]F"
+ linden["plant11"].axiom := "F"
+ linden["plant11"].angle := 30.0
+ linden["plant11"].gener := 3
+ linden["plant11"].length := 10
+ linden["quadgos"] := lsys_0l("", table(), 0, 90)
+ linden["quadgos"].rewrite["R"] := "+FLFL-FR-FR+FL+FLFR+FL-FRFR-FL-FR+FLFRFR-FL-FRFL+FL+FR-FR-FL+FL+FRFR"
+ linden["quadgos"].rewrite["L"] := "FLFL-FR-FR+FL+FL-FR-FRFL+FR+FLFLFR-FL+FR+FLFL+FR-FLFR-FR-FL+FL+FRFR-"
+ linden["quadgos"].rewrite["F"] := ""
+ linden["quadgos"].axiom := "-FR"
+ linden["quadgos"].angle := 90.0
+ linden["quadkoch"] := lsys_0l("", table(), 0, 90)
+ linden["quadkoch"].rewrite["F"] := "F+FF-FF-F-F+F+FF-F-F+F+FF+FF-F"
+ linden["quadkoch"].axiom := "FX++FX++FX++FX++FX"
+ linden["quadkoch"].angle := 90.0
+ linden["quartet"] := lsys_0l("", table(), 0, 90)
+ linden["quartet"].rewrite["H"] := "-"
+ linden["quartet"].rewrite["B"] := "FB+FA-FB-JFBFA"
+ linden["quartet"].rewrite["J"] := "+"
+ linden["quartet"].rewrite["A"] := "FBFA+HFA+FB-FA"
+ linden["quartet"].rewrite["F"] := ""
+ linden["quartet"].axiom := "FB"
+ linden["quartet"].angle := 90.0
+ linden["quartet"].gener := 8
+ linden["sier1"] := lsys_0l("", table(), 0, 90)
+ linden["sier1"].rewrite["X"] := "+FXF-FXF-FXF+"
+ linden["sier1"].rewrite["F"] := "FXF"
+ linden["sier1"].axiom := "F"
+ linden["sier1"].angle := 120.0
+ linden["sier1"].gener := 5
+ linden["sier2"] := lsys_0l("", table(), 0, 90)
+ linden["sier2"].rewrite["X"] := "--FXF++FXF++FXF--"
+ linden["sier2"].rewrite["F"] := "FF"
+ linden["sier2"].axiom := "FXF--FF--FF"
+ linden["sier2"].angle := 60.0
+ linden["sier2"].gener := 5
+ linden["sier3"] := lsys_0l("", table(), 0, 90)
+ linden["sier3"].rewrite["F"] := "F[-F]F"
+ linden["sier3"].axiom := "F-F-F"
+ linden["sier3"].angle := 120.0
+ linden["sier3"].gener := 5
+ linden["siersqar"] := lsys_0l("", table(), 0, 90)
+ linden["siersqar"].rewrite["F"] := "FF+F+F+F+FF"
+ linden["siersqar"].axiom := "F+F+F+F"
+ linden["siersqar"].angle := 90.0
+ linden["siersqar"].gener := 4
+ linden["snoflake"] := lsys_0l("", table(), 0, 90)
+ linden["snoflake"].rewrite["F"] := "F-F+F+F-F"
+ linden["snoflake"].axiom := "+F"
+ linden["snoflake"].gener := 4
+ linden["space1"] := lsys_0l("", table(), 0, 90)
+ linden["space1"].rewrite["X"] := "YFXFY+F+YFXFY-F-XFYFX"
+ linden["space1"].rewrite["Y"] := "YFXFY-F-XFYFX+F+YFXFY"
+ linden["space1"].axiom := "X"
+ linden["space1"].gener := 3
+ linden["sphinx"] := lsys_0l("", table(), 0, 90)
+ linden["sphinx"].rewrite["X"] := "+FF-YFF+FF--FFFXF--YFFFYFFF"
+ linden["sphinx"].rewrite["G"] := "GG"
+ linden["sphinx"].rewrite["Y"] := "-FF+XFF-FF++FFFYF++XFFFXFFF"
+ linden["sphinx"].rewrite["F"] := "GG"
+ linden["sphinx"].axiom := "X"
+ linden["sphinx"].angle := 60.0
+ linden["sphinx"].gener := 5
+ linden["sqgasket"] := lsys_0l("", table(), 0, 90)
+ linden["sqgasket"].rewrite["X"] := "+FXF+FXF+FXF+FXF"
+ linden["sqgasket"].rewrite["F"] := "FF"
+ linden["sqgasket"].axiom := "X"
+ linden["sqgasket"].angle := 90.0
+ linden["sqgasket"].gener := 5
+ linden["square"] := lsys_0l("", table(), 0, 90)
+ linden["square"].rewrite["F"] := "FF+F+F+F+FF"
+ linden["square"].axiom := "F+F+F+F"
+ linden["square"].angle := 90.0
+ linden["tile"] := lsys_0l("", table(), 0, 90)
+ linden["tile"].rewrite["X"] := "[F+F+F+F[---X-Y]+++++F++++++++F-F-F-F]"
+ linden["tile"].rewrite["Y"] := "[F+F+F+F[---Y]+++++F++++++++F-F-F-F]"
+ linden["tile"].axiom := "X+X+X+X+X+X+X+X+X+X+X+X+X+X+X+X+X+X+X+X+X+X+X+X"
+ linden["tile"].angle := 15.0
+ linden["tile"].length := 10
+ linden["tree"] := lsys_0l("", table(), 0, 90)
+ linden["tree"].rewrite["X"] := "[-FX]+FX"
+ linden["tree"].axiom := "+++FX"
+ linden["tree"].angle := 30.0
+ linden["tree"].gener := 8
+ linden["tree"].length := 10
+ linden["tree1"] := lsys_0l("", table(), 0, 90)
+ linden["tree1"].rewrite["X"] := "[-FX]+FX"
+ linden["tree1"].axiom := "+++FX"
+ linden["tree1"].angle := 30.0
+ linden["tree1"].gener := 5
+ linden["tree1"].length := 8
+ linden["tree2"] := lsys_0l("", table(), 0, 90)
+ linden["tree2"].rewrite["X"] := "+FY"
+ linden["tree2"].rewrite["Y"] := "-FX"
+ linden["tree2"].rewrite["F"] := "FF-[XY]+[XY]"
+ linden["tree2"].axiom := "++++F"
+ linden["tree2"].angle := 22.5
+
+ return linden
+
+end
diff --git a/ipl/gprocs/linddraw.icn b/ipl/gprocs/linddraw.icn
new file mode 100644
index 0000000..5020972
--- /dev/null
+++ b/ipl/gprocs/linddraw.icn
@@ -0,0 +1,63 @@
+############################################################################
+#
+# File: linddraw.icn
+#
+# Subject: Procedure to draw L-System strings
+#
+# Author: Ralph E. Griswold
+#
+# Date: May 2, 2001
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# This procedure draws strings of characters produced by
+# L-systems.
+#
+############################################################################
+#
+# Links: lindgen, turtle, graphics
+#
+############################################################################
+
+link lindgen
+link turtle
+link graphics
+
+# The drawing is based on the axiom and the rewriting rules. The other
+# parameters are the line length, the angle delta between lines, and
+# the number of generations. Drawing starts at x,y.
+
+procedure linddraw( #: draw L-system
+ x, y, axiom, rewrite, length, delta, gener, delay
+ )
+ local c
+
+ /x := (WAttrib(\&window, "width") / 2) | 250
+ /y := (WAttrib(\&window, "height") / 2) | 250
+ /length := 5
+ /delta := 90
+
+ TReset()
+ TGoto(x, y)
+
+ every c := lindgen(!axiom, rewrite, gener) do {
+ WDelay(delay)
+ case c of {
+ "F": TDraw(length) # draw forward
+ "f": TSkip(length) # skip forward
+ "+": TRight(delta) # turn right
+ "-": TLeft(delta) # turn left
+ "[": TSave() # save state
+ "]": TRestore() # restore state
+ } # ignore other characters
+ }
+
+ WFlush()
+
+ return
+
+end
diff --git a/ipl/gprocs/lindrec.icn b/ipl/gprocs/lindrec.icn
new file mode 100644
index 0000000..5290630
--- /dev/null
+++ b/ipl/gprocs/lindrec.icn
@@ -0,0 +1,22 @@
+############################################################################
+#
+# File: lindrec.icn
+#
+# Subject: Declarations for L-systems
+#
+# Author: Ralph E. Griswold
+#
+# Date: August 18, 1994
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# These declarations are provided for representing Lindenmayer systems
+# as records.
+#
+############################################################################
+
+record lsys_0l(axiom, rewrite, gener, angle, length, x, y, color)
diff --git a/ipl/gprocs/lindterp.icn b/ipl/gprocs/lindterp.icn
new file mode 100644
index 0000000..2f01f1a
--- /dev/null
+++ b/ipl/gprocs/lindterp.icn
@@ -0,0 +1,73 @@
+############################################################################
+#
+# File: lindterp.icn
+#
+# Subject: Procedure to interpret and draw L-System strings
+#
+# Author: Ralph E. Griswold
+#
+# Date: May 2, 2001
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# This procedure interpreters strings of characters produced by
+# L-Systems and draws them using turtle graphics.
+#
+############################################################################
+#
+# Links: lindrec, lindgen, turtle
+#
+############################################################################
+
+link lindrec
+link lindgen
+link turtle
+
+global size
+
+# length is the length of line segments and delta is the amount of
+# direction change.
+
+procedure lindterp(x, y, lsys, gener, length, color, fnc)
+ local rewrite, delta, axiom, symbols, c
+
+ /size := 500
+ /x := size / 2
+ /y := size / 2
+ rewrite := lsys.rewrite
+ axiom := lsys.axiom
+ delta := lsys.delta
+ /gener := lsys.gener
+ /length := lsys.length
+
+# The table symbols contains definitions for other symbols as
+# string of other characters. It remains to be seen how this
+# will be represented. Note also there is a potential for
+# circularity and unbounded recursion.
+
+ symbols := table() # table of defined symbols
+
+ TReset()
+ TGoto(x, y)
+
+ every c := lindgen(!axiom, rewrite, gener) do
+ case c of {
+ "F": TDraw(length) # draw forward
+ "f": TSkip(length) # skip forward
+ "+": TRight(delta) # turn right
+ "-": TLeft(delta) # turn left
+ "[": TSave() # save state
+ "]": TRestore() # restore state
+ # interpret defined symbol
+ default: lindterp(\symbols[c], length, delta)
+ } # ignore other characters
+
+ WFlush()
+
+ return
+
+end
diff --git a/ipl/gprocs/lsystem.icn b/ipl/gprocs/lsystem.icn
new file mode 100644
index 0000000..b6ef102
--- /dev/null
+++ b/ipl/gprocs/lsystem.icn
@@ -0,0 +1,181 @@
+############################################################################
+#
+# File: lsystem.icn
+#
+# Subject: Procedures for Lindenmayer systems support
+#
+# Author: Stephen B. Wampler
+#
+# Date: May 2, 2001
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# Version: 1.0
+#
+############################################################################
+#
+# Comments: This package is the collection of routines
+# developed to facilitate experiments with L-systems,
+# including the interpretation of strings as turtle
+# graphics commands.
+#
+# Only rudimentary L-systems are currently implemented.
+# users are encouraged to extend this system.
+#
+############################################################################
+#
+# Requires: Version 9 graphics, co-expressions (for glib.icn)
+#
+############################################################################
+#
+# Links: glib
+#
+############################################################################
+
+link glib
+record Lsys(order, dist, delta, axiom, rewrite)
+
+# lsmap(s1,T) - replace, in s1, occurrences of character key values in T
+# with assigned value for that key. (Suitable for l-system rules!)
+#
+procedure lsmap(s1,T)
+ local s
+
+ if type(T) ~== "table" then
+ stop("lsmap() - second argument not a table!")
+
+ s := ""
+ s1 ? while s ||:= (\T[move(1)] | move(1))
+
+ return s
+end
+
+# mk_map(L) - build a rewriting map table from list L
+#
+procedure mk_map(L)
+ local a, t
+
+ t := table()
+ every a := !L do {
+ t[a[1]] := a[2]
+ }
+
+ return t
+end
+
+# read_Lsystem(f) - read in an L system from a file...
+#
+# Form for an L_system:
+#
+# order: n
+# delta: angle
+# axiom: string
+# map: c = string
+#
+procedure read_Lsystem(f)
+ local ls, line, next_token
+
+ ls := Lsys(0,10,90,"",table())
+
+ while line := read(f) do {
+ next_token := create gen_tokens(line)
+
+ case map(@next_token) of {
+ "order:": ls.order := integer(@next_token)
+ "dist:" : ls.dist := integer(@next_token)
+ "delta:": ls.delta := numeric(@next_token)
+ "axiom:": ls.axiom := @next_token
+ "map:" : ls.rewrite[@next_token] := (@next_token, @next_token)
+ }
+ }
+
+ return ls
+end
+
+
+# write_Lsystem(ls) - display L-system ls (for debugging, mainly)
+#
+procedure write_Lsystem(ls)
+ write("L-system:")
+ write("\torder: ",ls.order)
+ write("\t dist: ",ls.dist)
+ write("\tdelta: ",ls.delta)
+ write("\taxiom: ",ls.axiom)
+ every key := key(ls.rewrite) do
+ write("\t map: ",key," -> ",ls.rewrite[key])
+ return
+end
+
+
+# build_cmd(ls) - return the command string for
+# l-system ls
+#
+procedure build_cmd(ls)
+ local s
+
+ s := ls.axiom
+ every 1 to ls.order do
+ s := lsmap(s, ls.rewrite)
+ return s
+
+end
+
+# eval_cmd(s) - apply turtle t to command string
+#
+procedure eval_cmd(t,s,dist,delta)
+
+ s ? while obey(t,move(1), dist, delta)
+
+ return
+end
+
+
+# eval_lsys(t,ls,dist,delta) - apply turtle t directly to
+# an Lsystem avoids constructing full Lsystem string
+# at once (i.e. no need to call build_cmd).
+#
+procedure eval_lsys(t,ls)
+ evaluate(t,ls.axiom, ls.rewrite, ls.order, ls.delta, ls.dist)
+end
+
+# evaluate(t,s, Ls_map, n, delta, dist) - recursive l-system evaluation
+# (avoids building entire command string)
+procedure evaluate(t, s, Ls_map, n, delta, dist)
+
+ if n = 0 then return eval_cmd(t,s,dist,delta)
+
+ s ? while evaluate(t, lsmap(move(1), Ls_map), Ls_map, n-1, delta, dist)
+ return
+end
+
+# obey(t, c, dist, delta) - execute the appropriate turtle command
+# using turtle t. (INCOMPLETE) (this is where L-systems could
+# be greatly extended.)
+procedure obey(t, c, dist, delta)
+
+ case c of {
+ "f" : Move_Forward(t, dist)
+ "+" : Left(t, delta)
+ "-" : Right(t, delta)
+ default: Line_Forward(t, dist)
+ }
+
+ return
+end
+
+# get_tokens(s) - suspend the tokens in string s
+#
+procedure gen_tokens(s, ws)
+ local nws
+
+ /ws := ' \t'
+ nws := ~ws
+
+ s ? while tab(upto(nws)) do
+ suspend tab(many(nws)) \ 1
+
+end
diff --git a/ipl/gprocs/mapnav.icn b/ipl/gprocs/mapnav.icn
new file mode 100644
index 0000000..c72ef65
--- /dev/null
+++ b/ipl/gprocs/mapnav.icn
@@ -0,0 +1,320 @@
+############################################################################
+#
+# File: mapnav.icn
+#
+# Subject: Procedures for navigating a map interactively
+#
+# Authors: Gregg M. Townsend
+#
+# Date: May 7, 2002
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# These procedures implement a user interface for browsing a map
+# that is drawn using a simple rectangular projection. The
+# following interface actions are provided, if not overridden
+# by the calling program:
+# The arrow keys pan the display.
+# Shifted arrows pan by a smaller amount.
+# The + and - keys zoom in and out.
+# The 0, 1, or HOME key resets the original display.
+# The q key causes an immediate exit.
+# Sweeping a region with the mouse zooms the display.
+# Resizing the window causes it to be redrawn.
+#
+# The calling program provides the main loop and a drawing
+# procedure; this module handles the interface and computes
+# the output transformation.
+#
+# mapinit(win, proc, arg, xleft, xright, ytop, ybottom, m) initializes.
+# mapgen(win, proc, arg) generates the map by invoking the callback.
+# mapevent(win, e) handles a window event, possibly invoking a callback.
+# mapproj(win) returns the projection used in the window.
+#
+# The win argument is optional in all procedures but can be used
+# to supply the correct graphics context. The window argument is
+# always supplied to the callback procedure.
+#
+############################################################################
+#
+# Typical use is like this:
+#
+# procedure main(...)
+# ... initialize ...
+# ... load data ...
+# ... open window ...
+# mapinit(draw, ...)
+# mapgen()
+# case e := Event() of {
+# ... handle custom events ...
+# default: mapevent(e)
+# }
+# end
+#
+# procedure draw(win, p, arg)
+# ... create list of coordinates ...
+# ... L2 := project(p, L1) ...
+# ... draw map ...
+# end
+#
+############################################################################
+#
+# mapinit(win, proc, arg, xleft, xright, ytop, ybottom, m) configures
+# the navigator. proc is a drawing procedure to be called whenever
+# the window needs to be redrawn. arg is an arbitrary value to be
+# passed to proc along with the transformation parameters.
+#
+# xleft, xright, ytop, and ybottom specify the range of coordinates
+# for the data that is to be displayed. For both the x and y pairs,
+# the values must differ but either can be the greater.
+#
+# The value of m (default 1.0) specifies the aspect ratio of the
+# input units. If the input data is in units of latitude and
+# longitude, choose a central latitude for projection and pass
+# the cosine of that latitude as m.
+#
+############################################################################
+#
+# mapgen(win, proc, arg) calls the drawing procedure proc to draw a
+# map. win is optional, and proc and arg default to the values
+# registered by the last mapinit() call.
+#
+# The drawing procedure is called as
+# proc(win, pj, arg)
+# where pj the projection returned by mapproj(win).
+#
+# The drawing procedure should project and display its data.
+# It must ensure that the resulting coordinates lie inside
+# the range -32768 <= v <=32767 before passing them to Icon
+# drawing functions. (See also clipping.icn.)
+#
+############################################################################
+#
+# mapevent(win, e) handles a window event. If e is recognized as
+# an interface action, the map parameters are altered and mapgen()
+# is called, resulting in a call to the drawing procedure. For
+# a panning action, the window contents are first shifted;
+# otherwise, the window is first erased. mapevent() fails if
+# e is not recognized.
+#
+# The calling program can intercept and override any action it does
+# not want handled by the navigator. This can be used to customize
+# the interface -- for example, to use "0" for something other than
+# "reset zooming". However, any &resize event, even if handled by
+# the caller, should be passed to the navigator to allow it to
+# properly adjust its view of the world.
+#
+############################################################################
+#
+# mapproj(win) returns a rectangular projection (see cartog.icn)
+# that maximizes the display of the currently selected data range
+# for viewing in the center of window win. The "selected data range"
+# is that passed to mapinit() and subsequently modified by any
+# zooming or panning actions.
+#
+############################################################################
+#
+# Links: graphics, cartog
+#
+############################################################################
+
+$include "keysyms.icn"
+
+link graphics
+link cartog
+
+$define MARGIN 16
+
+global mnv_proc # registered drawing procedure
+global mnv_arg # arbitrary argument for that procedure
+global mnv_aspr # coordinate system aspect ratio
+
+global mnv_prjn # current projection
+
+# map limits
+global mnv_mleft, mnv_mright, mnv_mtop, mnv_mbottom
+
+# viewport configuration
+global mnv_vleft, mnv_vright, mnv_vtop, mnv_vbottom
+
+procedure mapinit(win,p,a,xleft,xright,ytop,ybottom,m) #: initialize navigator
+
+ if type(win) ~== "window" then # handle missing optional win argument
+ return mapinit((\&window | runerr(140)),
+ win, p, a, xleft, xright, ytop, ybottom)
+
+ mnv_proc := p
+ mnv_arg := a
+ mnv_aspr := \m | 1.0
+
+ mnv_mleft := mnv_vleft := xleft
+ mnv_mright := mnv_vright := xright
+ mnv_mtop := mnv_vtop := ytop
+ mnv_mbottom := mnv_vbottom := ybottom
+
+ return
+end
+
+procedure mapgen(win, proc, arg) #: invoke callback to redraw the map
+
+ if type(win) ~== "window" then { # handle missing optional win argument
+ win :=: proc :=: arg
+ win := \&window | runerr(140)
+ }
+
+ /proc := mnv_proc
+ /arg := mnv_arg
+ return proc(win, mapproj(win), arg)
+end
+
+procedure mapproj(win) #: compute map projection
+ local l, r, t, b, d, nx, ny, xmul, ymul
+
+ /win := \&window | runerr(140) # handle missing optional win argument
+
+ l := \WAttrib(win, "clipx") | 0
+ t := \WAttrib(win, "clipy") | 0
+ r := l + \WAttrib(win, "clipw" | "width")
+ b := t + \WAttrib(win, "cliph" | "height")
+ nx := MARGIN
+ ny := MARGIN
+
+ xmul := real(r - l - 2 * nx) / (mnv_vright - mnv_vleft)
+ ymul := real(b - t - 2 * ny) / (mnv_vbottom - mnv_vtop)
+
+ d := abs(xmul / (ymul * mnv_aspr))
+ if d > 1.0 then {
+ xmul /:= d
+ nx := (r - l - xmul * (mnv_vright - mnv_vleft)) / 2
+ }
+ else {
+ ymul *:= d
+ ny := (b - t - ymul * (mnv_vbottom - mnv_vtop)) / 2
+ }
+
+ mnv_prjn := rectp(mnv_vleft, mnv_vtop, l + nx, t + ny, xmul, ymul)
+ return mnv_prjn
+end
+
+procedure mapevent(win, e) #: navigate map as directed by action e
+ local win2, xywh, ltrb
+
+ if type(win) ~== "window" then { # handle missing optional win argument
+ e := win
+ win := \&window | runerr(140)
+ }
+
+ case e of {
+
+ &resize: {
+ EraseArea(win)
+ mapgen(win)
+ }
+
+ &lpress: {
+ win2 := Clone(win, "linewidth=4", "linestyle=solid", "fg=orange")
+ xywh := Sweep(win2)
+ Uncouple(win2)
+ if xywh[3|4] < 10 then
+ return
+ xywh[3] +:= xywh[1]
+ xywh[4] +:= xywh[2]
+ ltrb := project(invp(mnv_prjn), xywh)
+
+ mnv_vleft := get(ltrb)
+ mnv_vtop := get(ltrb)
+ mnv_vright := get(ltrb)
+ mnv_vbottom := get(ltrb)
+ EraseArea(win)
+ mapgen(win)
+ }
+
+ !"01" | Key_Home: {
+ mnv_vleft := mnv_mleft
+ mnv_vright := mnv_mright
+ mnv_vtop := mnv_mtop
+ mnv_vbottom := mnv_mbottom
+ EraseArea(win)
+ mapgen(win)
+ }
+
+ Key_Up: mnv_pan(win, 0, -1)
+ Key_Down: mnv_pan(win, 0, +1)
+ Key_Left: mnv_pan(win, -1, 0)
+ Key_Right: mnv_pan(win, +1, 0)
+
+ !"+=": mnv_inout(win, -0.20)
+ !"-_": mnv_inout(win, +0.25)
+
+ !"Qq": exit()
+
+ default: fail
+
+ }
+
+ return
+end
+
+procedure mnv_pan(win, px, py) # process panning action
+ local n, l, r, t, b, w, h, nx, ny, dx, dy, xyxy
+
+ n := if &shift then 10 else 100
+ nx := n * px
+ ny := n * py
+
+ l := \WAttrib(win, "clipx") | 0
+ t := \WAttrib(win, "clipy") | 0
+ r := l + \WAttrib(win, "clipw" | "width")
+ b := t + \WAttrib(win, "cliph" | "height")
+ w := r - l - abs(nx)
+ h := b - t - abs(ny)
+
+ if nx > 0 then {
+ CopyArea(win, l + nx, t, w, h, l, t)
+ EraseArea(win, r - nx, t, nx, h)
+ }
+ else if nx < 0 then {
+ CopyArea(win, l, t, w, h, l - nx, t)
+ EraseArea(win, l, t, -nx, h)
+ }
+
+ if ny > 0 then {
+ CopyArea(win, l, t + ny, w, h, l, t)
+ EraseArea(win, l, b - ny, w, ny)
+ }
+ else if ny < 0 then {
+ CopyArea(win, l, t, w, h, l, t - ny)
+ EraseArea(win, l, t, w, -ny)
+ }
+
+ xyxy := project(invp(mnv_prjn), [l, t, l + nx, t + ny])
+ dx := xyxy[3] - xyxy[1]
+ dy := xyxy[4] - xyxy[2]
+ mnv_vleft +:= dx
+ mnv_vright +:= dx
+ mnv_vtop +:= dy
+ mnv_vbottom +:= dy
+ mapgen(win)
+
+ return
+end
+
+procedure mnv_inout(win, f) # process zooming action
+ local xc, yc
+
+ xc := (mnv_vleft + mnv_vright) / 2
+ yc := (mnv_vtop + mnv_vbottom) / 2
+
+ mnv_vleft +:= f * (mnv_vleft - xc)
+ mnv_vright +:= f * (mnv_vright - xc)
+ mnv_vtop +:= f * (mnv_vtop - yc)
+ mnv_vbottom +:= f * (mnv_vbottom - yc)
+
+ EraseArea(win)
+ mapgen(win)
+ return
+end
diff --git a/ipl/gprocs/mirror.icn b/ipl/gprocs/mirror.icn
new file mode 100644
index 0000000..fd1076e
--- /dev/null
+++ b/ipl/gprocs/mirror.icn
@@ -0,0 +1,66 @@
+############################################################################
+#
+# File: mirror.icn
+#
+# Subject: Procedure to mirror tile
+#
+# Author: Ralph E. Griswold
+#
+# Date: November 15, 1997
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# mirror(win) mirrors win using p2mm symmetry and returns the result as a
+# hidden window.
+#
+############################################################################
+#
+# Requires: Version 9 graphics
+#
+############################################################################
+#
+# Links: wopen
+#
+############################################################################
+
+link wopen
+
+procedure mirror(win, x, y, w, h) # mirror with p2mm symmetry
+ local width, height, sym, x1, y1
+
+ /win := &window
+ /x := 0
+ /y := 0
+ /w := WAttrib(win, "width")
+ /h := WAttrib(win, "height")
+
+ if w < 0 then {
+ w := -w
+ x -:= w
+ }
+
+ if h < 0 then {
+ h := -h
+ y -:= h
+ }
+
+ width := 2 * w
+ height := 2 * h
+
+ sym := WOpen("canvas=hidden", "size=" || width || "," || height) | fail
+
+ CopyArea(win, sym, x, y, w, h)
+
+ every x := 0 to w - 1 do
+ CopyArea(sym, sym, x, 0, 1, h, width - x - 1, 0)
+
+ every y := 0 to h - 1 do
+ CopyArea(sym, sym, 0, y, width, 1, 0, height - y - 1)
+
+ return sym
+
+end
diff --git a/ipl/gprocs/modlines.icn b/ipl/gprocs/modlines.icn
new file mode 100644
index 0000000..6fe2151
--- /dev/null
+++ b/ipl/gprocs/modlines.icn
@@ -0,0 +1,51 @@
+############################################################################
+#
+# File: modlines.icn
+#
+# Subject: Procedure to produce trace of modular lines
+#
+# Author: Ralph E. Griswold
+#
+# Date: August 3, 2000
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# For a description of the method used here, see
+#
+# Geometric and Artistic Graphics; Design Generation with
+# Microcomputers, Jean-Paul Delahaye, Macmillan, 1987, pp. 90-95.
+#
+############################################################################
+#
+# Links: calls, gobject, gtrace
+#
+############################################################################
+
+link calls
+link gobject
+link gtrace
+
+# modlines produces a trace of lines between points selected modulo n,
+# where n is the number of points on a supporting curve. k is an
+# offset factor. A trace of the supporting curve is produced by call.
+#
+procedure modlines(call, m, k, limit)
+ local points, n, i
+
+ /limit := 500 # maximum number of points allowed
+
+ points := point_list(call, limit)
+
+ n := *points # number of points on supporting curve
+
+ every i := 0 to m do {
+# i1 := i % n + 1
+# i2 := (i * k) % n + 1
+ suspend points[(i % n + 1) | ((i * k) % n + 1)]
+ }
+
+end
diff --git a/ipl/gprocs/navitrix.icn b/ipl/gprocs/navitrix.icn
new file mode 100644
index 0000000..ad8a79a
--- /dev/null
+++ b/ipl/gprocs/navitrix.icn
@@ -0,0 +1,279 @@
+############################################################################
+#
+# File: navitrix.icn
+#
+# Subject: Procedures to perform file navigation
+#
+# Author: Ralph E. Griswold
+#
+# Date: July 10, 2002
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# This package provides an interface for file navigation. It is
+# intended for use with another application with a visual interface.
+#
+############################################################################
+#
+# The code is based on Unix but may work in other Unix-like environments.
+#
+# Directories are shown with a trailing slash. Clicking on a directory
+# moves there. Clicking on a file name selects it. The text of the
+# button used to dismiss the navigator is put in the global variable
+# nav_state, while the name of the selected file is put in the global
+# variable nav_file.
+#
+# nav_keyboard() processes keyboard shortcuts. A return character is
+# equivalent to clicking on the Okay button. Other characters cause
+# the top list entry to be positioned at a name that starts with or is
+# close to the character.
+#
+# The other application needs only to know this:
+#
+# The navigator is initialized by calling nav_init(). This opens a
+# hidden window, assigned to the global variable nav_window, for
+# the navigator. It also assigns the navigator root vidget to the
+# global variable nav_root.
+#
+# To use the navigator, the other application needs to change the
+# canvas status nav_window to normal so it can accept user events
+# and hide it again when it has been "dismissed". The navigator
+# puts the selected file in nav_file as mentioned above.
+#
+# If the application wants to support the navigator's keyboard
+# shortcuts, it needs to set the shortcut procedure to nav_keyboard
+# when the navigator window is active.
+#
+# A typical event loop for using the navigator is:
+#
+# repeat { # event loop
+# case Active() of {
+# &window : { # application window
+# root_cur := root
+# shortcuts_cur := shortcuts
+# }
+# nav_window : { # navigation window
+# root_cur := nav_root
+# shortcuts_cur := nav_keyboard
+# }
+# }
+# ProcessEvent(root_cur, , shortcuts_cur)
+# case nav_state of {
+# &null : next
+# "Okay" : load_pattern()
+# }
+# nav_state := &null
+# WAttrib(nav_window, "canvas=hidden")
+# }
+#
+# where process_file() is a procedure that does something with the
+# file.
+#
+# Note that the value of nav_state determines what needs to be done. It is
+# null when the navigator has not been used since the last event. If
+# the navigator is dismissed with "Cancel" instead of "Okay", nothing
+# needs to be done except hide the navigator window and set the nav_state
+# to null.
+#
+# Coupled with this is a procedure (or more than one) that makes the
+# navigator window visible, as in
+#
+# procedure open_cb()
+# WAttrib(nav_window, "canvas=normal")
+# ...
+# return
+# end
+#
+# If there is more than one use of the navigator, the callbacks that
+# enable it can set process_file to the appropriate companion procedure.
+#
+############################################################################
+#
+# Requires: Version 9 graphics, UNIX
+#
+############################################################################
+#
+# Links: vsetup
+#
+############################################################################
+
+link vsetup
+
+$include "keysyms.icn"
+
+global directory
+global dir
+global file_list
+global files
+
+# Globals used to communicate with the application that uses the navigator
+
+global nav_file
+global nav_root
+global nav_state
+global nav_vidgets
+global nav_window
+
+procedure nav_init()
+ local window_save, atts
+
+ window_save := &window # save current subject window
+ &window := &null # clear for new subject
+ atts := navig_atts()
+ put(atts, "canvas=hidden")
+ (WOpen ! atts) | stop("*** can't open navigation window")
+ nav_vidgets := navig() # initialize interface
+ nav_window := &window # name navigation window
+ &window := window_save # restore previous subject window
+
+ files := nav_vidgets["files"]
+ nav_root := nav_vidgets["root"]
+
+ nav_file := &null
+
+ nav_refresh()
+
+ return
+
+end
+
+procedure nav_files_cb(vidget, value)
+ static last_file, last_time
+
+ initial {
+ last_file := ""
+ last_time := 0
+ }
+
+ if /value then {
+ last_time := 0
+ return
+ }
+
+ if value ?:= tab(upto('/')) then {
+ chdir(value)
+ nav_refresh()
+ return
+ }
+
+ nav_file := value
+
+ if (value == last_file) then {
+ last_file := ""
+ nav_state := "Okay"
+ return
+ }
+
+ last_time := 0
+ last_file := value
+
+ return
+
+end
+
+procedure nav_refresh()
+ local ls, input
+ static x, y
+
+ initial {
+ x := nav_vidgets["placeholder"].ax
+ y := nav_vidgets["placeholder"].ay
+ directory := ""
+ }
+
+ input := open("pwd", "p")
+
+ WAttrib( nav_window, "drawop=reverse")
+ DrawString(nav_window, x, y, directory)
+ DrawString(nav_window, x, y, directory := !input)
+ WAttrib(nav_window, "drawop=copy")
+
+ close(input)
+
+ file_list := []
+
+ ls := open("ls -a -p .", "p")
+
+ every put(file_list, !ls)
+
+ VSetItems(files, file_list)
+
+ close(ls)
+
+ return
+
+end
+
+procedure nav_okay_cb()
+
+ if /nav_file then {
+ Notice("No file selected.")
+ fail
+ }
+
+ nav_state := "Okay"
+
+ return
+
+end
+
+procedure nav_keyboard(e)
+
+ case e of {
+ "\r" : nav_okay_cb()
+ Key_Home : VSetState(files, 1)
+ Key_End : VSetState(files, *file_list)
+ default : if type(e) == "string" then nav_locate(e)
+ }
+
+ return
+
+end
+
+procedure nav_locate(e)
+ local i
+ static pos
+
+ initial pos := list(1)
+
+ every i := 1 to *file_list do {
+ if file_list[i] >>= e then break
+ }
+
+ pos[1] := i
+
+ VSetState(files, pos)
+
+ return
+
+end
+
+procedure nav_cancel_cb()
+
+ nav_state := "Cancel"
+
+ return
+
+end
+
+#===<<vib:begin>>=== modify using vib; do not remove this marker line
+procedure navig_atts()
+ return ["size=294,412", "bg=pale gray", "label=Navitrix"]
+end
+
+procedure navig(win, cbk)
+return vsetup(win, cbk,
+ ["navig:Sizer:::0,0,294,412:Navitrix",],
+ ["cancel:Button:regular::86,378,49,20:Cancel",nav_cancel_cb],
+ ["files:List:w::13,50,273,314:",nav_files_cb],
+ ["okay:Button:regular::21,378,49,20:Okay",nav_okay_cb],
+ ["placeholder:Button:regularno::20,22,65,17: ",],
+ ["refresh:Button:regular::224,378,56,20:Refresh",nav_refresh],
+ ["border:Rect:grooved::18,374,55,28:",nav_okay_cb],
+ )
+end
+#===<<vib:end>>=== end of section maintained by vib
diff --git a/ipl/gprocs/optwindw.icn b/ipl/gprocs/optwindw.icn
new file mode 100644
index 0000000..e034c6a
--- /dev/null
+++ b/ipl/gprocs/optwindw.icn
@@ -0,0 +1,177 @@
+############################################################################
+#
+# File: optwindw.icn
+#
+# Subject: Procedures to open window with standard options
+#
+# Author: Gregg M. Townsend
+#
+# Date: October 10, 1997
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# optwindow() opens a window, interpreting command options to
+# set various window attributes.
+#
+############################################################################
+#
+# optwindow(opttable, attribute...) -- open window based on option table
+#
+# optwindow returns a new X-window configured according to a table of
+# options such as that returned by options(). If a window cannot be
+# opened, the program is aborted.
+#
+# If any attribute arguments are supplied they are passed to the open call
+# ahead of anything generated from the option table.
+#
+# In general, upper-case letters are used for generic window options, and
+# any letters not listed below are reserved for future use. This leaves
+# the lower-case letters for program-specific options.
+#
+# The following options are recognized:
+#
+# -B color background color default: "pale gray"
+# -F color foreground color default: "black"
+# -L label window label (title) default: &progname (trimmed)
+# -T font text font default: unspecified
+#
+# -D display window device default: unspecified
+# -X xpos x position default: unspecified
+# -Y ypos y position default: unspecified
+# -W width window width default: 500
+# -H height window height default: 300
+# -M margin frame margin default: 0
+#
+# -S width,height window size default: 500,300 + margins
+# -P xpos,ypos window position default: unspecified
+# -G [wxh][+x+y] geometry, in usual X terms (but NOTE: no negative x | y)
+#
+# -! echo the window creation call on stderr (for debugging)
+#
+# -G is translated into -X -Y -W -H and overrides those values.
+# -P and -S override values from -G, -X, -Y, -W, and -H.
+#
+# Table values for {B,F,L,X,Y,W,H,M,P,S} are guaranteed to be set upon return.
+#
+# The "margin" is the internal border between the actual window frame and the
+# area used for display; you don't usually want to write right up to the edge.
+# If a negative value is given for -M, a standard margin of 10 pixels is set.
+# -M is added twice (for two margins) to -W and -H when calculating the actual
+# window size so that -W and -H reflect the actual usable area. If -W and -H
+# are derived from -G, which specifies actual window sizes, -M is twice
+# subtracted so that -W and -H always reflect the usable dimensions.
+#
+# winoptions() can be used to combine the above options with other options
+# for the options() call.
+#
+# Example:
+#
+# # get option table; allow standard options plus "-f filename"
+# opts := options(args, winoptions() || "f:")
+#
+# # set defaults if not given explicitly
+# /opts["W"] := 400 # usable width
+# /opts["H"] := 400 # usable height
+#
+# # open the window
+# win := optwindow(opts, "cursor=off")
+#
+# # save actual values given by the window manager
+# h := opts["H"] # usable height
+# w := opts["W"] # usable width
+# m := opts["M"] # specified margin
+#
+# (The usable area, then, is from (m,m) to (m+w, m+h).
+#
+############################################################################
+#
+# Includes: vdefns
+#
+############################################################################
+#
+# Requires: Version 9 graphics
+#
+############################################################################
+
+
+$include "vdefns.icn"
+
+
+procedure winoptions()
+ return "!B:D:F:L:T:X+Y+W+H+M+G:P:S:"
+end
+
+
+procedure optwindow(opts, args[]) #: open window with options
+ local a, w
+ /opts["F"] := "black"
+ /opts["B"] := VBackground
+ /opts["L"] := (&progname ? { while tab(upto('/')+1); tab(0)})
+ /opts["W"] := 500
+ /opts["H"] := 300
+ (/opts["M"] := 0) | (if opts["M"] < 0 then opts["M"] := 10)
+ \opts["G"] ? {
+ if any(&digits) then {
+ opts["W"] := integer(tab(many(&digits))) - 2*opts["M"] | Optw_Err("G")
+ tab(any('xX')) | Optw_Err("G")
+ opts["H"] := integer(tab(many(&digits))) - 2*opts["M"] | Optw_Err("G")
+ }
+ if not pos(0) then {
+ opts["X"] := integer(tab(any('+-'))||tab(many(&digits)))|Optw_Err("G")
+ opts["Y"] := integer(tab(any('+-'))||tab(many(&digits)))|Optw_Err("G")
+ }
+ if not pos(0) then
+ Optw_Err("G")
+ }
+ \opts["P"] ? {
+ opts["X"] := integer(tab(many('+-0123456789'))) | Optw_Err("P")
+ move(1)
+ opts["Y"] := integer(tab(many('+-0123456789'))) | Optw_Err("P")
+ if not pos(0) then
+ Optw_Err("P")
+ }
+ \opts["S"] ? {
+ opts["W"] := integer(tab(many(&digits))) - 2*opts["M"] | Optw_Err("S")
+ move(1)
+ opts["H"] := integer(tab(many(&digits))) - 2*opts["M"] | Optw_Err("S")
+ if not pos(0) then
+ Optw_Err("S")
+ }
+ if \opts["X"] & \opts["Y"] then
+ put(args, "pos=" || opts["X"] || "," || opts["Y"])
+ put(args, "display=" || \opts["D"])
+ put(args, "width=" || (opts["W"] + 2 * opts["M"]))
+ put(args, "height=" || (opts["H"] + 2 * opts["M"]))
+ put(args, "fg=" || opts["F"])
+ put(args, "bg=" || opts["B"])
+ push(args, "x")
+ push(args, opts["L"])
+ if \opts["!"] then {
+ writes(&errout, "open(\"", args[1])
+ every writes(&errout, "\",\"", args[2 to *args])
+ write(&errout, "\")")
+ }
+ w := open ! args | stop(args[1], ": can't open window")
+ if \opts["T"] then
+ Font(w, opts["T"]) | stop(args[1], ": invalid font: ", opts["T"])
+
+ # store actual values returned after window placement
+ WAttrib(w, "pos") ? {
+ opts["X"] := integer(tab(many('+-0123456789')))
+ move(1)
+ opts["Y"] := integer(tab(many('+-0123456789')))
+ }
+ opts["P"] := opts["X"] || "," || opts["Y"]
+ opts["W"] := WAttrib(w, "width") - 2 * opts["M"]
+ opts["H"] := WAttrib(w, "height") - 2 * opts["M"]
+ opts["S"] := WAttrib(w, "width") || "," || WAttrib(w, "height")
+ return w
+end
+
+procedure Optw_Err(ch)
+ stop("bad specification: -", ch, " ", &subject)
+end
diff --git a/ipl/gprocs/orbits.icn b/ipl/gprocs/orbits.icn
new file mode 100644
index 0000000..5377a61
--- /dev/null
+++ b/ipl/gprocs/orbits.icn
@@ -0,0 +1,82 @@
+############################################################################
+#
+# File: orbits.icn
+#
+# Subject: Procedures to produce traces of orbits
+#
+# Author: Ralph E. Griswold
+#
+# Date: May 2, 2001
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# These procedures produce traces of orbits. See
+#
+# Geometric and Artistic Graphics; Design Generation with
+# Microcomputers, Jean-Paul Delahaye, Macmillan, 1987, pp. 65-73.
+#
+# The arguments specify the starting positions, the extent of the
+# drawing, the number of segments, and various parameters that
+# control the orbit.
+#
+############################################################################
+#
+# Links: gobject
+#
+############################################################################
+
+link gobject
+
+procedure orbit1(x, y, extent, n, t1, t2, k1, k2, radius1, sscale,
+ xfact, yfact)
+ local incr1, incr2, real_n, angle1, angle2, i, radius2, loff
+
+ radius1 *:= extent #scaling
+ loff := 0.5 * extent
+ sscale *:= extent
+
+ real_n := real(n)
+ incr1 := 2 * &pi * t1 / n
+ incr2 := 2 * &pi * t2 / n
+ angle1 := angle2 := 0
+
+ every i := 1 to n do {
+ radius2 := sscale * (1 - i / real_n)
+ angle1 +:= incr1
+ angle2 +:= incr2
+ suspend Point(x + xfact * (loff + radius1 * cos(k1 * angle1) +
+ radius2 * cos(angle2)),
+ y + yfact * (loff + radius1 * sin(k2 * angle1) +
+ radius2 * sin(angle2)))
+ }
+
+end
+
+procedure orbit2(x, y, extent, n, t1, t2, k1, k2, radius1, sscale,
+ xfact, yfact, roff, rfact, rratio, div)
+ local incr1, incr2, rangle, angle1, angle2, i, radius2, loff
+
+ rangle := 2 * &pi / div * rratio
+ radius1 *:= extent #scaling
+ loff := 0.5 * extent
+ sscale *:= extent
+
+ incr1 := 2 * &pi * t1 / n
+ incr2 := 2 * &pi * t2 / n
+ angle1 := angle2 := 0
+
+ every i := 1 to n do {
+ radius2 := sscale * (roff + rfact * cos(i * rangle))
+ angle1 +:= incr1
+ angle2 +:= incr2
+ suspend Point(x + xfact * (loff + radius1 * cos(k1 * angle1) +
+ radius2 * cos(angle2)),
+ y + yfact * (loff + radius1 * sin(k2 * angle1) +
+ radius2 * sin(angle2)))
+ }
+
+end
diff --git a/ipl/gprocs/overlay.icn b/ipl/gprocs/overlay.icn
new file mode 100644
index 0000000..38c661f
--- /dev/null
+++ b/ipl/gprocs/overlay.icn
@@ -0,0 +1,48 @@
+############################################################################
+#
+# File: overlay.icn
+#
+# Subject: Procedure to overlay an image in a window
+#
+# Author: Ralph E. Griswold
+#
+# Date: May 26, 1994
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# overlay(window, image) writes the image in the window, a line at a time.
+#
+############################################################################
+#
+# Requires: Version 9 graphics
+#
+############################################################################
+#
+# Links: xcompat
+#
+############################################################################
+
+link xcompat
+
+procedure overlay(window, name)
+ local pixmap, width, height, x
+
+ pixmap := XBind(, , "image=" || name) |
+ stop("*** cannot bind image")
+
+ width := WAttrib(pixmap, "width")
+ height := WAttrib(pixmap, "height")
+
+ every x := 0 to width - 1 do
+ CopyArea(pixmap, window, x, 0, 1, height, x, 0)
+
+ close(pixmap)
+
+ return
+
+end
+
diff --git a/ipl/gprocs/palettes.icn b/ipl/gprocs/palettes.icn
new file mode 100644
index 0000000..a0f596e
--- /dev/null
+++ b/ipl/gprocs/palettes.icn
@@ -0,0 +1,405 @@
+############################################################################
+#
+# File: palettes.icn
+#
+# Subject: Procedures for programmer-defined palettes
+#
+# Author: Ralph E. Griswold
+#
+# Date: January 23, 2001
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# These procedures implement programmer-defined palettes. They overload
+# and build on top of the built-in palette mechanism.
+#
+############################################################################
+#
+# Data structures:
+#
+# Palette_() is a record that holds the information for a
+# programmer-defined palette. Its fields are:
+#
+# name: the name the palette is known by
+# keys: the string of the palette characters
+# table: a table keyed by the palette characters
+# whose corresponding values are the colors
+#
+# Color_() is a record that holds the components of an RGB
+# color in separate r, g, and b fields.
+#
+# PDB_ is a table whose keys are the names of programmer-
+# defined palettes and whose corresponding values are the
+# palettes. PDB_ is a global variable and provides the
+# way for programmer-defined palette procedures to access
+# a particular database. If it is null, a new database is
+# created.
+#
+# Procedures:
+#
+# BuiltinPalette(name)
+# succeeds if name is the name of a built-in palette but
+# fails otherwise.
+#
+# CreatePalette(name, keys, colors)
+# creates a new palette with the given colors and
+# corresponding keys. The colors used are the given ones.
+#
+# InitializePalettes()
+# initializes the built-in palette mechanism; it is called
+# by the first palette procedure that is called.
+#
+# Measure(color1, color2) returns the a measure of the distance
+# between color1 and color2 in RGB space.
+#
+# NearColor(name, color)
+# returns a color close to color in the palette name.
+#
+# PaletteChars(win, palette)
+# returns the palette characters of palette. It extends
+# the standard version.
+#
+# PaletteColor(win, palette, key)
+# returns color in palette for the given key. It extends
+# the standard version.
+#
+# PaletteKey(win, palette, color)
+# returns the key in palette closest to the given color.
+#
+# RGB(color)
+# parses RGB color and returns a corresponding record.
+#
+# makepalette(name, clist)
+# makes a palette from the list of colors, choosing
+# keys automatically.
+#
+# palette_colors(palette)
+#
+# returns the list of colors in palette.
+#
+# Procedures fail in case of errors. This leaves control and error
+# reporting to programs that use this module. This module is intended
+# to be used by programs that manage the necessary data and supply
+# the table through PDB_. The problem with this is that there is
+# no way to differentiate errors. A solution would be to post error
+# messages in a global variable.
+#
+# Limitations and problems:
+#
+# The names of built-in palettes may not be used for programmer-
+# defined ones.
+#
+# PaletteGrays() is not implemented for programmer-defined
+# palettes. The library version should work for built-in
+# palettes with this module linked.
+#
+# Transparency is not yet implemented for DrawImage().
+#
+# ReadImage() does not yet support programmer defined palettes.
+#
+# Not tested: Capture(), which may work.
+#
+# There is some library code that checks for the names of
+# built-in palettes in an ad-hoc fashion. It therefore is
+# not advisable to use names for programmer-defined palettes
+# that begin with "c" or "g" followed by a digit.
+#
+############################################################################
+#
+# Requires: Version 9 graphics
+#
+############################################################################
+#
+# Links: imrutils, lists, sort
+#
+############################################################################
+
+link imrutils
+link lists
+link sort
+
+global PDB_
+
+record Palette_(name, keys, table)
+record Color_(r, g, b)
+
+# Check for built-in palette
+
+procedure BuiltinPalette(name) #: check for built-in palette
+
+ BuiltinPalette := proc("PaletteChars", 0)
+
+ return BuiltinPalette(name)
+
+end
+
+procedure CreatePalette(name, keys, colors) #: create palette
+ local i, k, t
+
+ initial InitializePalettes()
+
+ if BuiltinPalette(name) then fail
+
+ if *keys ~= *cset(keys) then fail # duplicate keys
+
+ if *keys ~= *colors then fail # mismatch
+
+ t := table()
+
+ every i := 1 to *colors do
+ t[keys[i]] := ColorValue(colors[i]) | fail
+
+ PDB_[name] := Palette_(name, keys, t)
+
+ return PDB_[name]
+
+end
+
+# Extended version of DrawImage()
+
+procedure DrawImage(args[]) #: draw image
+ local palette_pixels, palette_lookup, keys, c, i, row, imr
+ static draw_image
+
+ initial draw_image := proc("DrawImage", 0)
+
+ if type(args[1]) ~== "window" then push(args, &window)
+
+ imr := imstoimr(args[4]) | return draw_image ! args
+
+ if BuiltinPalette(imr.palette) then return draw_image ! args
+
+ palette_lookup := (\PDB_[imr.palette]).table | fail
+ palette_pixels := copy(palette_lookup)
+
+ keys := cset(imr.pixels)
+
+ every !palette_pixels := [] # empty lists for coordinates
+
+ every c := !keys do {
+ i := 0
+ imr.pixels ? {
+ while row := move(imr.width) do {
+ row ? {
+ every put(palette_pixels[c], upto(c) - 1, i)
+ }
+ i +:= 1
+ }
+ }
+ }
+
+ every c := !keys do {
+ Fg(palette_lookup[c]) | fail # fails for invalid character
+ DrawPoint ! palette_pixels[c]
+ }
+
+ return
+
+end
+
+# Initialize defined palette mechanism
+
+procedure InitializePalettes() #: initialize palettes
+
+ /PDB_ := table()
+
+ if type(PDB_) ~== "table" then runerr(777)
+
+ InitializePalettes := 1 # make this procedure a no-op
+
+ return
+
+end
+
+procedure Measure(s1, s2) #: measure of RGB distance
+ local color1, color2
+
+ color1 := RGB(s1)
+ color2 := RGB(s2)
+
+ return (color1.r - color2.r) ^ 2 + (color1.g - color2.g) ^ 2 +
+ (color1.b - color2.b) ^ 2
+
+end
+
+# Get color close to specified key
+
+procedure NearColor(name, s) #: close color in palette
+ local palette_lookup, k, measure, close_key, color
+
+ measure := 3 * (2 ^ 16 - 1) ^ 2 # maximum
+
+ color := ColorValue(s) | fail
+
+ palette_lookup := (\PDB_[name]).table | fail
+
+ every k := key(palette_lookup) do
+ if measure >:= Measure(palette_lookup[k], color) then {
+ close_key := k
+ if measure = 0 then break
+ }
+
+ return \close_key
+
+end
+
+# Extended version of PaletteChars()
+
+procedure PaletteChars(args[]) #: characters in palette
+ local name
+ static palette_chars
+
+ initial {
+ InitializePalettes()
+ palette_chars := proc("PaletteChars", 0)
+ }
+
+ if type(args[1]) == "window" then get(args)
+
+ name := args[1]
+
+ if BuiltinPalette(name) then return palette_chars(name)
+ else return (\PDB_[name]).keys
+
+end
+
+# Extended version of PaletteColor()
+
+procedure PaletteColor(args[]) #: color for key in palette
+ local palette_lookup, name, s
+ static palette_color
+
+ initial {
+ InitializePalettes()
+ palette_color := proc("PaletteColor", 0)
+ }
+
+ if type(args[1]) == "window" then get(args)
+
+ name := args[1]
+ s := args[2]
+
+ if BuiltinPalette(name) then return palette_color(name, s)
+
+ palette_lookup := (\PDB_[name]).table | fail
+
+ return \palette_lookup[s]
+
+end
+
+# Extended version of PaletteKey()
+
+procedure PaletteKey(args[]) #: key for color in palette
+ local name, s
+ static palette_key
+
+ initial {
+ InitializePalettes()
+ palette_key := proc("PaletteKey", 0)
+ }
+
+ if type(args[1]) == "window" then get(args)
+
+ name := args[1]
+ s := args[2]
+
+ if BuiltinPalette(name) then return palette_key(name, s)
+ else return NearColor(name, s)
+
+end
+
+procedure RGB(s) #: convert RGB color to record
+ local color
+
+ color := Color_()
+
+ ColorValue(s) ? {
+ color.r := tab(upto(',')) &
+ move(1) &
+ color.g := tab(upto(',')) &
+ move(1) &
+ color.b := tab(0)
+ } | fail
+
+ return color
+
+end
+
+procedure makepalette(name, clist) #: make palette automatically
+ local keys
+ static alphan
+
+ initial alphan := &digits || &letters
+
+ if *clist = 0 then fail
+
+ keys :=
+ if *clist < *alphan then alphan
+ else &cset
+
+ CreatePalette(name, keys[1+:*clist], clist) | fail
+
+ return
+
+end
+
+procedure palette_colors(p) #: list of palette colors
+ local clist
+
+ clist := []
+
+ every put(clist, PaletteColor(p, !PaletteChars(p)))
+
+ return clist
+
+end
+
+procedure keyseq(palette, colors[]) #: sequence of palette keys
+ local chars
+
+ chars := PaletteChars(palette)
+
+ suspend upto(PaletteKey(palette, !colors), chars)
+
+end
+
+procedure color_range(color, range) #: adjust RGB range
+ local r, g, b
+
+ range := 2 ^ 16 / range
+
+ color ? {
+ r := tab(upto(','))
+ move(1)
+ g := tab(upto(','))
+ move(1)
+ b := tab(0)
+ return (r * range) || "," || (g * range) || "," || (b * range)
+ }
+
+end
+
+procedure colorseq(palette) #: sequence of palette colors
+
+ suspend PaletteColor(palette, !PaletteChars(palette))
+
+end
+
+procedure sort_colors(colors)
+
+ return isort(colors, value)
+
+end
+
+procedure value(s) #: RGB magnitude
+ local color
+
+ color := RGB(s)
+
+ return color.r ^ 2 + color.g ^ 2 + color.b ^ 2
+
+end
diff --git a/ipl/gprocs/pattread.icn b/ipl/gprocs/pattread.icn
new file mode 100644
index 0000000..d9613d5
--- /dev/null
+++ b/ipl/gprocs/pattread.icn
@@ -0,0 +1,42 @@
+############################################################################
+#
+# File: pattread.icn
+#
+# Subject: Procedure to read pattern
+#
+# Author: Ralph E. Griswold
+#
+# Date: December 10, 2001
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# Reads BLP or row file and produces pattern in row form.
+#
+############################################################################
+#
+# Links: patutils
+#
+############################################################################
+
+link patutils
+
+procedure pattread(file)
+ local line, rows
+
+ line := read(file) | fail
+
+ line ? {
+ if upto("#", line) then rows := pat2rows(line)
+ else {
+ rows := [line]
+ while put(rows, read(file)) # read in row pattern
+ }
+ }
+
+ return rows
+
+end
diff --git a/ipl/gprocs/patutils.icn b/ipl/gprocs/patutils.icn
new file mode 100644
index 0000000..8da4da3
--- /dev/null
+++ b/ipl/gprocs/patutils.icn
@@ -0,0 +1,584 @@
+############################################################################
+#
+# File: patutils.icn
+#
+# Subject: Procedures to manipulate patterns
+#
+# Author: Ralph E. Griswold
+#
+# Date: July 8, 2002
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# This file contains procedures that manipulate graphic pattern
+# representations. These procedures are intended for bi-level patterns
+# representable by 0s and 1s.
+#
+# A row pattern is a list of strings, with each string representing
+# a row in the pattern.
+#
+# DrawTile(win, xoff, yoff, pattern, magnif, mode)
+# DrawRows(win, xoff, yoff, rows, magnif, mode)
+# bits2hex(s)
+# decspec(pattern)
+# eqpats(prws, rows2)
+# getpatt(line)
+# getpattnote(line)
+# hex2bits(s)
+# hexspec(pattern)
+# legalpat(tile)
+# legaltile(tile)
+# pat2xbm(pattern, name)
+# tilebits(rows)
+# pdensity(pattern)
+# pix2pat(window, x, y, cols, rows)
+# readpatt(input)
+# readpattline(input)
+# rowbits(pattern)
+# pat2rows(pattern)
+# rows2pat(rlist)
+# showbits(pattern)
+# tiledim(pattern)
+# xbm2rows(input)
+#
+############################################################################
+#
+# Requires: Version 9 graphics
+#
+############################################################################
+#
+# Links: convert
+#
+############################################################################
+
+link convert
+
+record tdim(w, h)
+
+#
+# Draw a tile at a given location. If mode is nonnull, the
+# area on which the tile is drawn is erased.
+
+procedure DrawTile(win, xoff, yoff, pattern, magnif, mode)
+ local x, y, row, pixel, dims, arglist
+
+ if type(win) ~== "window" then {
+ win :=: xoff :=: yoff :=: pattern :=: mode
+ win := &window
+ }
+
+ /magnif := 1
+
+ y := yoff
+
+ if \mode then {
+ dims := tiledim(pattern)
+ EraseArea(xoff, yoff, dims.w * magnif, dims.h * magnif)
+ }
+
+ every row := rowbits(pattern) do { # draw a row
+ x := xoff
+ arglist := []
+
+ if magnif = 1 then {
+ every pixel := !row do {
+ if pixel == "1" then put(arglist, x, y)
+ x +:= 1
+ }
+ y +:= 1
+ }
+ else {
+ every pixel := !row do {
+ if pixel == "1" then put(arglist, x, y, magnif, magnif)
+ x +:= magnif
+ }
+ y +:= magnif
+ }
+ if *arglist = 0 then next
+ if magnif = 1 then DrawPoint ! arglist else FillRectangle ! arglist
+ }
+
+ return
+
+end
+#
+# Draw rows at a given location. If mode is nonnull, the
+# area on which the tile is drawn is erased.
+
+procedure DrawRows(win, xoff, yoff, rows, magnif, mode)
+ local x, y, row, pixel, arglist
+
+ if type(win) ~== "window" then {
+ win :=: xoff :=: yoff :=: rows :=: magnif :=: mode
+ win := &window
+ }
+
+ /magnif := 1
+
+ y := yoff
+
+ if \mode then
+ EraseArea(xoff, yoff, *rows[1] * magnif, *rows * magnif)
+
+ every row := !rows do { # draw a row
+ x := xoff
+ arglist := []
+
+ if magnif = 1 then {
+ every pixel := !row do {
+ if pixel == "1" then put(arglist, x, y)
+ x +:= 1
+ }
+ y +:= 1
+ }
+ else {
+ every pixel := !row do {
+ if pixel = "1" then put(arglist, x, y, magnif, magnif)
+ x +:= magnif
+ }
+ y +:= magnif
+ }
+ if *arglist = 0 then next
+ if magnif = 1 then DrawPoint ! arglist else FillRectangle ! arglist
+ }
+
+ return
+
+end
+
+#
+# Convert bit string to hex pattern string
+
+procedure bits2hex(s)
+ static bittab
+ local hex
+
+ initial {
+ bittab := table()
+ bittab["0000"] := "0"
+ bittab["1000"] := "1"
+ bittab["0100"] := "2"
+ bittab["1100"] := "3"
+ bittab["0010"] := "4"
+ bittab["1010"] := "5"
+ bittab["0110"] := "6"
+ bittab["1110"] := "7"
+ bittab["0001"] := "8"
+ bittab["1001"] := "9"
+ bittab["0101"] := "a"
+ bittab["1101"] := "b"
+ bittab["0011"] := "c"
+ bittab["1011"] := "d"
+ bittab["0111"] := "e"
+ bittab["1111"] := "f"
+ }
+
+ hex := ""
+
+ s ? {
+ while hex := bittab[move(4)] || hex
+ if not pos(0) then hex := bittab[left(tab(0), 4, "0")] || hex
+ }
+
+ return hex
+
+end
+
+#
+# Convert pattern specification to decimal form
+
+procedure decspec(pattern)
+ local cols, chunk, dec
+
+ pattern ? {
+ if not upto("#") then return pattern
+ cols := tab(upto(','))
+ move(2)
+ chunk := (cols + 3) / 4
+ dec := cols || ","
+ while dec ||:= integer("16r" || move(chunk)) || ","
+ }
+
+ return dec[1:-1]
+
+end
+
+procedure eqpats(rows1, rows2) #: test row patterns for equality
+ local i
+
+ if (*rows1 ~= *rows2) | (*rows1[1] ~= *rows2[1]) then fail
+
+ every i := 1 to *rows1 do
+ if rows1[i] ~== rows2[i] then fail
+
+ return rows2
+
+end
+
+#
+# Get pattern from line. It trims off leading and trailing whitespace
+# and removes any annotation (beginning with a # after the first whitespace
+
+procedure getpatt(line)
+
+ line ? {
+ tab(many(' \t'))
+ return tab(upto(' \t') | 0)
+ }
+
+end
+
+#
+# Get pattern annotation. It returns an empty string if there is
+# no annotation.
+
+procedure getpattnote(line)
+
+ line ? {
+ tab(many(' \t')) # remove leading whitespace
+ tab(upto(' \t')) | return "" # skip pattern
+ tab(upto('#')) | return "" # get to annotation
+ tab(many('# \t')) # get rid of leading junk
+ return tab(0) # annotation
+ }
+
+end
+
+# Convert hexadecimal string to bits
+
+procedure hex2bits(s)
+ static hextab
+ local bits
+
+ initial {
+ hextab := table()
+ hextab["0"] := "0000"
+ hextab["1"] := "0001"
+ hextab["2"] := "0010"
+ hextab["3"] := "0011"
+ hextab["4"] := "0100"
+ hextab["5"] := "0101"
+ hextab["6"] := "0110"
+ hextab["7"] := "0111"
+ hextab["8"] := "1000"
+ hextab["9"] := "1001"
+ hextab["a"] := "1010"
+ hextab["b"] := "1011"
+ hextab["c"] := "1100"
+ hextab["d"] := "1101"
+ hextab["e"] := "1110"
+ hextab["f"] := "1111"
+ }
+
+ bits := ""
+
+ map(s) ? {
+ while bits ||:= hextab[move(1)]
+ }
+
+ return bits
+
+end
+
+#
+# Convert pattern to hexadecimal form
+
+procedure hexspec(pattern)
+ local cols, chunk, hex
+
+ pattern ? {
+ if find("#") then return pattern
+ cols := tab(upto(','))
+ move(1)
+ chunk := (cols + 3) / 4
+ hex := cols || ",#"
+ while hex ||:= right(exbase10(tab(upto(',') | 0), 16), chunk, "0") do
+ move(1) | break
+ }
+
+ return hex
+
+end
+
+#
+# Succeed if tile is legal and small enough for (X) pattern. Other
+# windows systems may be more restrictive.
+
+procedure legalpat(tile)
+
+ if not legaltile(tile) then fail
+
+ tile ? {
+ if 0 < integer(tab(upto(','))) <= 32 then return tile
+ else fail
+ }
+
+end
+
+#
+# Succeed if tile is legal. Accepts tiles that are too big for
+# patterns.
+
+procedure legaltile(tile)
+
+ map(tile) ? { # first check syntax
+ (tab(many(&digits)) & =",") | fail
+ if ="#" then (tab(many('0123456789abcdef')) & pos(0)) | fail
+ else {
+ while tab(many(&digits)) do {
+ if pos(0) then break # okay; end of string
+ else ="," | fail
+ }
+ if not pos(0) then fail # non-digit
+ }
+ }
+
+ return hexspec(decspec(tile)) == tile
+
+end
+
+#
+# Convert pattern specification to an XBM image file.
+
+procedure pat2xbm(pattern, name)
+ local dims, chunk, row
+
+ /name := "noname"
+
+ dims := tiledim(pattern)
+
+
+ write("#define ", name, "_width ", dims.w)
+ write("#define ", name, "_height ", dims.h)
+ write("static char ", name, "_bits[] = {")
+
+ chunk := (dims.w + 3) / 4
+
+ pattern ? {
+ tab(upto('#') + 1)
+ while row := move(chunk) do {
+ if *row % 2 ~= 0 then row := "0" || row
+ row ? {
+ tab(0)
+ while writes("0x", move(-2), ",")
+ }
+ write()
+ }
+ }
+
+ write("};")
+
+end
+
+#
+# Count the number of bits set in a tile
+
+procedure tilebits(rows)
+ local bits
+
+ bits := 0
+
+ every bits +:= !!rows
+
+ return bits
+
+end
+
+#
+# Compute density (percentage of black bits) of pattern
+
+procedure pdensity(pattern)
+
+ local dark, dims
+
+ dims := tiledim(pattern)
+
+ hexspec(pattern) ? {
+ dark := 0
+ every rowbits(pattern) ? {
+ every upto('1') do
+ dark +:= 1
+ }
+ return dark / real(dims.w * dims.h)
+ }
+
+end
+
+#
+# Procedure to produce pattern specification from a square section of a window.
+
+procedure pix2pat(window, x, y, cols, rows)
+ local c, j, tile, pattern, pixels, y0
+
+ pattern := ""
+
+ every y0 := 0 to rows - 1 do {
+ pixels := ""
+ every j := 0 to cols - 1 do
+ every c := Pixel(window, x + j, y0 + y, 1, 1) do
+ pixels ||:= (if c == "0,0,0" then "1" else "0")
+ pattern ||:= bits2hex(pixels)
+ }
+
+ if *pattern = 0 then fail # out of bounds specification
+ else return cols || ",#" || pattern
+
+end
+
+#
+# Read pattern. It skips lines starting with a #,
+# empty lines, and trims off any trailing characters after the
+# first whitespace of a pattern.
+
+procedure readpatt(input)
+ local line
+
+ while line := read(input) do
+ line ? {
+ if pos(0) | ="#" then next
+ return tab(upto(' \t') | 0)
+ }
+
+ fail
+
+end
+
+#
+# Read pattern line. It skips lines starting with a # and empty lines but
+# does not trim off any trailing characters after the first whitespace of
+# a pattern.
+
+procedure readpattline(input)
+ local line
+
+ while line := read(input) do
+ line ? {
+ if pos(0) | ="#" then next
+ return tab(0)
+ }
+
+ fail
+
+end
+
+#
+# Generate rows of bits in a pattern. Doesn't work correctly for small
+# patterns. (Why?)
+
+procedure rowbits(pattern)
+ local row, dims, chunk, hex
+
+ dims := tiledim(pattern)
+
+ hexspec(pattern) ? {
+ tab(upto(',') + 2)
+ hex := tab(0)
+ chunk := *hex / dims.h
+ hex ? {
+ while row := right(hex2bits(move(chunk)), dims.w, "0") do
+ suspend reverse(row)
+ }
+ }
+
+end
+
+#
+# Produce a list of the rows of a pattern
+
+procedure pat2rows(pattern)
+ local rlist
+
+ rlist := []
+
+ every put(rlist, rowbits(pattern))
+
+ return rlist
+
+end
+
+#
+# Convert row list to pattern specification
+
+procedure rows2pat(rlist)
+ local pattern
+
+ pattern := *rlist[1] || ",#"
+
+ every pattern ||:= bits2hex(!rlist)
+
+ return pattern
+
+end
+
+# Show bits of a pattern
+
+procedure showbits(pattern)
+
+ every write(rowbits(pattern))
+
+ write()
+
+ return
+
+end
+
+
+#
+# Produce dimensions of the tile for a pattern
+
+procedure tiledim(pattern)
+ local cols
+
+ hexspec(pattern) ? {
+ cols := integer(tab(upto(',')))
+ =",#" | fail
+ return tdim(cols, *tab(0) / ((cols + 3) / 4))
+ }
+
+end
+
+#
+# Generate rows of bits from an XBM file
+
+procedure xbm2rows(input)
+ local image, bits, row, hex, width, height, chunks
+
+ image := ""
+
+ read(input) ? {
+ tab(find("width") + 6)
+ tab(upto(&digits))
+ width := integer(tab(many(&digits)))
+ }
+
+ read(input) ? {
+ tab(find("height") + 6)
+ tab(upto(&digits))
+ height := integer(tab(many(&digits)))
+ }
+
+ chunks := (width / 8) + if (width % 8) > 0 then 1 else 0
+
+ while image ||:= reads(input, 500000) # Boo! -- can do better
+
+ image ? {
+ every 1 to height do {
+ row := ""
+ every 1 to chunks do {
+ tab(find("0x") + 2)
+ hex := move(2) # a bit of optimization
+ row ||:= case hex of {
+ "00": "00000000"
+ "ff": "11111111"
+ default: reverse(right(hex2bits(hex), 8, "0"))
+ }
+ }
+ suspend left(row, width)
+ }
+ }
+
+end
diff --git a/ipl/gprocs/patxform.icn b/ipl/gprocs/patxform.icn
new file mode 100644
index 0000000..fb5ba97
--- /dev/null
+++ b/ipl/gprocs/patxform.icn
@@ -0,0 +1,504 @@
+############################################################################
+#
+# File: patxform.icn
+#
+# Subject: Procedures to transform patterns in row form
+#
+# Author: Ralph E. Griswold
+#
+# Date: June 26, 2002
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# pborder(rows, l, r, t, b, c)
+# pcaten(rows1, rows2, dir)
+# pcenter(rows, w, h)
+# pcrop(rows, l, r, t, b)
+# pdisplay(rows)
+# pdouble(rows, dir)
+# pflip(rows, dir)
+# phalve(rows, dir, choice)
+# pinvert(rows)
+# pminim(rows)
+# por(rows1, rows2)
+# protate(rows, dir)
+# pscramble(rows, dir)
+# pshift(rows, shift, dir)
+# ptrim(rows, c)
+#
+############################################################################
+#
+# Requires: Version 9 graphics
+#
+############################################################################
+#
+# Links: patutils, random, strings
+#
+############################################################################
+
+link patutils
+link random
+link strings
+
+#
+# Place a border around a pattern. l, r, t, and b specify the number of bits
+# to add at the left, right, top, and bottom, respectively. c specifies
+# the color of the border, "0" for white, "1" for black.
+
+procedure pborder(rows, l, r, t, b, c) #: place border around pattern
+ local i, row, left, right
+
+ /l := 1
+ /r := 1
+ /t := 1
+ /b := 1
+ /c := "0"
+
+ if l = r = t = b = 0 then return rows
+
+ row := repl(c, *rows[1] + l + r)
+ left := repl(c, l)
+ right := repl(c, r)
+
+ every i := 1 to *rows do
+ rows[i] := left || rows[i] || right
+
+ every 1 to t do
+ push(rows, row)
+
+ every 1 to b do
+ put(rows, row)
+
+ return rows
+
+end
+
+#
+# Concatenate patterns
+
+procedure pcaten(rows1, rows2, dir) #: concatenate patterns
+ local rows, i
+
+ # if art is nonnull, delete duplicate line at boundary
+
+ if (*rows1 ~= *rows2) | (*rows1[1] ~= *rows2[1]) then
+ stop("nonconformal patterns in pcaten()")
+
+ /dir := "h"
+
+ case dir of {
+ "h" : {
+ rows := []
+ every i := 1 to *rows1 do
+ put(rows, rows1[i] || rows2[i])
+ }
+ "v" : {
+ rows := copy(rows1)
+ every put(rows, !rows2)
+ }
+ default: stop("invalid direction specification in pcaten()")
+ }
+
+ return rows
+
+end
+
+#
+# Concatenate patterns pattern style
+
+procedure pcatenp(rows1, rows2, dir)
+ local rows, i
+
+ if (*rows1 ~= *rows2) | (*rows1[1] ~= *rows2[1]) then
+ stop("nonconformal patterns in pcaten()")
+
+ /dir := "h"
+
+ rows2 := copy(rows2) # may delete row or column
+
+ case dir of {
+ "h" : {
+ repeat {
+ every i := 1 to *rows1 do
+ if rows1[i][-1] ~== rows2[i][1] then break break
+ every i := 1 to *rows2 do
+ rows2[i][1] := ""
+ break
+ }
+ rows := []
+ every i := 1 to *rows1 do
+ put(rows, rows1[i] || rows2[i])
+ }
+ "v" : {
+ if rows1[-1] == rows2[1] then # eliminate duplicate
+ get(rows2)
+ rows := copy(rows1)
+ every put(rows, !rows2)
+ }
+ default: stop("invalid direction specification in pcaten()")
+ }
+
+ return rows
+
+end
+
+#
+# Centers non-white portion of pattern
+
+procedure pcenter(rows, w, h) #: center pattern
+ local rw, rh, vert, horz, t, l
+
+ rows := ptrim(rows)
+
+ rw := *rows[1]
+ rh := *rows
+
+ if (rh = h) & (rw = w) then return rows
+ if (rh > h) | (rw > w) then fail
+
+ horz := w - rw
+ vert := h - rh
+ l := horz / 2
+ t := vert / 2
+
+ return pborder(rows, l, horz - l, t, vert - t)
+
+end
+
+#
+# Crop a pattern. l, r, t, and b specify the number of bits
+# to crop at the left, right, top, and bottom, respectively.
+
+procedure pcrop(rows, l, r, t, b) #: crop pattern
+ local i
+
+ /l := 0
+ /r := 0
+ /t := 0
+ /b := 0
+
+ if l = r = t = b = 0 then return rows
+
+ if ((*rows[1] - l - r) | (*rows - t - b)) < 2 then fail
+
+ every 1 to t do
+ get(rows)
+
+ every 1 to b do
+ pull(rows)
+
+ every i := 1 to *rows do
+ rows[i] := rows[i][l + 1 : -r]
+
+ return rows
+
+end
+
+#
+# Display pattern
+
+procedure pdisplay(rows, pat) #: display pattern
+
+ /pat := "01" # mapping string
+
+ every write(map(!rows, "01", pat))
+
+ return
+
+end
+
+#
+# Creates a tile in which each pixel doubled. dir determines the
+# direction in which the doubling is done. If dir is "b" or null, it's
+# done both horizontally and vertically. If dir is "v", it's only done
+# vertically, while if dir is "h", it's done only horizontally.
+
+procedure pdouble(rows, dir) #: double pattern
+ local row, newrows
+
+ newrows := []
+
+ case dir of {
+ "v": {
+ every row := !rows do
+ put(newrows, row, row)
+ }
+ "h": {
+ every row := !rows do
+ put(newrows, collate(row, row))
+ }
+ "b" | &null: return pdouble(pdouble(rows, "v"), "h")
+ }
+
+ return newrows
+
+end
+
+#
+# Flip pattern. The possible values of dir are "h" (horizontal flip),
+# "v" (vertical flip), "l" (left diagonal), and "r" (right diagonal).
+# (The left diagonal extends from the upper left corner to the bottom
+# right corner; the right diagonal from the upper right to the lower
+# left.
+
+procedure pflip(rows, dir) #: flip pattern
+ local newrows, x, y, i
+
+ case dir of {
+ "l": {
+ newrows := list(*rows[1], repl("0", *rows))
+ every y := 1 to *rows do
+ every x := 1 to *rows[1] do
+ if rows[y, x] == "1" then
+ newrows[-x, -y] := "1"
+ }
+ "r": {
+ newrows := list(*rows[1], repl("0", *rows))
+ every y := 1 to *rows do
+ every x := 1 to *rows[1] do
+ if rows[y, x] == "1" then
+ newrows[x, y] := "1"
+ }
+ "h": {
+ newrows := copy(rows)
+ every i := 1 to *rows do
+ newrows[i] := reverse(newrows[i])
+ }
+ "v": {
+ newrows := copy(rows)
+ every i := 1 to *rows / 2 do
+ newrows[i] :=: newrows[-i]
+ }
+ default: stop("*** illegal flip specification in pflip()")
+ }
+
+ return newrows
+
+end
+
+# Creates a tile in every other pixel is discarded. dir determines the
+# direction is which the halving is done. If dir is "b" or null, it's
+# done both vertically and horizontally. If dir is "v", it's only done
+# vertically, while if dir is "v", it's done only vertically.
+# If choice is "o" or null, odd-numbered rows or columns are kept;
+# if "e", the even-numbered ones.
+
+procedure phalve(rows, dir, choice) #: halve pattern by bits
+ local newrows, i
+
+ choice := if choice === ("o" | &null) then 1 else 0
+ newrows := []
+
+ case dir of {
+ "v": {
+ every i := choice to *rows by 2 do
+ put(newrows, rows[i])
+ }
+ "h": every put(newrows, decollate(!rows, choice))
+ "b" | &null: return phalve(phalve(rows, "v", choice), "h", choice)
+ }
+
+ return newrows
+
+end
+
+#
+# Invert white and black bits in pattern specification
+
+procedure pinvert(rows) #: invert B&W pattern
+ local i
+
+ rows := copy(rows)
+
+ every i := 1 to *rows do
+ rows[i] := map(rows[i], "10", "01")
+
+ return rows
+
+end
+
+#
+# Reduce pattern to its smallest equivalent form (with at least 4 columns).
+# Limited to square patterns for portability -- other possibilities exist
+# for operating on and/or producing patterns that are not square.
+
+
+procedure pminim(rows) #: minimize pattern
+ local halfw, halfh, i
+
+# if (*rows ~= *rows[1]) | (*rows % 2 ~= 0) then return rows
+
+ repeat {
+
+ if *rows[1] < 4 then break
+
+ halfw := *rows[1] / 2
+ halfh := *rows / 2
+
+ every i := 1 to halfh do # check rows in top and bottom
+ if (rows[i] ~== rows[i + halfh]) |
+ (rows[i][1+:halfw] ~== rows[i][0-:halfw]) then break break
+
+ every 1 to halfh do # reducible; remove rows
+ pop(rows)
+
+ every i := 1 to halfh do # truncate rows
+ rows[i] := rows[i][1+:halfw]
+
+ }
+
+ return rows
+
+end
+
+# For the logical "or" of two row bit patterns
+
+procedure por(rows1, rows2) #: "or" patterns
+ local rows, i
+
+ if *rows1 ~= *rows2 then fail # nonconformal
+ if *rows1[1] ~= *rows2[1] then fail # nonconformal
+
+ rows := copy(rows1)
+
+ every i := 1 to *rows do {
+ rows2[i] ? { # overlay 1s of row2 on row1
+ while tab(upto('1')) do {
+ rows[i][&pos] := "1"
+ move(1) | break
+ }
+ }
+ }
+
+ return rows
+
+end
+
+# Create rotated copy of a pattern. If dir is "cw" or "90", rotation is 90
+# degrees clockwise; if "ccw" or "-90", 90 degrees counter-clockwise.
+# If dir is "180", rotation is 180 degrees. The default is "cw".
+
+procedure protate(rows, dir) #: rotate pattern
+ local newrows, i, row, pix
+
+ /dir := "cw"
+
+ case string(dir) of {
+ "ccw" | "-90": { # counter-clockwise
+ newrows := list(*rows[1], "")
+ every row := !rows do {
+ i := 0
+ every pix := !row do
+ newrows[i -:= 1] ||:= pix
+ }
+ }
+ "cw" | "90" | &null: { # clockwise
+ newrows := list(*rows[1], "")
+ every row := !rows do {
+ i := 0
+ every pix := !row do
+ newrows[i +:= 1] := pix || newrows[i]
+ }
+ }
+ "180": {
+ newrows := []
+ every push(newrows, reverse(!rows))
+ }
+ default: stop("*** illegal rotation specification in protate()")
+ }
+
+ return newrows
+
+end
+
+#
+# Scrambles a pattern by shuffling it. If dir is "h", the columns of each row
+# are scrambled; if "v", the the rows are scrambled. If "b", bits are
+# scrambled throughout the pattern.
+
+procedure pscramble(rows, dir) #: scramble pattern
+ local i, all
+
+ case dir of {
+ "h": {
+ every i := 1 to *rows do
+ rows[i] := shuffle(rows[i])
+ }
+ "v": rows := shuffle(rows)
+ "b" | &null: {
+ all := ""
+ every all ||:= !rows
+ all := shuffle(all)
+ every i := 1 to *rows do {
+ rows[i] := left(all, *rows[1])
+ all[1 +: *rows[1]] := ""
+ }
+ }
+ default: stop("*** illegal specification in scramble()")
+ }
+
+ return rows
+
+end
+
+
+#
+# Create bit-shifted copy of a pattern. If dir is "h", then the
+# shift is horizontal; if "v", vertical. The default is horizontal.
+# Positive shift is to the right for horizontal shifts, downward for vertical
+# shifts. The default shift is 0 and the default direction is horizontal.
+
+procedure pshift(rows, shift, dir) #: bit shift pattern
+ local i
+
+ /shift := 0
+
+ case dir of {
+ "h" | &null: { # horizontal shift
+ every i := 1 to *rows do
+ rows[i] := rotate(rows[i], -shift)
+ }
+ "v": { # vertical shift
+ if shift > 0 then
+ every 1 to shift do
+ push(rows, pull(rows))
+ else if shift < 0 then
+ every 1 to -shift do
+ put(rows, pop(rows))
+ }
+ default: stop("*** illegal specification in pshift()")
+ }
+
+ return rows
+
+end
+
+#
+# Trim border from pattern; c gives color; default "1"
+
+procedure ptrim(rows, c) #: trim pattern
+
+ /c := '1'
+ c := cset(c)
+
+ while (*rows > 2) & not(upto(c, rows[1])) do
+ get(rows)
+
+ while (*rows > 2) & not(upto(c, rows[-1])) do
+ pull(rows)
+
+ rows := protate(rows, "cw")
+
+ while (*rows > 2) & not(upto(c, rows[1])) do
+ get(rows)
+
+ while (*rows > 2) & not(upto(c, rows[-1])) do
+ pull(rows)
+
+ return protate(rows, "ccw")
+
+end
diff --git a/ipl/gprocs/pixelmap.icn b/ipl/gprocs/pixelmap.icn
new file mode 100644
index 0000000..7160e28
--- /dev/null
+++ b/ipl/gprocs/pixelmap.icn
@@ -0,0 +1,59 @@
+############################################################################
+#
+# File: pixelmap.icn
+#
+# Subject: Procedure to create image from pixel list
+#
+# Author: Ralph E. Griswold
+#
+# Date: January 23, 1995
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# pixelmap(name, p, args[]) reads the pixel list in file name and
+# constructs an image, applying p ! args to each pixel. If p is
+# omitted or null, the pixels are used as-is.
+#
+############################################################################
+#
+# Requires: Version 9 graphics
+#
+############################################################################
+#
+# Links: wopen
+#
+############################################################################
+
+link wopen
+
+procedure pixelmap(name, p, args[])
+ local input, width, height, x, y, win
+
+ /p := 1
+ push(args) # place holder
+
+ input := open(name) | stop("*** cannot open pixel list")
+
+ read(input) ? {
+ ="width=" &
+ width := tab(many(&digits)) &
+ =" height=" &
+ height := tab(many(&digits))
+ } | stop("*** invalid pixel list header")
+
+ win := WOpen("width=" || width, "height=" || height)
+
+ every y := 0 to height - 1 do
+ every x := 0 to width - 1 do {
+ args[1] := read(input) | stop("*** short data in pixel list")
+ Fg(win, p ! args)
+ DrawPoint(x, y)
+ }
+
+ return win
+
+end
diff --git a/ipl/gprocs/popular.icn b/ipl/gprocs/popular.icn
new file mode 100644
index 0000000..0a68a09
--- /dev/null
+++ b/ipl/gprocs/popular.icn
@@ -0,0 +1,54 @@
+############################################################################
+#
+# File: popular.icn
+#
+# Subject: Procedure to show "popularity" of colors in image string
+#
+# Author: Ralph E. Griswold
+#
+# Date: September 17, 1998
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# This procedure shows the "popularity" of colors in an image string.
+#
+############################################################################
+#
+# Requires: Version 9 graphics
+#
+############################################################################
+#
+# Links: imrutils, wopen
+#
+############################################################################
+
+link imrutils
+link wopen
+
+procedure popularity(ims) #: color popularity in image string
+ local imr, color_tbl, color_list, color
+
+ imr := imstoimr(ims)
+
+ color_tbl := table(0)
+
+ every color_tbl[PaletteColor(imr.palette, !imr.pixels)] +:= 1
+
+ color_list := sort(color_tbl, 4)
+
+ write("dimensions: ", imr.width, "x", imr.height)
+ write("pixels: ", *imr.pixels)
+ write("palette: ", imr.palette)
+ write("number of different colors: ", *color_tbl)
+ write()
+ write("color popularity:")
+ write()
+
+ while color := pull(color_list) do
+ write(left(pull(color_list), 20), right(color, 6))
+
+end
diff --git a/ipl/gprocs/psrecord.icn b/ipl/gprocs/psrecord.icn
new file mode 100644
index 0000000..a72129d
--- /dev/null
+++ b/ipl/gprocs/psrecord.icn
@@ -0,0 +1,555 @@
+############################################################################
+#
+# File: psrecord.icn
+#
+# Subject: Procedures for PostScript record of window
+#
+# Author: Gregg M. Townsend
+#
+# Date: May 10, 1999
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# Contributors: Stephen B. Wampler and Ralph E. Griswold
+#
+############################################################################
+#
+# These procedures intercept graphics calls in order to produce
+# a PostScript copy of what is drawn. The record is decidedly
+# imperfect.
+#
+############################################################################
+#
+# These procedures produce a PostScript record of the screen display
+# of an Icon program. The technique used is to intercept calls to
+# graphics functions and write PostScript before calling the built-in
+# versions.
+#
+# Because the X emulation is imperfect, psrecord works best for
+# programs designed with it in mind. Not all function calls are
+# intercepted; some such as CopyArea cannot be handled at all. The
+# list of functions is in the internal routine PS_swap(). It is assumed
+# that there is only a single window and a single graphics context;
+# programs that switch among multiple graphics contexts will not be
+# recorded properly.
+#
+# PostScript recording is enabled by calling PSEnable(window, filename)
+# any time after after the window has been opened. (The procedures in
+# "autopost.icn" may be used for this.) Defaults for PSEnable are
+# &window and "xlog.ps". At the end, PSDone() should be called to
+# properly terminate the file; when PSDone() is not called, the file is
+# still be legal but lacks the "showpage" command needed for printing.
+#
+# If the argument to PSDone is non-null, no showpage is written.
+# This is recommended for Encapsulated PostScript that is to be
+# placed in documents, since otherwise the bounding box resulting
+# from showpage may interfere with document layout. showpage is, of
+# course, needed for PostScript that is to be printed stand-alone.
+#
+# Additional procedures provide more detailed control but must be used
+# with care. PSDisable() and PSEnable() turn recording off and back on;
+# any graphics state changes during this time (such as changing the
+# foreground color) are lost. PSSnap() inserts a "copypage" command in
+# the output; this prints a snapshot of the partially constructed page
+# without erasing it. PSRaw() writes a line of PostScript to the output
+# file.
+#
+# PSStart(window, filename) is similar to PSEnable except that it
+# always starts a fresh output file each time it is called.
+#
+# The output file is legal Encapsulated PostScript unless PSSnap is
+# used; PSSnap renders the output nonconforming because by definition
+# an EPS file consists of a single page and does not contain a "copypage"
+# command. It should be possible to postprocess such output to make a
+# set of legal EPS files.
+#
+# Some of the other limitations are as follows:
+# Only a few font names are recognized, and scaling is inexact.
+# Newlines in DrawString() calls are not interpreted.
+# Output via write() or writes() is not recorded.
+# The echoing of characters by read() or reads() is not recorded.
+# DrawCurve output is approximated by straight line segments.
+# Window resizing is ignored.
+# Drawing arguments must be explicit; few defaults are supplied.
+#
+############################################################################
+#
+# Requires: Version 9 graphics
+#
+############################################################################
+
+global PS_active, PS_f, PS_win, PS_width, PS_height
+
+######################### External Functions #########################
+
+# PSEnable(window, filename) -- enable PostScript recording.
+#
+# window and filename are significant only on the very first call.
+
+procedure PSEnable(w, f) #: enable PostScript recording
+ initial PS_init(w, f)
+ if /PS_active := 1 then
+ PS_swap()
+ return
+end
+
+# PSSnap() -- take snapshot at this point
+
+procedure PSSnap() #: take PostScript snapshot
+ static inited
+ if /PS_active then
+ fail
+ if /inited := 1 then {
+ seek(PS_f, 1)
+ write(PS_f, "%! nonconforming.......") # overwrite 1st line
+ seek(PS_f, 0)
+ }
+ PS_out("copypage")
+ return
+end
+
+# PSRaw(s) -- output a line of raw PostScript (at user's own risk)
+
+procedure PSRaw(s) #: output raw PostScript
+ if /PS_active then
+ fail
+ return write(PS_f, s)
+end
+
+# PSDisable() -- temporarily turn off recording
+
+procedure PSDisable() #: disable PostScript recording
+ if \PS_active := &null then
+ PS_swap()
+ return
+end
+
+# PSDone(sw) -- terminate output
+
+procedure PSDone(sw) #: terminate PostScript recording
+ initial PS_init()
+ PSDisable()
+ if /sw then PS_out("showpage") # if sw nonnull, do not output
+ PS_out("%%EOF")
+ close(PS_f)
+ return
+end
+
+######################### Internal Functions #########################
+
+# PS_swap() -- swap local functions for the real versions
+
+procedure PS_swap()
+ PS_attrib :=: WAttrib
+ PS_bg :=: Bg
+ PS_clip :=: Clip
+ PS_drawarc :=: DrawArc
+ PS_drawcircle :=: DrawCircle
+ PS_drawcurve :=: DrawCurve
+ PS_drawline :=: DrawLine
+ PS_drawrect :=: DrawRectangle
+ PS_drawpoint :=: DrawPoint
+ PS_drawsegment :=: DrawSegment
+ PS_drawstring :=: DrawString
+ PS_erasearea :=: EraseArea
+ PS_fg :=: Fg
+ PS_fillarc :=: FillArc
+ PS_fillcircle :=: FillCircle
+ PS_fillrect :=: FillRectangle
+ PS_fillpoly :=: FillPolygon
+ PS_flush :=: WFlush
+ PS_font :=: Font
+ return
+end
+
+# PS_init(w, f) -- initialize recording system
+
+procedure PS_init(a[])
+ if /PS_active then PSStart ! a
+ return
+end
+
+procedure PSStart(a[])
+local fname, scale, psw, psh, llx, lly
+
+ if \PS_active then PSDone()
+ PS_afix(a)
+ PS_win := \a[1] | \&window | runerr(140, a[1])
+ fname := \a[2] | "xlog.ps"
+ PS_f := open(fname, "w") | stop("can't open", fname)
+
+ # calculate output scaling
+ # max (&default) scaling is 1.0 (72 pixels per inch)
+ # max size image allowed comes within 0.5" of all four borders
+ PS_width := WAttrib(PS_win, "width")
+ PS_height := WAttrib(PS_win, "height")
+ scale := 1.0
+ scale >:= 72 * (8.5 - 0.5 - 0.5) / PS_width
+ scale >:= 72 * (11.0 - 0.5 - 0.5) / PS_height
+
+ # position window in center of page
+ psw := integer(scale * PS_width + 0.9999) # width in ps coords
+ psh := integer(scale * PS_height + 0.9999) # height
+ llx := integer((72 * 8.5 - psw) / 2) # center horizontally
+ lly := integer((72 * 11.0 - psh) / 2) # center vertically
+ if lly + psh < 72 * 9.5 then
+ lly := integer(72 * 9.5 - psh) # but not over 1.5" from top
+
+ # write EPS header
+ PS_out("%!PS-Adobe-3.0 EPSF-3.0")
+ PS_out("%%BoundingBox:", llx, lly, llx + psw + 1, lly + psh + 1)
+ PS_out("%%Creator:", &progname)
+ PS_out("%%CreationDate:", &dateline)
+ PS_out("%%EndComments")
+ PS_out()
+
+ every PS_out(![ # output PostScript file header
+
+ # define variables now so that bound procs get correct versions
+ "/BGR 0 def /BGG 0 def /BGB 0 def",
+
+ # shorthand procedures
+ "/bd {bind def} bind def",
+ "/m {moveto} bd",
+ "/l {lineto} bd",
+ "/s {stroke} bd",
+ "/f {fill} bd",
+
+ # construct a rectangular path; usage is: w h x y <r>
+"/r {moveto dup 0 exch rlineto exch 0 rlineto neg 0 exch rlineto closepath} bd",
+
+ # procedures for remembering state
+ "/fg {setrgbcolor} bd", # foreground
+ "/bg {/BGB exch def /BGG exch def /BGR exch def} bd", # background
+ "/ft {findfont exch dup neg matrix scale makefont setfont} bd", # font
+
+ # A new clip path may not be inside old path as needed by PS.
+ # Save the old context, pop back to full-screen graphics state,
+ # restore other context, and set clip path.
+ "/cp {currentfont currentrgbcolor grestore gsave setrgbcolor setfont",
+ " r clip newpath } bd",
+
+ # drawing procedures
+ "/t {moveto show newpath} bd", # text string
+ "/p {0.5 0 360 arc fill} bd", # point
+ "/g {moveto lineto stroke} bd", # line segment
+ "/pf {closepath fill} bd", # filled polygon
+ "/c {0 360 arc stroke} bd", # circle
+ "/cf {0 360 arc fill} bd", # filled circle
+ "/a {gsave translate 1.0 exch scale arc stroke grestore} bd", # arc
+ "/af {gsave translate 1.0 exch scale 0 0 moveto arc fill grestore} bd",
+ "/er {gsave r BGR BGG BGB setrgbcolor fill grestore} bd", # erase area
+ ])
+
+ # establish coordinate system
+ PS_out(llx, lly + psh, "translate")
+ PS_out(psw + 1, -(psh + 1), "0 0 r clip newpath")
+ PS_out(scale, -scale, "scale")
+ PS_out("0.5 0.5 translate")
+ PS_out("gsave") # save full-window gpx env
+
+ # swap our routines for those of Icon
+ if /PS_active := 1 then
+ PS_swap()
+
+ # note graphics values in PS file
+ Font(PS_win, Font(PS_win))
+ Fg(PS_win, Fg(PS_win))
+ Bg(PS_win, Bg(PS_win))
+
+ PS_out(PS_width, PS_height, "0 0 er") # fill background
+ write(PS_f)
+return
+end
+
+# PS_out(s, s, ...) -- output strings to PS file, with spaces between
+
+procedure PS_out(a[])
+ if /a[1] then
+ return
+ writes(PS_f, get(a))
+ while writes(PS_f, " ", get(a))
+ write(PS_f)
+ return
+end
+
+# PS_path(a, s) -- output path from a[2..*] followed by command s
+
+procedure PS_path(a, s)
+ local i
+
+ PS_out(a[2], a[3], "m")
+ every i := 4 to *a - 3 by 2 do
+ PS_out(a[i], a[i+1], "l")
+ PS_out(a[-2], a[-1], "l", s)
+ return
+end
+
+# PS_afix(a) -- fix arg list to ensure that first arg is a window
+
+procedure PS_afix(a)
+ if not (type(a[1]) == "window") then
+ push(a, &window)
+ return a
+end
+
+#################### Icon Function Substitutes ####################
+
+procedure PS_flush(a[]) # replaces WFlush
+ # we don't know why they're flushing, but we'll flush, too
+ flush(PS_f)
+ return PS_flush ! a
+end
+
+
+procedure PS_bg(a[]) # replaces Bg
+ PS_afix(a)
+ # note that following line fails if there is no a[2]
+ PS_out(PS_color(a[2]), "bg")
+ return PS_bg ! a
+end
+
+procedure PS_fg(a[]) # replaces Fg
+ PS_afix(a)
+ # note that following line fails if there is no a[2]
+ PS_out(PS_color(a[2]), "fg")
+ return PS_fg ! a
+end
+
+procedure PS_color(color) # parse color, return string of PS r, g, b
+ local r, g, b
+ (ColorValue(PS_win, color) | fail) ? {
+ r := tab(many(&digits)); move(1)
+ g := tab(many(&digits)); move(1)
+ b := tab(many(&digits))
+ }
+ return (r / 65535.0) || " " || (g / 65535.0) || " " || (b / 65535.0)
+end
+
+procedure PS_drawpoint(a[]) # replaces DrawPoint
+ local i
+
+ PS_afix(a)
+ every i := 2 to *a by 2 do
+ PS_out(a[i], a[i+1], "p")
+ return PS_drawpoint ! a
+end
+
+procedure PS_drawsegment(a[]) # replaces DrawSegment
+ local i
+
+ PS_afix(a)
+ every i := 2 to *a by 4 do
+ PS_out(a[i], a[i+1], a[i+2], a[i+3], "g")
+ return PS_drawsegment ! a
+end
+
+procedure PS_drawline(a[]) # replaces DrawLine
+ local i
+
+ PS_afix(a)
+ if *a == 5 then
+ PS_out(a[2], a[3], a[4], a[5], "g")
+ else
+ PS_path(a, "s")
+ return PS_drawline ! a
+end
+
+procedure PS_drawcurve(a[]) # replaces DrawCurve -- approx with line segs
+ local i
+
+ PS_afix(a)
+ PS_path(a, "s")
+ return PS_drawcurve ! a
+end
+
+procedure PS_drawrect(a[]) # replaces DrawRectangle
+ local i
+
+ PS_afix(a)
+ every i := 2 to *a by 4 do
+ PS_out(a[i+2], a[i+3], a[i], a[i+1], "r s")
+ return PS_drawrect ! a
+end
+
+procedure PS_fillrect(a[]) # replaces FillRectangle
+ local i
+
+ PS_afix(a)
+ every i := 2 to *a by 4 do
+ PS_out(a[i+2], a[i+3], a[i], a[i+1], "r f")
+ return PS_fillrect ! a
+end
+
+procedure PS_fillpoly(a[]) # replaces FillPolygon
+ local i
+
+ PS_afix(a)
+ PS_path(a, "pf")
+ return PS_fillpoly ! a
+end
+
+
+procedure PS_clip(a[]) # replaces Clip
+ PS_area(a, "cp")
+ return PS_clip ! a
+end
+
+procedure PS_erasearea(a[]) # replaces EraseArea
+ PS_area(a, "er")
+ return PS_erasearea ! a
+end
+
+procedure PS_area(a, cmd) # generate w, h, x, y, and cmd, with defaults
+ local x, y, w, h
+ PS_afix(a)
+
+ x := \a[2] | 0
+ y := \a[3] | 0
+ w := (0 ~= \a[4]) | PS_width
+ h := (0 ~= \a[5]) | PS_height
+ PS_out(w, h, x, y, cmd)
+end
+
+procedure PS_drawcircle(a[]) # replaces DrawCircle
+ PS_arc(a, 0, "")
+ return PS_drawcircle ! a
+end
+
+procedure PS_fillcircle(a[]) # replaces FillCircle
+ PS_arc(a, 0, "f")
+ return PS_fillcircle ! a
+end
+
+procedure PS_drawarc(a[]) # replaces DrawArc
+ PS_arc(a, 1, "")
+ return PS_drawarc ! a
+end
+
+procedure PS_fillarc(a[]) # replaces FillArc
+ PS_arc(a, 1, "f")
+ return PS_fillarc ! a
+end
+
+procedure PS_arc(a, n, f) # handle draw/fill arc/circle, append f to cmd
+ local x, y, w, h, ar, a1, a2, r, i
+ static mul
+ initial mul := 180 / &pi
+
+ PS_afix(a)
+ every i := 2 to *a by (5 + n) do {
+ x := a[i]
+ y := a[i+1]
+ w := a[i+2]
+ h := a[i+2+n]
+ a1 := (\a[i+n+3] * mul) | 0.0
+ a2 := (\a[i+n+4] * mul) | 360.0
+ if n = 1 then { # if DrawArc
+ r := w / 2.0 # radius
+ x +:= r # center coordinates
+ y +:= r
+ }
+ else
+ r := w
+ if w = h & abs(a2) > 359.99 then # if circle
+ PS_out(x, y, r, "c" || f)
+ else { # general case
+ if a2 < 0 then {
+ a1 := a1 + a2 # ensure counterclockwise arc (in PS coords)
+ a2 := -a2
+ }
+ if w = 0 then
+ ar := 0.0
+ else
+ ar := real(h) / real(w)
+ PS_out("0 0", r, a1, a1 + a2, ar, x, y, "a" || f)
+ }
+ }
+ return
+end
+
+procedure PS_font(a[]) # replaces Font (very crudely)
+ local ret, xname, psname, n
+
+ PS_afix(a)
+ if not (ret := PS_font ! a) then
+ fail
+ if xname := \a[2] then {
+ map(xname) ? {
+ if tab(many(&digits)) & ="x" & tab(many(&digits)) & pos(0) then
+ psname := "/Courier"
+ else if find("fixed" | "courier" | "typewriter") & find("bold") then
+ psname := "/Courier-Bold"
+ else if find("fixed" | "courier" | "typewriter") then
+ psname := "/Courier"
+ else if find("helvetica" | "sans") & find("bold") then
+ psname := "/Helvetica-Bold"
+ else if find("helvetica" | "sans") & find("oblique") then
+ psname := "/Helvetica-Oblique"
+ else if find("helvetica" | "sans") then
+ psname := "/Helvetica"
+ else if find("times") & find("bold")then
+ psname := "/Times-Bold"
+ else if find("times") & find("italic")then
+ psname := "/Times-Italic"
+ else if find("times") then
+ psname := "/Times-Roman"
+ else if find("bold") then
+ psname := "/Palatino-Bold"
+ else if find("italic") then
+ psname := "/Palatino-Italic"
+ else
+ psname := "/Palatino-Roman"
+ }
+ n := WAttrib(PS_win, "ascent") + 1 # could possibly be smarter
+ PS_out(n, psname, "ft %", xname)
+ }
+ return ret
+end
+
+procedure PS_drawstring(a[]) # replaces DrawString
+ PS_afix(a)
+ PS_psstring(a[4])
+ PS_out("", a[2], a[3], "t")
+ return PS_drawstring ! a
+end
+
+procedure PS_psstring(s) # output a string as a PS string
+ s ? {
+ writes(PS_f, "(")
+ while writes(PS_f, tab(upto('()\\'))) do
+ writes(PS_f, "\\", move(1))
+ writes(PS_f, tab(0), ")")
+ }
+ return
+end
+
+# PS_attrib() -- handle WAttrib calls
+#
+# Any attribute that is accepted here should also be checked and set to
+# the correct value during initialization in order to catch attributes
+# that were set on the open() call.
+
+procedure PS_attrib(alist[]) # replaces WAttrib
+ local win, ret, name, val, a
+
+ PS_afix(alist)
+ ret := alist
+ ret := PS_attrib ! alist
+ win := pop(alist) # remove window arg
+ every a := !alist do a ? { # process each attribute
+ name := tab(upto('=')) | next
+ move(1)
+ val := tab(0)
+ case name of {
+ "fg": Fg(win, val)
+ "bg": Bg(win, val)
+ "font": Font(win, val)
+ }
+ }
+ return a ~=== ret # return value or fail if WAttrib failed
+end
diff --git a/ipl/gprocs/putpixel.icn b/ipl/gprocs/putpixel.icn
new file mode 100644
index 0000000..a31ee68
--- /dev/null
+++ b/ipl/gprocs/putpixel.icn
@@ -0,0 +1,163 @@
+############################################################################
+#
+# File: putpixel.icn
+#
+# Subject: Procedure to write quantized, processed pixel
+#
+# Author: Gregg M. Townsend
+#
+# Date: August 14, 1996
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# These procedures assist pixel-by-pixel image construction.
+#
+# PutPixel(W, x, y, k) draws a single pixel after applying
+# dithering, color quantization, and
+# gamma correction.
+#
+# PixInit(gamma, cquant, gquant, drandom)
+# initializes parameters for PutPixel().
+#
+############################################################################
+#
+# PutPixel([win,] x, y, colr) sets the pixel at (x,y) to the given color
+# after applying dithering, color quantization, and gamma correction.
+# It is designed for constructing images a pixel at a time. The window's
+# foreground color is left set to the adjusted color.
+#
+# Colr can be any value acceptable to Fg. Mutable colors are not
+# dithered, quantized, or gamma-corrected.
+#
+# PixInit(gamma, cquant, gquant, drandom) may be called before PutPixel
+# to establish non-default parameters. The default gamma value is 1.0
+# (that is, no correction beyond Icon's usual gamma correction).
+# cquant and gquant specify the number of color and grayscale quantization
+# steps; the defaults are 6 and 16 respectively. If gquant + cquant ^ 3
+# exceeds 256 there is a potential for running out of colors. drandom
+# is the fraction (0 to 1) of the dithering to be done randomly; the
+# default is zero.
+#
+############################################################################
+#
+# Requires: Version 9 graphics
+#
+############################################################################
+
+global XPP_qtab, XPP_gtab, XPP_dtab, XPP_rtab, XPP_gadjust
+
+# PixInit -- set parameters and build tables
+
+procedure PixInit(gamma, cquant, gquant, drandom) #: initialize pixel processing
+ local PIXRANGE, NRANDOM, cstep, gstep, indx, appx, gcor, i
+
+ /gamma := 1.0 # gamma correction factor
+ /cquant := 6 # color quantization steps
+ /gquant := 16 # grayscale quantization
+ /drandom := 0.0 # fraction of dithering to do randomly
+
+ NRANDOM := 500 # size of random number table
+ PIXRANGE := 255 # pixel value range 0..255
+
+ if gamma < 0.01 then # ensure legal values
+ gamma := 2.5
+ cquant <:= 2
+ gquant <:= 2
+ drandom <:= 0.0
+ drandom >:= 1.0
+
+ cstep := (PIXRANGE / (cquant-1.0)) # color step size
+ gstep := (PIXRANGE / (gquant-1.0)) # grayscale step size
+
+ # build 4 x 4 dither table (choose one)
+ # XPP_dtab := [0,8,2,10,12,4,14,6,3,11,1,9,15,7,13,5] # ordered dither
+ XPP_dtab := [0,6,9,15,11,13,2,4,7,1,14,8,12,10,5,3] # magic square dither
+ every i := 1 to 16 do # normalize
+ XPP_dtab[i] := (XPP_dtab[i]/15.0 - 0.5) * (cstep - 3) * (1.0 - drandom)
+
+ # build list of scaled random numbers for dithering
+ XPP_rtab := list(NRANDOM)
+ every !XPP_rtab := (?0 - 0.5) * 2 * (cstep - 3) * drandom
+
+ # build table for combined quantization and gamma correction
+ XPP_qtab := list(PIXRANGE+1)
+ every i := 0 to PIXRANGE do {
+ indx := integer((i + cstep / 2) / cstep)
+ appx := cstep * indx
+ gcor := PIXRANGE * ((real(appx) / real(PIXRANGE)) ^ (1.0 / gamma))
+ XPP_qtab[i+1] := integer(gcor + 0.5)
+ }
+ # build similar table for grayscale
+ XPP_gtab := list(PIXRANGE+1)
+ every i := 0 to PIXRANGE do {
+ indx := integer((i + gstep / 2) / gstep)
+ appx := gstep * indx
+ gcor := PIXRANGE * ((real(appx) / real(PIXRANGE)) ^ (1.0 / gamma))
+ XPP_gtab[i+1] := integer(gcor + 0.5)
+ }
+ # grayscale adjustment for different quantization
+ XPP_gadjust := (gstep - 3) / (cstep - 3)
+ return
+end
+
+# PutPixel -- write a pixel
+
+procedure PutPixel(win, x, y, color) #: write pixel
+ local i, r, g, b
+
+ initial if /XPP_qtab then PixInit()
+
+ # default win to &window if omitted
+ if type(win) ~== "window" then {
+ win :=: x :=: y :=: color
+ win := &window
+ }
+
+ # convert color to 8-bit r, g, b
+ if type(color) == "integer" then {
+ # mutable -- don't quantize
+ Fg(win, color)
+ DrawPoint(win, x, y)
+ return
+ }
+
+ (color | ColorValue(color) | fail) ? (
+ (r := tab(many(&digits))) & move(1) &
+ (g := tab(many(&digits))) & move(1) &
+ (b := tab(many(&digits)))
+ )
+
+ # convert three 0..65535 ints to 0..255
+ r := (r + 255) / 257
+ g := (g + 255) / 257
+ b := (b + 255) / 257
+
+ # get dither table index based on coordinates
+ i := iand(x, 3) + 4 * iand(y, 3) + 1
+
+ if r = g = b then {
+ g := integer(g + XPP_gadjust * (XPP_dtab[i] + ?XPP_rtab))
+ (g <:= 1) | (g >:= 256)
+ r := g := b := 257 * XPP_gtab[g]
+ }
+ else {
+ r := integer(r + XPP_dtab[i] + ?XPP_rtab + 1.5)
+ g := integer(g - XPP_dtab[i] + ?XPP_rtab + 1.5)
+ b := integer(b + XPP_dtab[i] + ?XPP_rtab + 1.5)
+ (r <:= 1) | (r >:= 256)
+ (g <:= 1) | (g >:= 256)
+ (b <:= 1) | (b >:= 256)
+ r := 257 * XPP_qtab[r]
+ g := 257 * XPP_qtab[g]
+ b := 257 * XPP_qtab[b]
+ }
+
+ # finally, put the pixel on the screen
+ Fg(win, r || "," || g || "," || b)
+ DrawPoint(win, x, y)
+ return
+end
diff --git a/ipl/gprocs/randarea.icn b/ipl/gprocs/randarea.icn
new file mode 100644
index 0000000..130a0a4
--- /dev/null
+++ b/ipl/gprocs/randarea.icn
@@ -0,0 +1,65 @@
+############################################################################
+#
+# File: randarea.icn
+#
+# Subject: Procedures to generate random points in areas
+#
+# Author: Ralph E. Griswold
+#
+# Date: May 2, 2001
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# These procedures generate randomly selected points with specified
+# areas.
+#
+############################################################################
+#
+# Links: gobject
+#
+############################################################################
+
+link gobject
+
+procedure randrect(x, y, w, h)
+
+ w := integer(w) | stop("*** bad value")
+ h := integer(h) | stop("*** bad value")
+
+ x -:= 1
+ y -:= 1
+
+ suspend Point(x + ?|w, y + ?h)
+
+end
+
+procedure randellip(x, y, w, h)
+ local r1, r2, xc, yc, xp, yp, xq, yq, theta, rp, r
+
+ w := integer(w) | stop("*** bad value")
+ h := integer(h) | stop("*** bad value")
+
+ r1 := w / 2
+ r2 := h / 2
+ xc := x + r1
+ yc := y + r2
+
+ x -:= 1
+ y -:= 1
+
+ repeat {
+ xq := x + ?w
+ yq := y + ?h
+ xp := xq - xc
+ yp := yq - yc
+ theta := -atan(yp, xp)
+ rp := sqrt(xp ^ 2 + yp ^ 2)
+ r := sqrt((r1 * cos(theta)) ^ 2 + (r2 * sin(theta)) ^ 2)
+ if r > rp then suspend Point(xq, yq)
+ }
+
+end
diff --git a/ipl/gprocs/randfigs.icn b/ipl/gprocs/randfigs.icn
new file mode 100644
index 0000000..4097f07
--- /dev/null
+++ b/ipl/gprocs/randfigs.icn
@@ -0,0 +1,48 @@
+############################################################################
+#
+# File: randfigs.icn
+#
+# Subject: Procedures to generate random figures
+#
+# Author: Ralph E. Griswold
+#
+# Date: March 27, 1993
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# These procedures generate random geometrical figures.
+#
+############################################################################
+#
+# Links: gobject
+#
+############################################################################
+
+link gobject
+
+#
+# random_points(width, height) generates an infinite sequence of
+# randomly chosen points within the area bounded by 0, 0 and width - 1,
+# height - 1.
+
+procedure random_points(width, height)
+
+ suspend |Point(?width - 1, ?height - 1)
+
+end
+
+#
+# random_lines(width, height) generates an infinite sequence of
+# randomly chosen lines within the area bounded by 0, 0 and width - 1,
+# height - 1.
+
+procedure random_lines(width, height)
+
+ suspend |Line(Point(?width - 1, ?height - 1),
+ Point(?width - 1, ?height - 1))
+
+end
diff --git a/ipl/gprocs/rawimage.icn b/ipl/gprocs/rawimage.icn
new file mode 100644
index 0000000..8385c5b
--- /dev/null
+++ b/ipl/gprocs/rawimage.icn
@@ -0,0 +1,143 @@
+############################################################################
+#
+# File: rawimage.icn
+#
+# Subject: Procedures to write and read images in raw format
+#
+# Author: Ralph E. Griswold
+#
+# Date: May 2, 2001
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# These procedures write and read raw image files. The format of a raw
+# image file is:
+#
+# width,height
+# <palette entries with 2 hex digits, a blank, and a color specification>
+# <blank line>
+# <image data consisting of pairs of hext digits in row-primary order>
+#
+# These procedures are slow and should only be used when the image file
+# formats that Icon can read and write are not sufficient.
+#
+############################################################################
+#
+# Links: wopen
+#
+############################################################################
+#
+# Requires: Version 9 graphics and co-expressions
+#
+############################################################################
+
+link wopen
+
+$define LineLen 64
+
+procedure WriteRaw(win, x, y, w, h)
+ local nextid, palette, line, c, temp, tempname
+
+ if type(win) ~== "window" then {
+ win :=: x :=: y :=: w :=: h
+ win := &window
+ }
+
+ /w := WAttrib(win, "width")
+ /h := WAttrib(win, "height")
+ /x := 0
+ /y := 0
+
+ tempname := "/tmp/reg." || map("mmhhss", "mm:hh:ss", &clock)
+ temp := open(tempname, "w") | stop("*** cannot open temporary file")
+
+
+ line := ""
+
+ palette := table()
+
+ nextid := create !"0123456789abcdef" || !"0123456789abcdef"
+
+ every c := Pixel(win, x, y, w, h) do {
+ /palette[c] := @nextid
+ line ||:= palette[c]
+ line ?:= {
+ write(temp, move(LineLen)) & tab(0)
+ }
+ }
+
+ write(temp, "" ~== line)
+
+ write(w, ",", h)
+
+ palette := sort(palette, 4)
+
+ while c := get(palette) do
+ write(get(palette), " ", c)
+
+ write() # separator
+
+ close(temp)
+ temp := open(tempname) | stop("*** cannot find temporary file")
+
+ while writes(reads(temp, 10000)) # copy image data
+
+ close(temp)
+ remove(tempname)
+
+ return
+
+end
+
+procedure ReadRaw(win, s, x, y)
+ local input, palette, c, temp, size, width, height, line
+
+ if type(win) ~== "window" then {
+ win :=: s :=: x :=: y
+ win := &window
+ }
+
+ input := open(s) | stop("*** cannot read raw image file")
+
+ temp := WOpen("size=" || (size := read(input)), "canvas=hidden") |
+ stop("*** malformed raw image file")
+
+ size ? {
+ width := integer(tab(upto(','))) &
+ move(1) &
+ height := integer(tab(0)) | stop("invalid raw image header")
+ }
+
+ palette := table()
+
+ while line := read(input) do
+ line ? {
+ palette[move(2) | break] := (move(1), tab(0))
+ }
+
+ x := y := 0
+
+ repeat {
+ line := read(input) | break
+ line ? {
+ while c := move(2) do {
+ Fg(temp, palette[c]) | stop("***invalid color: ", c)
+ DrawPoint(temp, x, y)
+ x +:= 1
+ if x = width then {
+ x := 0
+ y +:= 1
+ }
+ }
+ }
+ }
+
+ CopyArea(temp, win, 0, 0, width, height, x, y)
+
+ return
+
+end
diff --git a/ipl/gprocs/repeats.icn b/ipl/gprocs/repeats.icn
new file mode 100644
index 0000000..524ea61
--- /dev/null
+++ b/ipl/gprocs/repeats.icn
@@ -0,0 +1,53 @@
+############################################################################
+#
+# File: repeats.icn
+#
+# Subject: Procedure to repeat image
+#
+# Author: Ralph E. Griswold
+#
+# Date: August 23, 1996
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# This procedure produces repeats of an image specified number of times.
+#
+############################################################################
+#
+# Requires: Version 9 graphics
+#
+############################################################################
+#
+# Links: tile, wopen
+#
+############################################################################
+
+link tile
+link wopen
+
+procedure repeats(name, i, j) #: repeat image
+ local opts, prefix, win1, win2, width, height
+ local auto, wdim, hdim, limit
+
+ /i := 1 # horizontal repeats
+ /j := 1 # vertical repeats
+
+ win1 := WOpen("canvas=hidden", "image=" || name) | fail
+ width := WAttrib(win1, "width")
+ height := WAttrib(win1, "height")
+ hdim := height * i
+ wdim := width * j
+
+ win2 := WOpen("canvas=hidden", "width=" || wdim, "height=" || hdim) |
+ stop(&errout, "*** cannot open window for repeat")
+
+ tile(win1, win2)
+
+ WClose(win1)
+
+ return win2
+end
diff --git a/ipl/gprocs/rgbcomp.icn b/ipl/gprocs/rgbcomp.icn
new file mode 100644
index 0000000..544e108
--- /dev/null
+++ b/ipl/gprocs/rgbcomp.icn
@@ -0,0 +1,98 @@
+############################################################################
+#
+# File: rgbcomp.icn
+#
+# Subject: Procedures to perform computations on RGB values
+#
+# Author: Ralph E. Griswold
+#
+# Date: January 14, 1995
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# rgbsum(s1, s2) returns a color whose RGB components are the sums of the
+# components for s1 and s2.
+#
+# rgbdif(s1, s2) returns a color whose RGB components are the differences of
+# the components for s1 and s2.
+#
+# rgbavg(s1, s2) returns a color whose RGB components are the averages of
+# the components for s1 and s2.
+#
+# rsgcomp(s) returns the color that is the complement of s.
+#
+# The results may not be what's expected in some cases.
+#
+############################################################################
+#
+# Requires: Version 9 graphics
+#
+############################################################################
+#
+# Links: numbers, rgbrec
+#
+############################################################################
+
+link numbers
+link rgbrec
+
+$define MaxIntensity (2 ^ 16 - 1)
+
+procedure rgbsum(s1, s2)
+ local rgb1, rgb2
+
+ rgb1 := rgbrec(s1) | fail
+ rgb2 := rgbrec(s2) | fail
+
+ return rgbrec(
+ max(rgb1.r + rgb2.r, MaxIntensity),
+ max(rgb1.g + rgb2.g, MaxIntensity),
+ max(rgb1.b + rgb2.b, MaxIntensity)
+ )
+
+end
+
+procedure rgbdif(s1, s2)
+ local rgb1, rgb2
+
+ rgb1 := rgbrec(s1) | fail
+ rgb2 := rgbrec(s2) | fail
+
+ return rgbrec(
+ min(rgb1.r - rgb2.r, 0),
+ min(rgb1.g - rgb2.g, 0),
+ min(rgb1.b - rgb2.b)
+ )
+
+end
+
+procedure rgbavg(s1, s2)
+ local rgb1, rgb2
+
+ rgb1 := rgbrec(s1) | fail
+ rgb2 := rgbrec(s2) | fail
+
+ return rgbrec(
+ (rgb1.r + rgb2.r) / 2,
+ (rgb1.g + rgb2.g) / 2,
+ (rgb1.b + rgb2.b) / 2
+ )
+
+end
+
+procedure rgbcomp(s)
+ local rgb
+
+ rgb := rgbrec(s) | fail
+
+ return rgbrec(
+ MaxIntensity - rgb.r,
+ MaxIntensity - rgb.g,
+ MaxIntensity - rgb.b
+ )
+
+end
diff --git a/ipl/gprocs/rgbrec.icn b/ipl/gprocs/rgbrec.icn
new file mode 100644
index 0000000..48092b1
--- /dev/null
+++ b/ipl/gprocs/rgbrec.icn
@@ -0,0 +1,48 @@
+############################################################################
+#
+# File: rgbrec.icn
+#
+# Subject: Procedure to produce RGB record from color specification
+#
+# Author: Ralph E. Griswold
+#
+# Date: May 2, 2001
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# This procedure produces a three-field RGB record from an Icon color
+# specification. It fails id its argument is not a valid color specifi-
+# cation.
+#
+############################################################################
+#
+# Requires: Version 9 graphics
+#
+############################################################################
+
+record rgb(r, g, b)
+
+procedure rgbrec(s)
+ local result
+
+ s := ColorValue(s) | fail
+
+ result := rgb()
+
+ s ? {
+ result.r := tab(upto(','))
+ move(1)
+ result.g := tab(upto(','))
+ move(1)
+ result.b := tab(0)
+ }
+
+ return result
+
+end
+
+
diff --git a/ipl/gprocs/rpolys.icn b/ipl/gprocs/rpolys.icn
new file mode 100644
index 0000000..4af3195
--- /dev/null
+++ b/ipl/gprocs/rpolys.icn
@@ -0,0 +1,40 @@
+############################################################################
+#
+# File: rpolys.icn
+#
+# Subject: Procedure to produce traces of regular polygons
+#
+# Author: Ralph E. Griswold
+#
+# Date: March 24, 1999
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# Generate points for a regular polygon with the specified number of
+# vertices and radius, centered at cx and cy. The offset angle is theta;
+# default 0.
+#
+############################################################################
+#
+# Links: gobject
+#
+############################################################################
+
+link gobject
+
+procedure rpoly(cx, cy, radius, vertices, theta) #: generate polygon points
+ local incr, i
+
+ incr := 2 * &pi / vertices
+ /theta := 0 # starting angle
+
+ every i := 1 to vertices do {
+ suspend Point(cx + radius * cos(theta), cy + radius * sin(theta))
+ theta +:= incr
+ }
+
+end
diff --git a/ipl/gprocs/rstars.icn b/ipl/gprocs/rstars.icn
new file mode 100644
index 0000000..3372f2a
--- /dev/null
+++ b/ipl/gprocs/rstars.icn
@@ -0,0 +1,58 @@
+############################################################################
+#
+# File: rstars.icn
+#
+# Subject: Procedure to generate traces of regular stars
+#
+# Author: Ralph E. Griswold
+#
+# Date: March 27, 1993
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# This procedure generates traces of regular stars.
+#
+############################################################################
+#
+# Links: gobject
+#
+############################################################################
+
+link gobject
+
+global size
+
+#
+# Generate points on regular star with n vertices, jumping j vertices,
+# centered at x and y, with scaled radius, with an initial offset angle,
+# and with a specified frame size.
+
+procedure rstar(x, y, n, j, scale, offset, size) #: regular star
+ local i, jangle, angle
+
+ /x := 100 # defaults
+ /y := 100
+ /n := 5
+ /j := 3
+ /scale := 0.45
+ /offset := 0.5
+ /size := 200
+
+ jangle := j * 2 * &pi / n
+
+ scale *:= size
+ offset *:= &pi
+
+ every i := 0 to n do {
+ angle := jangle * i + offset
+ suspend Point(
+ x + scale * cos(angle),
+ y + scale * sin(angle)
+ )
+ }
+
+end
diff --git a/ipl/gprocs/rstartbl.icn b/ipl/gprocs/rstartbl.icn
new file mode 100644
index 0000000..0e7ec66
--- /dev/null
+++ b/ipl/gprocs/rstartbl.icn
@@ -0,0 +1,46 @@
+############################################################################
+#
+# File: rstartbl.icn
+#
+# Subject: Procedure to produce calls for regular stars
+#
+# Author: Ralph E. Griswold
+#
+# Date: April 8, 1993
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# This procedure produces a table of calls from which regular stars
+# can be produced.
+#
+############################################################################
+#
+# See also: rstars.icn
+#
+############################################################################
+#
+# Links: calls, rstars
+#
+############################################################################
+
+link calls
+link rstars
+
+procedure rstartbl()
+ local rstars
+
+ rstars := table()
+ rstars["rstar01"] := call(rstar, [300, 300, 5, 3, 0.45])
+ rstars["rstar02"] := call(rstar, [300, 300, 7, 3, 0.45])
+ rstars["rstar03"] := call(rstar, [300, 300, 20, 9, 0.45])
+ rstars["rstar04"] := call(rstar, [300, 300, 20, 7, 0.45])
+ rstars["rstar05"] := call(rstar, [300, 300, 51, 20, 0.45])
+ rstars["rstar06"] := call(rstar, [300, 300, 51, 25, 0.45])
+
+ return rstars
+
+end
diff --git a/ipl/gprocs/select.icn b/ipl/gprocs/select.icn
new file mode 100644
index 0000000..9557c00
--- /dev/null
+++ b/ipl/gprocs/select.icn
@@ -0,0 +1,99 @@
+############################################################################
+#
+# File: select.icn
+#
+# Subject: Procedure to get selection from window
+#
+# Author: Ralph E. Griswold
+#
+# Date: August 30, 1996
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+#
+#
+############################################################################
+#
+# Requires: Version 9 graphics
+#
+############################################################################
+#
+# Links: grecords
+#
+############################################################################
+
+link grecords
+
+procedure select(win) #: interactive selection from window
+ local x0, x1, y0, y1, w, h, state, event
+
+ /win := &window
+
+ WAttrib(win, "drawop=reverse")
+ WAttrib(win, "linestyle=onoff")
+
+ state := "wait"
+
+ while event := Event(win) do {
+ if event == "q" then {
+ DrawRectangle(win, \x0, y0, 0, 0) # clear if already drawn
+ fail
+ }
+ case state of {
+ "wait": { # waiting for selection
+ case event of {
+ &lpress: {
+ x1 := x0 := &x # initial coordinates
+ y1 := y0 := &y
+ DrawRectangle(win, x0, y0, 0, 0) # start selection
+ state := "select" # now select the rectangle
+ }
+ }
+ }
+ "select": { # select the rectangle
+ case event of {
+ &ldrag: { # selecting ...
+ DrawRectangle(win, x0, y0, x1 - x0, y1 - y0) # erase
+ x1 := &x # new opposite corner
+ y1 := &y
+ DrawRectangle(win, x0, y0, x1 - x0, y1 - y0) # draw
+ }
+ &lrelease: { # got it!
+ DrawRectangle(win, x0, y0, x1 - x0, y1 - y0) # erase
+ x1 := &x # new opposite corner
+ y1 := &y
+ if (x0 = x1) | (y0 = y1) then # no area
+ state := "wait"
+ else {
+ w := x1 - x0 # set up for action
+ h := y1 - y0
+ DrawRectangle(win, x0, y0, w, h) # draw rectangle
+ state := "act" # now do something
+ }
+ }
+ }
+ }
+ "act": {
+ case event of {
+ "n": { # new selection
+ state := "wait"
+ DrawRectangle(win, x0, y0, w, h) # try again
+ }
+ "q": { # quit
+ DrawRectangle(win, x0, y0, w, h)
+ fail
+ }
+ "r": { # return selection
+ DrawRectangle(win, x0, y0, w, h) #
+ return rect(x0, y0, w, h)
+ }
+ }
+ }
+ }
+ }
+
+end
diff --git a/ipl/gprocs/slider.icn b/ipl/gprocs/slider.icn
new file mode 100644
index 0000000..39c0dba
--- /dev/null
+++ b/ipl/gprocs/slider.icn
@@ -0,0 +1,210 @@
+############################################################################
+#
+# File: slider.icn
+#
+# Subject: Procedures for slider sensors
+#
+# Author: Gregg M. Townsend
+#
+# Date: August 14, 1996
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# These procedures implement slider using the "evmux" event
+# multiplexor instead of the usual vidget library.
+#
+# slider(win, proc, arg, x, y, w, h, lb, iv, ub) creates a slider.
+#
+# slidervalue(h, v) modifies a slider's value.
+#
+############################################################################
+#
+# slider(win, proc, arg, x, y, w, h, lb, iv, ub)
+#
+# establishes a slider and returns a handle for use with slidervalue().
+#
+# x,y,w,h give the dimensions of the slider. The slider runs vertically
+# or horizontally depending on which of w and h is larger. 20 makes a
+# nice width (or height).
+#
+# lb and ub give the range of real values represented by the slider;
+# lb is the left or bottom end. iv is the initial value.
+# proc(win, arg, value) is called as the slider is dragged to different
+# positions.
+#
+# slidervalue(h, v)
+#
+# changes the position of the slider h to reflect value v.
+# The underlying action procedure is not called.
+#
+############################################################################
+#
+# Example: A simple color picker
+#
+# record color(red, green, blue)
+# global win, spot
+#
+# ...
+# Fg(win, spot := NewColor(win))
+# Color(win, spot, "gray50")
+# FillArc(win, 10, 10, 100, 100)
+# Fg(win, "black")
+# h1 := slider(win, setcolor, 1, 110, 10, 20, 100, 0, 32767, 65535)
+# h2 := slider(win, setcolor, 2, 140, 10, 20, 100, 0, 32767, 65535)
+# h3 := slider(win, setcolor, 3, 170, 10, 20, 100, 0, 32767, 65535)
+# ...
+#
+# procedure setcolor(win, n, v)
+# static fg
+# initial fg := color(32767, 32767, 32767)
+# fg[n] := v
+# Color(win, spot, fg.red || "," || fg.green || "," || fg.blue)
+# end
+#
+# Draw a filled circle in a mutable color that is initially gray.
+# Draw three parallel, vertical sliders of size 20 x 100. Their values
+# run from 0 to 65535 and they are each initialized at the midpoint.
+# (The values are only used internally; the sliders are unlabeled.)
+#
+# When one of the sliders is moved, call setcolor(win, n, v).
+# n, from the "arg" value when it was built, identifies the slider.
+# v is the new value of the slider. Setcolor uses the resulting
+# color triple to set the color of the mutable color "spot".
+#
+# Additional calls
+# every slidervalue(h1 | h2 | h3, 32767)
+# every setcolor(win, 1 to 3, 32767)
+# would reset the original gray color. Note that explicit calls to
+# setcolor are needed because slidervalue does not call it.
+#
+############################################################################
+#
+# Links: evmux, graphics
+#
+############################################################################
+#
+# Requires: Version 9 graphics
+#
+############################################################################
+#
+# See also: evmux.icn
+#
+############################################################################
+
+link evmux
+link graphics
+
+$define MARGIN 10
+
+record Slider_Rec(win, proc, arg, x, y, w, h, lb, ub, n)
+
+procedure slider(win, proc, arg, x, y, w, h, lb, iv, ub)
+ local r
+
+ r := Slider_Rec(win, proc, arg, x, y, w, h, lb, ub)
+ slidervalue(r, iv)
+ if h > w then # vertical slider
+ sensor(win, &lpress, Exec_Vert_Slider, r, x, y - MARGIN, w, h + 2*MARGIN)
+ else # horizontal slider
+ sensor(win, &lpress, Exec_Horiz_Slider, r, x - MARGIN, y, w + 2*MARGIN, h)
+ return r
+end
+
+procedure slidervalue(r, v)
+ local n
+
+ Erase_Slider_Bar(r) # erase old handle
+ if r.lb ~= r.ub then
+ v := real(v - r.lb) / (r.ub - r.lb)
+ else
+ v := 0.0
+ v <:= 0.0
+ v >:= 1.0
+ if r.h > r.w then # if vertical
+ n := r.y + integer((1.0 - v) * (r.h - 1) + 0.5)
+ else
+ n := r.x + integer(v * (r.w - 1) + 0.5)
+ Set_Slider_Posn(r, n) # redraw track and handle
+ return
+end
+
+procedure Set_Slider_Posn(r, n)
+ local c
+
+ r.n := n
+ if r.h > r.w then {
+ c := r.x + r.w / 2
+ BevelRectangle(r.win, c - 2, r.y, 4, r.h, -2) # vertical track
+ BevelRectangle(r.win, r.x, r.n - 3, r.w, 6) # horizontal bar
+ FillRectangle(r.win, r.x + 2, r.n - 1, r.w - 4, 2)
+ }
+ else {
+ c := r.y + r.h / 2
+ BevelRectangle(r.win, r.x, c - 2, r.w, 4, -2) # horizontal track
+ BevelRectangle(r.win, r.n - 3, r.y, 6, r.h) # vertical bar
+ FillRectangle(r.win, r.n - 1, r.y + 2, 2, r.h - 4)
+ }
+ return
+end
+
+procedure Erase_Slider_Bar(r)
+ if r.h > r.w then
+ EraseArea(r.win, r.x, \r.n - 3, r.w, 6) # horizontal bar on vert. track
+ else
+ EraseArea(r.win, \r.n - 3, r.y, 6, r.h) # vertical bar on horiz. track
+ return
+end
+
+procedure Exec_Vert_Slider(win, r, x, y)
+ local e, h, u, args, a, v
+
+ e := &lpress
+ repeat {
+ if type(e) == "integer" then { # if a mouse event
+ y <:= r.y
+ y >:= r.y + r.h - 1
+ if y ~= r.n then {
+ Erase_Slider_Bar(r)
+ Set_Slider_Posn(r, y)
+ flush(r.win)
+ v := real(r.y + r.h - y - 1) / real(r.h - 1) # 0.0 to 1.0
+ v := v * (r.ub - r.lb) + r.lb # user range
+ r.proc(win, r.arg, v)
+ }
+ if e = &lrelease then
+ return
+ }
+ e := Event(win)
+ y := &y
+ }
+ return
+end
+
+procedure Exec_Horiz_Slider(win, r, x, y)
+ local e, h, u, args, a, v
+
+ e := &lpress
+ repeat {
+ if type(e) == "integer" then { # if a mouse event
+ x <:= r.x
+ x >:= r.x + r.w - 1
+ if x ~= r.n then {
+ Erase_Slider_Bar(r)
+ Set_Slider_Posn(r, x)
+ flush(r.win)
+ v := real(x - r.x) / real(r.w - 1) # 0.0 to 1.0
+ v := v * (r.ub - r.lb) + r.lb # user range
+ r.proc(win, r.arg, v)
+ }
+ if e = &lrelease then
+ return
+ }
+ e := Event(win)
+ x := &x
+ }
+ return
+end
diff --git a/ipl/gprocs/spirals.icn b/ipl/gprocs/spirals.icn
new file mode 100644
index 0000000..52bc7cc
--- /dev/null
+++ b/ipl/gprocs/spirals.icn
@@ -0,0 +1,48 @@
+############################################################################
+#
+# File: spirals.icn
+#
+# Subject: Procedure to produce traces of fractal stars
+#
+# Author: Ralph E. Griswold
+#
+# Date: May 2, 2001
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# Draw spiral with n segments and t rotations, starting at (x,y).
+# The extent determines the size of the drawing.
+#
+# The eccentricity is e (1 gives circle) and the reduction factor is r.
+# The angular increment is incr and the y scaling factor is yfact.
+#
+############################################################################
+#
+# Links: gobject, numbers
+#
+############################################################################
+
+link gobject
+link numbers
+
+procedure spiral(x, y, extent, n, t, e, r, incr, yfact)
+ local i, c, s, angle, redrad, x1, y1
+
+ incr := dtor(incr)
+
+ every i := 0 to n do {
+ redrad := r ^ div(i, n)
+ angle := (incr * i) / n
+ x1 := redrad * cos(t * angle)
+ y1 := redrad * e * sin(t * angle)
+ c := cos(angle)
+ s := sin(angle)
+ suspend Point(x + extent / 2 * (1 + x1 * c - y1 * s),
+ y + extent / 2 * yfact * (1 + x1 * s + y1 * c))
+ }
+
+end
diff --git a/ipl/gprocs/spokes.icn b/ipl/gprocs/spokes.icn
new file mode 100644
index 0000000..853de2d
--- /dev/null
+++ b/ipl/gprocs/spokes.icn
@@ -0,0 +1,54 @@
+############################################################################
+#
+# File: spokes.icn
+#
+# Subject: Procedure to draw spokes
+#
+# Author: Ralph E. Griswold
+#
+# Date: May 2, 2001
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# spokes(x, y, radius1, radius2, n, m) draws spokes.
+#
+############################################################################
+#
+# Links: gobject
+#
+############################################################################
+
+link gobject
+
+procedure spokes(x, y, radius1, radius2, n, m)
+ local angle1, incr1, angle2, incr2
+
+ angle1 := 0.0
+ incr1 := 2 * &pi / n
+
+ every 1 to n do {
+ suspend rays(x + radius1 * cos(angle1), y + radius1 * sin(angle1),
+ radius2, m, angle1)
+ angle1 +:= incr1
+ }
+
+end
+
+procedure rays(xc, yc, r, m, angle)
+ local incr
+
+ incr := 2 * &pi / m
+
+ every 1 to m do {
+ suspend Point(xc, yc)
+ suspend Point(xc + r * cos(angle), yc + r * sin(angle))
+ suspend Point(xc, yc)
+ angle +:= incr
+ }
+
+end
+
diff --git a/ipl/gprocs/strpchrt.icn b/ipl/gprocs/strpchrt.icn
new file mode 100644
index 0000000..4a152d8
--- /dev/null
+++ b/ipl/gprocs/strpchrt.icn
@@ -0,0 +1,126 @@
+############################################################################
+#
+# File: strpchrt.icn
+#
+# Subject: Procedure for dynamic stripchart for windows
+#
+# Author: Gregg M. Townsend
+#
+# Date: August 14, 1996
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# A stripchart models a continuous roll of paper that is marked along
+# the right edge while it moves continuously to the left. This is
+# also known as a chart recording.
+#
+# stripchart(window, x, y, width, height) creates a stripchart.
+#
+# sadvance(sc) advances a stripchart.
+#
+# smark(sc, y1, y2) marks a stripchart.
+#
+############################################################################
+#
+#
+# stripchart(window, x, y, width, height)
+#
+# establishes a stripchart and returns a record sc for use with
+# other procedures.
+#
+# The chart can be marked by calling smark() or by drawing directly
+# at location (sc.x, y) where y is arbitrary.
+#
+# sadvance(sc)
+#
+# advances the stripchart by one pixel.
+#
+# smark(sc, y1, y2)
+#
+# marks the current position of the stripchart from y1 to y2. y2 may
+# be omitted, in which case a single pixel at (sc.x, y1) is marked.
+#
+# If the chart has not been advanced since the last mark at y1,
+# nothing happens.
+#
+############################################################################
+#
+# Requires: Version 9 graphics
+#
+############################################################################
+
+record StripChart_Rec(win, x0, y, w, h, x, n, last)
+
+
+## stripchart(win, x, y, w, h) - create stripchart of size w by h at (x, y)
+
+procedure stripchart(win, x, y, w, h) #: create stripchart
+ if type(win) ~== "window" then
+ return stripchart((\&window | runerr(140)), win, x, y, w)
+
+ /x := -WAttrib(win, "dx")
+ /y := -WAttrib(win, "dy")
+ /w := WAttrib(win, "width") - (x + WAttrib(win, "dx"))
+ /h := WAttrib(win, "height") - (y + WAttrib(win, "dy"))
+
+ if w < 0 then
+ x -:= (w := -w)
+ if h < 0 then
+ y -:= (h := -h)
+
+ EraseArea(win, x, y, w, h)
+ return StripChart_Rec(win, x, y, w, h, x, 0, list(y + h, -1))
+end
+
+
+## sadvance(sc, n) - advance stripchart n pixels (default 1)
+
+procedure sadvance(sc, n) #: advance stripchart
+
+ /n := 1
+ every 1 to n do {
+ if sc.x < (sc.x0 + sc.w - 1) then
+ sc.x +:= 1
+ else
+ CopyArea(sc.win, sc.x0 + 1, sc.y, sc.w - 1, sc.h, sc.x0, sc.y)
+ EraseArea(sc.win, sc.x, sc.y, 1, sc.h)
+ sc.n +:= 1
+ }
+ return
+end
+
+
+## smark (sc, y1, y2) - mark stripchart from y1 to y2.
+
+procedure smark(sc, y1, y2) #: mark stripchart
+ y1 := integer(y1)
+ if sc.last[y1] <:= sc.n then
+ DrawLine(sc.win, sc.x, y1, sc.x, \y2) | DrawPoint(sc.win, sc.x, y1)
+ return
+end
+
+
+
+# ## test program.
+# #
+# # usage: stripchart [n]
+# #
+# link graphics
+# procedure main(args)
+# local win, sc, n, y, d
+# Window("size=500,200", args)
+# n := integer(args[1]) | 700
+# sc := stripchart()
+# y := 80
+# d := 40
+# every 1 to n do {
+# smark(sc, y +:= 2 * (?0 - ?0))
+# smark(sc, y + (d +:= 2 * (?0 - ?0)))
+# sadvance(sc)
+# }
+# WDone()
+# end
diff --git a/ipl/gprocs/subturtl.icn b/ipl/gprocs/subturtl.icn
new file mode 100644
index 0000000..6464eb1
--- /dev/null
+++ b/ipl/gprocs/subturtl.icn
@@ -0,0 +1,275 @@
+############################################################################
+#
+# File: subturtl.icn
+#
+# Subject: Procedures for turtle-graphics (subset version)
+#
+# Author: Gregg M. Townsend
+#
+# Date: January 30, 1996
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# These procedures implement a simplified subset of the turtle.icn
+# package. The main omissions are scaling, TWindow(), THome(), and
+# high-level primitives like TCircle(). Some procedures accept fewer
+# arguments, omit defaults, or omit the return value.
+#
+############################################################################
+#
+# The procedures are as follows:
+#
+# TDraw(n) -- move forward and draw
+# TSkip(n) -- skip forward without drawing
+# The turtle moves forward n units. n can be negative to move
+# backwards.
+#
+# TDrawto(x, y) -- draw to the point (x,y)
+# The turtle turns and draws a line to the point (x,y).
+# The heading is also set as a consequence of this movement.
+#
+# TGoto(x, y) -- set location
+# The turtle moves to the point (x,y) without drawing.
+# The turtle's heading remains unaltered.
+#
+# TRight(d) -- turn right
+# TLeft(d) -- turn left
+# The turtle turns d degrees to the right or left of its current
+# heading. Its location does not change, and nothing is drawn.
+#
+# TFace(x, y) -- set heading
+# The turtle turns to face directly to face the point (x,y).
+# If the turtle is already at (x,y), the heading does not change.
+#
+# TX() -- query current x position
+# TY() -- query current y position
+# The x- or y-coordinate of the turtle's current location is
+# returned.
+#
+# THeading() -- query heading
+# The turtle's heading (in degrees) is returned.
+#
+# TSave() -- save turtle state
+# TRestore() -- restore turtle state
+# TSave saves the current turtle window, location, and heading
+# on an internal stack. TRestore pops the stack and sets
+# those values, or fails if the stack is empty.
+#
+# TReset() -- clear screen and reinitialize
+# The window is cleared, the turtle moves to the center of the
+# screen without drawing, the heading is set to -90 degrees, and
+# the TRestore() stack is cleared. These actions restore the
+# initial conditions.
+#
+############################################################################
+#
+# Links: graphics
+#
+############################################################################
+#
+# Requires: Version 9 graphics
+#
+############################################################################
+#
+# See also: turtle.icn
+#
+############################################################################
+
+link graphics
+
+global T_x, T_y # current location
+global T_deg # current heading
+global T_stack # turtle state stack
+
+
+# TInit() -- initialize turtle system, opening window if needed
+
+procedure TInit() #: initialize turtle system
+
+ initial {
+ if /&window then
+ WOpen("width=500", "height=500") | stop("can't open window")
+ T_stack := []
+ T_x := WAttrib("width") / 2 + 0.5
+ T_y := WAttrib("height") / 2 + 0.5
+ T_deg := -90.0
+ }
+
+ return
+
+end
+
+
+# TReset() -- clear screen and stack, go to center, head -90 degrees
+
+procedure TReset() #: reset turtle system
+ initial TInit()
+
+ EraseArea()
+ T_stack := []
+ T_x := WAttrib("width") / 2 + 0.5
+ T_y := WAttrib("height") / 2 + 0.5
+ T_deg := -90.0
+
+ return
+
+end
+
+
+# TDraw(n) -- move forward n units while drawing a line
+
+procedure TDraw(n) #: draw with turtle
+ local rad, x, y
+ initial TInit()
+
+ rad := dtor(T_deg)
+ x := T_x + n * cos(rad)
+ y := T_y + n * sin(rad)
+ DrawLine(T_x, T_y, x, y)
+ T_x := x
+ T_y := y
+
+ return
+
+end
+
+
+# TDrawto(x, y) -- draw line to (x,y)
+
+procedure TDrawto(x, y) #: draw to with turtle
+ initial TInit()
+
+ TFace(x, y)
+ DrawLine(T_x, T_y, x, y)
+ T_x := x
+ T_y := y
+
+ return
+
+end
+
+
+# TSkip(n) -- move forward n units without drawing
+
+procedure TSkip(n) #: skip with turtle
+ local rad
+ initial TInit()
+
+ rad := dtor(T_deg)
+ T_x +:= n * cos(rad)
+ T_y +:= n * sin(rad)
+
+ return
+
+end
+
+
+# TGoto(x, y) -- move to (x,y) without drawing
+
+procedure TGoto(x, y) #: goto with turtle
+ initial TInit()
+ T_x := x
+ T_y := y
+
+ return
+
+end
+
+
+# TRight(d) -- turn right d degrees
+
+procedure TRight(d) #: turn turtle right
+ initial TInit()
+
+ T_deg +:= d
+ T_deg %:= 360 # normalize
+
+ return
+
+end
+
+
+# TLeft(d) -- turn left d degrees
+
+procedure TLeft(d) #: turn turtle left
+ initial TInit()
+
+ T_deg -:= d
+ T_deg %:= 360 # normalize
+
+ return
+
+end
+
+
+# TFace(x, y) -- turn to face (x,y), unless already there
+
+procedure TFace(x, y) #: turn turtle to face point
+ initial TInit()
+
+ if x ~= T_x | y ~= T_y then
+ T_deg := rtod(atan(y - T_y, x - T_x))
+
+ return
+
+end
+
+
+# TX() -- return current x location
+
+procedure TX(x) #: turtle x coordinate
+ initial TInit()
+
+ return T_x
+
+end
+
+
+# TY() -- return current y location
+
+procedure TY(y) #: turtle y coordinate
+ initial TInit()
+
+ return T_y
+
+end
+
+
+# THeading() -- return current heading
+
+procedure THeading() #: turtle heading
+ initial TInit()
+
+ return T_deg
+
+end
+
+
+# TSave() -- save turtle state
+
+procedure TSave() #: save turtle state
+ initial TInit()
+
+ push(T_stack, T_deg, T_y, T_x)
+
+ return
+
+end
+
+
+# TRestore() -- restore turtle state
+
+procedure TRestore() #: restore turtle state
+ initial TInit()
+
+ T_x := pop(T_stack)
+ T_y := pop(T_stack)
+ T_deg := pop(T_stack)
+
+ return
+
+end
diff --git a/ipl/gprocs/symrand.icn b/ipl/gprocs/symrand.icn
new file mode 100644
index 0000000..c3fb3a3
--- /dev/null
+++ b/ipl/gprocs/symrand.icn
@@ -0,0 +1,48 @@
+############################################################################
+#
+# File: symrand.icn
+#
+# Subject: Procedures to generate random points
+#
+# Author: Ralph E. Griswold
+#
+# Date: August 14, 1996
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# rand(x, y, extentx, extenty, n) generates random points in a rectangle.
+#
+# symrand(x, y, extentx, extenty, size, n) generates points symmetrically.
+#
+############################################################################
+#
+# Links: gobject
+#
+############################################################################
+
+link gobject
+
+# Generate n random points within a rectangular area.
+
+procedure rand(x, y, extentx, extenty, n)
+
+ every 1 to n do
+ suspend Point(x + ?extentx + 1, y + ?extenty + 1)
+
+end
+
+procedure symrand(x, y, extentx, extenty, size, n)
+ local xp, yp
+
+ every 1 to n do {
+ xp := x + ?extentx + 1
+ yp := y + ?extenty + 1
+ suspend Point(xp | size - xp, yp | size - yp) |
+ Point(yp | size - yp, xp | size - xp)
+ }
+
+end
diff --git a/ipl/gprocs/tieedit.icn b/ipl/gprocs/tieedit.icn
new file mode 100644
index 0000000..a5fa744
--- /dev/null
+++ b/ipl/gprocs/tieedit.icn
@@ -0,0 +1,876 @@
+############################################################################
+#
+# File: tieedit.icn
+#
+# Subject: Procedures to create and edit binary arrays
+#
+# Authors: Ralph E. Griswold and Gregg M. Townsend
+#
+# Date: January 19, 2002
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# This package provides a variety of facilities for creating and
+# editing binary arrays. It is intended for use with weaving tie-ups
+# and liftplans.
+#
+############################################################################
+#
+# Requires: Version 9 graphics, /tmp
+#
+############################################################################
+#
+# Links: interact, patxform, vdialog, vsetup, dialog, wopen
+#
+############################################################################
+
+link interact
+link patxform
+link vdialog
+link vsetup
+link dialog
+link wopen
+
+global cellsize
+global flip_horiz # icon for horizontal flip
+global flip_left # icon for left flip
+global flip_right # icon for right flip
+global flip_vert # icon for vertical flip
+global grid_height
+global grid_pane
+global grid_root
+global grid_rows
+global grid_state
+global grid_window
+global grid_width
+global grid_vidgets
+global hbits # number of bits horizontally
+global hi_horiz # highlighted icon for h-flip
+global hi_ident # highlighted icon for identity
+global hi_left # highlighted icon for l-flip
+global hi_right # highlighted icon for r-flip
+global hi_rot_180 # highlighted icon for 180 rot
+global hi_rot_90 # highlighted icon for 90-rot
+global hi_rot_m90 # highlighted icon for -90 rot
+global hi_vert # highlighted icon for v-flip
+global ident # icon for identity
+global maxsize # maximum grid dimensions
+global mode # pattern/tile display mode
+global old_pat # old pattern for undo
+global rotate_180 # icon for 180-degree rotation
+global rotate_90 # icon for 90-degree rotation
+global rotate_m90 # icon for -90-degree rotation
+global subservient # application status
+global sym_image_current # current drawing images
+global sym_image_next # next drawing images
+global sym_state # drawing state
+global symmet_xpos
+global symmet_yoff
+global symmetries # general symmetry state
+global tile_touched # tile modification switch
+global vbits # number of bits veritcally
+global xform_xpos
+global xform_ypos
+
+$define MaxCell 24 # maximum size of grid cell
+$define IconSize 16 # size of button icons
+$define MaxPatt 32
+$define InfoLength 40 # length of lines in info box
+
+record pattrec(tile)
+
+procedure copy_tile()
+ local output
+
+ output := open("/tmp/tieclip", "w") | {
+ Notice("Cannot copy tile.")
+ fail
+ }
+
+ write(output, rows2pat(grid_rows))
+
+ close(output)
+
+ return
+
+end
+
+# draw editing grid
+
+procedure grid()
+ local x, y
+
+ EraseArea(grid_pane)
+ every x := 0 to hbits * cellsize by cellsize do
+ DrawLine(grid_pane, x, 0, x, vbits * cellsize)
+ every y := 0 to vbits * cellsize by cellsize do
+ DrawLine(grid_pane, 0, y, hbits * cellsize, y)
+
+ return
+
+end
+
+# editing grid
+
+procedure grid_cb(vidget, e)
+ local x, y, i, j
+ static xpos, ypos
+
+ initial {
+ xpos := grid_vidgets["grid"].ax
+ ypos := grid_vidgets["grid"].ay
+ }
+
+ if e === (&lpress | &rpress | &ldrag | &rdrag) then {
+ j := (&x - xpos) / cellsize
+ i := (&y - ypos) / cellsize
+ if j < 0 | j >= hbits | i < 0 | i >= vbits then return
+
+ if e === (&lpress | &ldrag) then setbit(i, j, "1")
+ else setbit(i, j, "0")
+
+ tile_touched := 1
+ }
+
+ return
+
+end
+
+# file menu
+
+procedure grid_file_cb(vidget, menu)
+
+ return case menu[1] of {
+ "read @R" : read_tile()
+ "open @O" : open_gif()
+ "ims @M" : open_ims()
+ "write @W" : write_tile()
+ "copy @C" : copy_tile()
+ "paste @P" : paste_tile()
+ "quit @Q" : return_tile()
+ "save @S" : save_image()
+ }
+
+ return
+
+end
+
+procedure grid_init()
+ local e, i, j, x, y, v, h, input, window_save, atts
+ local shift_up, shift_left, shift_right, shift_down, pixmap
+ local clear, invert, scramble, trim, enlarge, resize, crop
+
+ symmetries := 0 # initially no symmetries
+
+ sym_state := [ # initially no symmetries
+ [1, -1, -1, -1],
+ [-1, -1, -1, -1]
+ ]
+
+ tile_touched := &null
+
+# Set up vidgets
+
+ window_save := &window # save current subject window
+ &window := &null # clear for new subject
+ atts := grid_ui_atts()
+ put(atts, "canvas=hidden")
+ (WOpen ! atts) | stop("*** can't open drawdown editor window")
+ grid_vidgets := grid_ui()
+ grid_window := &window
+ &window := window_save # restore previous subject window
+
+ grid_root := grid_vidgets["root"]
+
+ xform_xpos := grid_vidgets["xform"].ux
+ xform_ypos := grid_vidgets["xform"].uy
+ grid_width := grid_vidgets["grid"].uw
+ grid_height := grid_vidgets["grid"].uh
+ maxsize := grid_width / 3
+
+ grid_pane := Clone(grid_window, "bg=white", "dx=" || grid_vidgets["grid"].ax,
+ "dy=" || grid_vidgets["grid"].ay)
+
+ Clip(grid_pane, 0, 0, grid_width, grid_height)
+
+ symmet_xpos := grid_vidgets["symregion"].ux
+ symmet_yoff := grid_vidgets["symregion"].uy
+
+ shift_up := "16,#3ffe6003408141c143e140814081408140814081408140_
+ 81408160033ffe0000"
+ shift_left := "16,#3ffe6003400140014001401140195ffd40194011400140_
+ 01400160033ffe0000"
+ shift_right := "16,#3ffe600340014001400144014c015ffd4c014401400140_
+ 01400160033ffe0000"
+ shift_down := "16,#3ffe60034081408140814081408140814081408143e141_
+ c1408160033ffe0000"
+ flip_left := "16,#3ffe600340014079403940394049408149014e014e014f_
+ 01400160033ffe0000"
+ flip_right := "16,#3ffe600340014f014e014e014901408140494039403940_
+ 79400160033ffe0000"
+ flip_vert := "16,#3ffe6003408141c143e14081408140814081408143e141_
+ c1408160033ffe0000"
+ flip_horiz := "16,#3ffe600340014001400144114c195ffd4c194411400140_
+ 01400160033ffe0000"
+ rotate_90 := "16,#3ffe6003400140f141014201420142014f814701420140_
+ 01400160033ffe0000"
+ rotate_m90 := "16,#3ffe600340014781404140214021402140f94071402140_
+ 01400160033ffe0000"
+ rotate_180 := "16,#3ffe6003400141c140214011401140114111432147c143_
+ 01410160033ffe0000"
+ clear := "16,#3ffe600340014001400140014001400140014001400140_
+ 01400160033ffe0000"
+ invert := "16,#3ffe60ff40ff40ff40ff40ff40ff7fff7f817f817f817f_
+ 817f817f833ffe0000"
+ scramble := "16,#3ffe60034c014c0d418d41814001403159b1598140194c_
+ 194c0160033ffe0000"
+ trim := "16,#3ffe60134011407d40394011400140fd48854c857e854c_
+ 8548fd60033ffe0000"
+ enlarge := "16,#3ffe6083418143fd418148815c017efd48854885488548_
+ 8548fd60033ffe0000"
+ resize := "16,#3ffe6093419943fd419948915c017efd488548857e855c_
+ 8548fd60033ffe0000"
+ crop := "16,#3ffe60034011401147fd441144114411441144115ff144_
+ 01440160033ffe0000"
+
+ ident := "16,#3ffe6003400140014001400141c141c141c14001400140_
+ 01400160033ffe0000"
+
+ hi_ident := "16,#00001ffc3ffe3ffe3ffe3ffe3e3e3e3e3e3e3ffe3ffe3f_
+ fe3ffe1ffc00000000"
+ hi_rot_90 := "16,#00001ffc3ffe3f0e3efe3dfe3dfe3dfe307e38fe3dfe3f_
+ fe3ffe1ffc00000000"
+ hi_rot_m90 := "16,#00001ffc3ffe387e3fbe3fde3fde3fde3f063f8e3fde3f_
+ fe3ffe1ffc00000000"
+ hi_rot_180 := "16,#00001ffc3ffe3e3e3fde3fee3fee3fee3eee3cde383e3c_
+ fe3efe1ffc00000000"
+ hi_right := "16,#00001ffc3ffe30fe31fe31fe36fe3f7e3fb63fc63fc63f_
+ 863ffe1ffc00000000"
+ hi_left := "16,#00001ffc3ffe3f863fc63fc63fb63f7e36fe31fe31fe30_
+ fe3ffe1ffc00000000"
+ hi_vert := "16,#00001ffc3f7e3e3e3c1e3f7e3f7e3f7e3f7e3f7e3c1e3e_
+ 3e3f7e1ffc00000000"
+ hi_horiz := "16,#00001ffc3ffe3ffe3ffe3bee33e6200233e63bee3ffe3f_
+ fe3ffe1ffc00000000"
+
+ sym_image_next := [
+ [ident, hi_rot_90, hi_rot_m90, hi_rot_180],
+ [hi_right, hi_left, hi_vert, hi_horiz]
+ ]
+ sym_image_current := [
+ [hi_ident, rotate_90, rotate_m90, rotate_180],
+ [flip_right, flip_left, flip_vert, flip_horiz]
+ ]
+
+# now place the images
+
+ place(xform_xpos, xform_ypos, 1, 0, shift_up)
+ place(xform_xpos, xform_ypos, 0, 1, shift_left)
+ place(xform_xpos, xform_ypos, 2, 1, shift_right)
+ place(xform_xpos, xform_ypos, 1, 2, shift_down)
+ place(xform_xpos, xform_ypos, 0, 4, flip_right)
+ place(xform_xpos, xform_ypos, 0, 5, flip_left)
+ place(xform_xpos, xform_ypos, 1, 4, flip_vert)
+ place(xform_xpos, xform_ypos, 1, 5, flip_horiz)
+ place(xform_xpos, xform_ypos, 0, 7, rotate_90)
+ place(xform_xpos, xform_ypos, 0, 8, rotate_m90)
+ place(xform_xpos, xform_ypos, 1, 7, rotate_180)
+ place(xform_xpos, xform_ypos, 0, 10, clear)
+ place(xform_xpos, xform_ypos, 1, 10, invert)
+ place(xform_xpos, xform_ypos, 2, 10, scramble)
+ place(xform_xpos, xform_ypos, 0, 12, trim)
+ place(xform_xpos, xform_ypos, 1, 12, enlarge)
+ place(xform_xpos, xform_ypos, 2, 12, resize)
+ place(xform_xpos, xform_ypos, 0, 14, crop)
+
+ place(symmet_xpos, symmet_yoff, 0, 0, hi_ident)
+ place(symmet_xpos, symmet_yoff, 1, 0, rotate_90)
+ place(symmet_xpos, symmet_yoff, 2, 0, rotate_m90)
+ place(symmet_xpos, symmet_yoff, 3, 0, rotate_180)
+ place(symmet_xpos, symmet_yoff, 0, 1, flip_right)
+ place(symmet_xpos, symmet_yoff, 1, 1, flip_left)
+ place(symmet_xpos, symmet_yoff, 2, 1, flip_vert)
+ place(symmet_xpos, symmet_yoff, 3, 1, flip_horiz)
+
+ VSetState(grid_vidgets["symstate"], "none ")
+
+ return
+
+end
+
+# keyboard shortcuts
+
+procedure grid_shortcuts(e)
+
+ if (e === "\r") & \subservient then return_tile() # subservient role
+
+ if &meta then case map(e) of {
+ "0" : read_rows()
+ "1" : write_rows()
+ "c" : copy_tile()
+ "i" : tile_info()
+ "m" : open_ims()
+ "n" : new_tile()
+ "o" : open_gif()
+ "p" : paste_tile()
+ "q" : return_tile()
+ "r" : read_tile()
+ "s" : save_image()
+ "z" : undo_xform()
+ "w" : write_tile()
+ }
+
+ return
+
+end
+
+# check for valid integers
+
+procedure icheck(values)
+ local i
+
+ every i := !values do
+ if not(integer(i)) | (i < 0) then {
+ Notice("Invalid value")
+ fail
+ }
+
+ return
+
+end
+
+procedure new_tile()
+
+ case Dialog("New:", ["height", "width"], [*grid_rows, *grid_rows[1]], 3,
+ ["Okay", "Cancel"]) of {
+ "Cancel" : fail
+ "Okay" : {
+ icheck(dialog_value) | fail
+ grid_rows := list(dialog_value[1], repl("0", dialog_value[2]))
+ tile_touched := 1
+ return setup()
+ }
+ }
+
+ return
+
+end
+
+procedure open_gif()
+ local win, ims
+
+ repeat {
+ if OpenDialog("Open image:") == "Cancel" then fail
+ win := WOpen("canvas=hidden", "image=" || dialog_value) | {
+ Notice("Cannot open image.")
+ next
+ }
+ ims := Capture(win, "g2")
+ WClose(win)
+ setup_ims(ims)
+ return
+ }
+
+end
+
+procedure open_ims()
+ local ims, input
+
+ repeat {
+ if OpenDialog("Open ims:") == "Cancel" then fail
+ input := open(dialog_value) | {
+ Notice("Cannot open ims file.")
+ next
+ }
+ ims := read(input)
+ close(input)
+ setup_ims(ims)
+ return
+ }
+
+end
+
+procedure setup_ims(ims)
+ local width
+
+ grid_rows := []
+
+ ims ? {
+ width := tab(upto(','))
+ while tab(upto(',') + 1)
+# while put(grid_rows, map(move(width), "01", "10"))
+ while put(grid_rows, move(width))
+ }
+
+ setup()
+
+ return
+
+end
+
+procedure paste_tile()
+ local input, tile
+
+ input := open("/tmp/tieclip") | {
+ Notice("Cannot paste tie-up file.")
+ fail
+ }
+
+ tile := read_pattern(input) | {
+ Notice("Cannot process matrix.")
+ close(input)
+ fail
+ }
+
+ close(input)
+
+ grid_rows := pat2rows(tile.tile)
+
+ return setup()
+
+end
+
+# place icon
+
+procedure place(xoff, yoff, col, row, pattern)
+
+ DrawImage(grid_window, xoff + col * IconSize,
+ yoff + row * IconSize, pattern)
+
+ return
+
+end
+
+# read pattern specification
+
+procedure read_pattern(file)
+ local line
+
+ line := readpattline(file) | fail
+
+ return pattrec(legaltile(getpatt(line)), getpattnote(line))
+
+end
+
+# read and add pattern to tile list
+
+procedure read_tile()
+ local input, tile
+ static file, line
+
+ initial line := "1"
+
+ repeat {
+ if TextDialog("Read tile:", ["file", "line"], [file, line], [60, 4]) ==
+ "Cancel" then fail
+ input := open(dialog_value[1]) | {
+ Notice("Cannot open file.")
+ next
+ }
+ file := dialog_value[1]
+ line := (0 < integer(dialog_value[2]))
+ every 1 to line - 1 do
+ read(input) | {
+ Notice("Not that many lines in file.")
+ close(input)
+ next
+ }
+ tile := read_pattern(input) | {
+ Notice("Cannot process matrix.")
+ close(input)
+ next
+ }
+ close(input)
+ grid_rows := pat2rows(tile.tile)
+ return setup()
+ }
+
+end
+
+# read and add rows to tile list
+
+procedure read_rows()
+ local input
+ static file
+
+ repeat {
+ if OpenDialog("Read rows:") == "Cancel" then fail
+ input := open(dialog_value) | {
+ Notice("Cannot open file.")
+ next
+ }
+ file := dialog_value
+ grid_rows := []
+ while put(grid_rows, read(input))
+ close(input)
+ return setup()
+ }
+
+end
+
+procedure return_tile()
+
+ grid_state := "Done"
+
+ return
+
+end
+
+procedure save_image()
+
+ snapshot(grid_pane)
+
+ return
+
+end
+
+# set bits of tile
+
+procedure setbit(i, j, c)
+ local x, y, xu, yu, xv, yv, xt, yt, action
+ static xpos, ypos
+
+ initial {
+ xpos := grid_vidgets["grid"].ax
+ ypos := grid_vidgets["grid"].ay
+ }
+
+ if (symmetries = 0) & (grid_rows[i + 1, j + 1] == c) then return # optimization
+
+ x := j * cellsize + 1 # the selected cell itself
+ y := i * cellsize + 1
+ xt := i * cellsize + 1
+ yt := j * cellsize + 1
+
+ i +:= 1 # computational convenience
+ j +:= 1
+
+ xu := (hbits - j) * cellsize + 1 # opposite cells
+ yu := (vbits - i) * cellsize + 1
+ xv := (hbits - i) * cellsize + 1
+ yv := (vbits - j) * cellsize + 1
+
+ action := if c = 1 then FillRectangle else EraseArea
+
+ if sym_state[1, 1] = 1 then { # cell itself
+ grid_rows[i, j] := c
+ action(grid_pane, x, y, cellsize - 1, cellsize - 1)
+ }
+ if sym_state[1, 2] = 1 then { # 90 degrees
+ if grid_rows[j, -i] := c then # may be out of bounds
+ action(grid_pane, xv, yt, cellsize - 1, cellsize - 1)
+ }
+ if sym_state[1, 3] = 1 then { # -90 degrees
+ if grid_rows[-j, i] := c then # may be out of bounds
+ action(grid_pane, xt, yv, cellsize - 1, cellsize - 1)
+ }
+ if sym_state[1, 4] = 1 then { # 180 degrees
+ grid_rows[-i, -j] := c
+ action(grid_pane, xu, yu, cellsize - 1, cellsize - 1)
+ }
+ if sym_state[2, 1] = 1 then { # left diagonal
+ if grid_rows[j, i] := c then # may be out of bounds
+ action(grid_pane, xt, yt, cellsize - 1, cellsize - 1)
+ }
+ if sym_state[2, 2] = 1 then { # right diagonal
+ if grid_rows[-j, -i] := c then # may be out of bounds
+ action(grid_pane, xv, yv, cellsize - 1, cellsize - 1)
+ }
+ if sym_state[2, 3] = 1 then { # vertical
+ grid_rows[-i, j] := c
+ action(grid_pane, x, yu, cellsize - 1, cellsize - 1)
+ }
+ if sym_state[2, 4] = 1 then { # horizontal
+ grid_rows[i, -j] := c
+ action(grid_pane, xu, y, cellsize - 1, cellsize - 1)
+ }
+
+ return
+
+end
+
+# set up editing grid and view area
+
+procedure setup()
+ local i, j
+
+ hbits := *grid_rows[1]
+ vbits := *grid_rows
+
+ if (hbits | vbits) > maxsize then { # based on cell size >= 3
+ Notice("Dimensions too large.")
+ fail
+ }
+
+ if hbits > MaxPatt then mode := &null # too large for pattern
+
+ cellsize := MaxCell # cell size on window
+ cellsize >:= grid_width / (vbits + 4)
+ cellsize >:= grid_height / (hbits + 4)
+
+ grid()
+
+ every i := 1 to hbits do
+ every j := 1 to vbits do
+ if grid_rows[j, i] == "1" then
+ FillRectangle(grid_pane, (i - 1) * cellsize,
+ (j - 1) * cellsize, cellsize, cellsize)
+
+ return
+
+end
+
+procedure symstate_cb(vidget, value)
+ local row, col
+
+ # Note: the blanks at the end of these radio-button labels are
+ # for interface formatting.
+
+ sym_state := case value of {
+ "none " : [[1, -1, -1, -1], [-1, -1, -1, -1]]
+ "all " : [[1, 1, 1, 1], [1, 1, 1, 1]]
+ }
+
+ sym_image_next := [
+ [ident, hi_rot_90, hi_rot_m90, hi_rot_180],
+ [hi_right, hi_left, hi_vert, hi_horiz]
+ ]
+ sym_image_current := [
+ [hi_ident, rotate_90, rotate_m90, rotate_180],
+ [flip_right, flip_left, flip_vert, flip_horiz]
+ ]
+
+ if value == "all " then sym_image_next :=: sym_image_current
+
+ every col := 1 to 4 do
+ every row := 1 to 2 do
+ place(symmet_xpos, symmet_yoff, col - 1, row - 1,
+ sym_image_current[row, col])
+ return
+
+end
+
+# symmetry buttons
+
+procedure symmet_cb(vidget, e)
+ local col, row, symcount
+
+ if e === (&lpress | &rpress | &mpress) then {
+ col := (&x - symmet_xpos) / IconSize + 1
+ row := (&y - symmet_yoff) / IconSize + 1
+ sym_state[row, col] *:= -1
+ sym_image_current[row, col] :=: sym_image_next[row, col]
+ place(symmet_xpos, symmet_yoff, col - 1, row - 1,
+ sym_image_current[row, col])
+ symcount := 0
+ every symcount +:= !!sym_state
+ if symcount = -8 then
+ Notice("No drawing mode enabled; pattern cannot be edited")
+ else if (sym_state[1, 1] = 1) & (symcount = -6) then symmetries := 0
+ else symmetries := 1
+
+ return
+ }
+
+ fail
+
+end
+
+# tile menu
+
+procedure tile_cb(vidget, value)
+ local result
+
+ case value[1] of {
+ "new @N" : new_tile()
+ "info @I" : tile_info()
+ }
+
+ return
+
+end
+
+# show information about tile
+
+procedure tile_info()
+ local line1, line2, pattern, bits, density
+
+ pattern := rows2pat(grid_rows)
+ bits := tilebits(grid_rows)
+ density := left(bits / real(*grid_rows[1] * *grid_rows), 6)
+
+ line1 := left(*grid_rows[1] || "x" || *grid_rows || " b=" || bits || " d=" ||
+ density, InfoLength)
+ line2 := if *pattern > InfoLength then pattern[1+:(InfoLength - 3)] ||
+ "..." else left(pattern, InfoLength)
+
+ Notice(line1, line2)
+
+ return
+
+end
+
+# undo transformation
+
+procedure undo_xform()
+
+ grid_rows := pat2rows(old_pat)
+
+ return setup()
+
+end
+
+# write pattern
+
+procedure write_tile()
+ local output
+
+ repeat {
+ if SaveDialog("Write pattern") == "Cancel" then fail
+ output := open(dialog_value, "w") | {
+ Notice("Cannot open file for writing.")
+ next
+ }
+ write(output, rows2pat(grid_rows))
+ close(output)
+ return
+ }
+
+end
+
+# write rows
+
+procedure write_rows()
+ local output
+
+ repeat {
+ if SaveDialog("Write rows") == "Cancel" then fail
+ output := open(dialog_value, "w") | {
+ Notice("Cannot open file for writing.")
+ next
+ }
+ every write(output, !grid_rows)
+ close(output)
+ return
+ }
+
+end
+
+# handle transformation
+
+procedure xform(col, row)
+ local result
+ static params
+
+ tile_touched := 1
+
+ return case col of {
+ 0: case row of {
+ 1: pshift(grid_rows, -1, "h")
+ 4: pflip(grid_rows, "r")
+ 5: pflip(grid_rows, "l")
+ 7: protate(grid_rows, 90)
+ 8: protate(grid_rows, -90)
+ 10: list(vbits, repl("0", hbits))
+ 12: ptrim(grid_rows)
+ 14: {
+ case Dialog("Crop:", ["left", "right", "top", "bottom"],
+ 0, 3, ["Okay", "Cancel"]) of {
+ "Cancel": fail
+ "Okay": {
+ icheck(dialog_value) | fail
+ result := copy(params := dialog_value)
+ push(result, grid_rows)
+ pcrop ! result
+ }
+ }
+ }
+ default: fail
+ }
+ 1: case row of {
+ 0: pshift(grid_rows, -1, "v")
+ 2: pshift(grid_rows, 1, "v")
+ 4: pflip(grid_rows, "v")
+ 5: pflip(grid_rows, "h")
+ 7: protate(grid_rows, 180)
+ 10: pinvert(grid_rows)
+ 12: {
+ case Dialog("Enlarge:", ["left", "right", "top", "bottom"],
+ 0, 3, ["Okay", "Cancel"]) of {
+ "Cancel": fail
+ "Okay": {
+ icheck(dialog_value) | fail
+ result := copy(params := dialog_value)
+ push(result, grid_rows)
+ pborder ! result
+ }
+ }
+ }
+ default: fail
+ }
+ 2: case row of {
+ 1: pshift(grid_rows, 1, "h")
+ 10: pscramble(grid_rows, "b")
+ 12: {
+ case Dialog("Center:", ["width", "height"], [*grid_rows[1], *grid_rows],
+ 3, ["Okay", "Cancel"]) of {
+ "Cancel": fail
+ "Okay": {
+ icheck(dialog_value) | fail
+ result := copy(params := dialog_value)
+ push(result, grid_rows)
+ pcenter ! result
+ }
+ }
+ }
+ default: fail
+ }
+ default: fail
+ }
+
+end
+
+# transformation buttons
+
+procedure xform_cb(vidget, e)
+ local col, row
+
+ if e === (&lpress | &rpress | &mpress) then {
+ old_pat := rows2pat(grid_rows)
+ col := (&x - xform_xpos) / IconSize
+ row := (&y - xform_ypos) / IconSize
+ grid_rows := xform(col, row) | fail
+ return setup()
+ }
+
+end
+
+#===<<vib:begin>>=== modify using vib; do not remove this marker line
+procedure grid_ui_atts()
+ return ["size=635,568", "bg=pale gray", "label=Drawdown Editor"]
+end
+
+procedure grid_ui(win, cbk)
+return vsetup(win, cbk,
+ ["grid_ui:Sizer:::0,0,635,568:Drawdown Editor",],
+ ["file:Menu:pull::0,0,36,21:File",grid_file_cb,
+ ["read @R","open @O","ims @M","write @W","copy @C",
+ "paste @P","quit @Q ","save @S"]],
+ ["line1:Line:::0,22,660,22:",],
+ ["symmetries:Label:::22,316,70,13:symmetries",],
+ ["symstate:Choice::2:26,384,64,42:",symstate_cb,
+ ["all ","none "]],
+ ["tile:Menu:pull::38,0,64,21:Drawdown",tile_cb,
+ ["new @N","info @I"]],
+ ["transformations:Label:::5,33,105,13:transformations",],
+ ["symregion:Rect:grooved::24,338,68,36:",symmet_cb],
+ ["info:Rect:invisible::123,32,251,19:",],
+ ["xform:Rect:grooved::32,58,52,244:",xform_cb],
+ ["grid:Rect:sunken::123,58,500,500:",grid_cb],
+ )
+end
+#===<<vib:end>>=== end of section maintained by vib
diff --git a/ipl/gprocs/tieutils.icn b/ipl/gprocs/tieutils.icn
new file mode 100644
index 0000000..042e102
--- /dev/null
+++ b/ipl/gprocs/tieutils.icn
@@ -0,0 +1,424 @@
+############################################################################
+#
+# File: tieutils.icn
+#
+# Subject: Procedures related to weaving tie-ups
+#
+# Author: Ralph E. Griswold
+#
+# Date: September 15, 2001
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# imr2tie(imr) converts g2 image record to tie-ip
+#
+# pat2tie(pat) converts bi-level pattern to tie-up string
+#
+# pat2tier(pat) converts bi-level pattern to tie-up record
+#
+# showpat(pat, size, fg, bg)
+# produces a hidden window for the pattern as a matrix
+# with the specified foreground and background colors
+#
+# str2matrix(shafts, treadles, s)
+# produce matrix from binary string
+#
+# testtie(s) succeeds if s is a valid tie-up but fails otherwise
+#
+# tie2imr(s) converts tie-up to g2 image record
+#
+# tie2pat(i, j, tie)
+# converts tie-up to bi-level pattern
+#
+# tie2coltier(s) creates a black/white color tieup-record for
+# tie-up s
+#
+# tie2tier(s) creates a 0/1 tie-up record for tie-up s
+#
+# tier2rstring(r) creates a tie-up string from a tie-up record
+#
+# twill(pattern, shift, shafts)
+# twill tie-up
+#
+# overunder(pattern, treadles)
+# over/under tie-up structure
+#
+# direct(shafts, treadles)
+# direct tie-up
+#
+# satin(counter, shafts, treadles)
+# satin tie-up
+#
+# tabby(shafts, treadles)
+# tabby tie-up
+#
+# general(pattern, shift, rep, shafts)
+# general tie-up
+#
+# exptie(expression, shafts, treadles)
+# expression tie-up
+#
+############################################################################
+#
+# Requires: Version 9 graphics
+#
+############################################################################
+#
+# Links: cells, numbers, wopen, patutils, imrutils, patxform
+#
+############################################################################
+
+link cells
+link numbers
+link wopen
+link patutils
+link patxform
+link imrutils
+
+record tie(shafts, treadles, matrix)
+
+procedure imr2tie(imr) #: convert image record to tie-up
+
+ return imr.width || ";" || *imr.pixels / imr.width || ";" || imr.pixels
+
+end
+
+procedure pat2tie(pat) #: convert pattern to tie-up string
+ local matrix, tieup, shafts, treadles
+
+ pat ? { # OLD-STYLE BIT STRING TIE-UP
+ if shafts := tab(upto(',')) &
+ move(1) &
+ treadles := tab(upto(',')) &
+ move(1) then {
+ matrix := list(shafts)
+ while put(matrix, move(treadles))
+ }
+ else matrix := pat2rows(pat)
+ }
+
+ tieup := tie(*matrix[1], *matrix, matrix)
+
+ return tier2string(tieup)
+
+end
+
+procedure pat2tier(pat) #: convert pattern to tie-up record
+ local matrix
+
+ matrix := pat2rows(pat)
+
+ return tie(*matrix[1], *matrix, matrix)
+
+end
+
+# Set up empty palette grid
+
+procedure showpat(pat, cellsize, fg, bg) #: image of bi-level pattern
+ local x, y, panel, row, rows, color, tieup
+
+ /cellsize := 10
+
+ rows := pat2rows(pat)
+
+ panel := makepanel(*rows[1], *rows, cellsize, fg, bg)
+
+ y := 1
+
+ every row := !rows do {
+ every x := 1 to *row do {
+ color := if row[x] == "1" then "black" else "white"
+ colorcell(panel, x, y, color)
+ }
+ y +:= 1
+ }
+
+ return panel
+
+end
+
+procedure str2matrix(shafts, treadles, tieup)
+ local matrix
+
+ matrix := []
+
+ tieup ? {
+ every 1 to treadles do
+ put(matrix, move(shafts))
+ }
+
+ return matrix
+
+end
+
+procedure testtie(s) #: test validity of tie-up s
+ local n, m, bits
+
+ s ? {
+ n := (0 < integer(tab(upto(';')))) &
+ move(1) &
+ m := (0 < integer(tab(upto(';')))) &
+ move(1) &
+ bits := tab(0)
+ } | fail # bad header
+
+ if *(cset(bits) -- '01') > 0 then fail # illegal characters
+
+ if *bits ~= (n * m) then fail # wrong length
+
+ return s
+
+end
+
+procedure tie2imr(tie) #: convert tie-up to image record
+ local width
+
+ tie ? {
+ width := tab(upto(';'))
+ move(1)
+ tab(upto(';') + 1)
+ return imstoimr(width || ",g2," || tab(0))
+ }
+
+end
+
+procedure tie2pat(shafts, treadles, tie) #: convert tie-up record to ims
+ local tieup, matrix
+
+ tieup := tie2tier(shafts, treadles, tie)
+ matrix := tieup.matrix
+ return rows2pat(matrix)
+
+end
+
+procedure tie2tier(shafts, treadles, tieup) #: create 0/1 tie-up record
+ local matrix
+
+ matrix := []
+
+ tieup ? {
+ every 1 to treadles do
+ put(matrix, move(shafts))
+ }
+
+ return tie(shafts, treadles, matrix)
+
+end
+
+procedure tie2coltier(tieup) #: create color tie-up record
+ local result, shafts, treadles, rec
+
+ result := []
+
+ if not upto(';', tieup) then # old-style tie-up
+ tieup := "8;8;" || tieup
+
+ tieup ? {
+ (
+ shafts := tab(upto(';')) &
+ move(1) &
+ treadles := tab(upto(';')) &
+ move(1)
+ ) | stop("*** invalid tieup")
+ every 1 to shafts do
+ put(result, tcolors(move(treadles)))
+ }
+
+ return tie(shafts, treadles, result)
+
+end
+
+procedure tcolors(s)
+ local i, result
+
+ result := []
+
+ every i := 1 to *s do
+ put(result, if s[i] == "0" then "black" else "white")
+
+ return result
+
+end
+
+procedure tier2string(rec) #: convert tie-up record to string
+ local result
+
+ result := ""
+
+ every result ||:= !rec.matrix
+
+ return result
+
+end
+
+procedure twill(pattern, shift, shafts, treadles) #: twill tie-up
+ local row, rows
+
+ /treadles := shafts
+
+ row := overunder(pattern, treadles) | fail
+
+ rows := []
+
+ put(rows, row)
+
+ every 1 to shafts - 1 do
+ put(rows, row := rotate(row, shift))
+
+ return rows
+
+end
+
+procedure overunder(pattern, treadles)
+ local row, count, i
+
+ row := ""
+
+ count := 1 # odd/even over/under toggle
+
+ pattern ? {
+ while ="/" do { # INITIAL / NEEDS TO BE REMOVED
+ i := tab(many(&digits)) | fail
+ row ||:= repl(count, i)
+ count +:= 1
+ count %:= 2
+ }
+ if not pos(0) then fail
+ }
+
+ return extend(row, treadles)
+
+end
+
+# direct() supports a "generalized" tie-up when the number of shafts
+# is not the same as the number of treadles.
+
+procedure direct(shafts, treadles) #: direct tie-up
+ local row, i, rows, swap
+
+ /treadles := shafts # normal direct tie-up
+
+ if shafts ~= treadles then {
+ shafts :=: treadles
+ swap := 1
+ }
+
+ rows := []
+
+ row := "1" || repl("0", treadles - 1)
+
+ put(rows, row)
+
+ every i := 1 to shafts - 1 do
+ put(rows, row := rotate(row, -1))
+
+ if /swap then return rows
+ else return pflip(protate(rows, -90), "v")
+
+end
+
+procedure satin(counter, shafts, treadles) #: satin tie-up
+ local row, rows, m, k
+
+ rows := list(shafts, repl("0", treadles))
+
+ m := 1
+ rows[1, 1] := "1"
+
+ every k := 2 to shafts do
+ rows[k, residue(m +:= counter, shafts, 1)] := "1"
+
+ return rows
+
+end
+
+procedure tabby(shafts, treadles) #: tabby tie-up
+ local rows, row, i
+
+ rows := []
+
+ row := repl("01", (treadles + 1) / 2)
+
+ push(rows, row)
+
+ every i := 1 to shafts - 1 do
+ push(rows, row := rotate(row, 1))
+
+ return rows
+
+ return
+
+end
+
+procedure general(pattern, shift, rep, shafts) #: general tie-up
+ local row, rows, i
+
+ row := overunder(pattern, shafts) | fail
+
+ rows := []
+
+ every 1 to rep do
+ put(rows, row)
+
+ every i := (1 to shafts - 1) \ (shafts / rep) do {
+ row := rotate(row, shift)
+ every 1 to rep do
+ put(rows, row)
+ }
+
+ rows := rows[1+:shafts] # trim
+
+ return rows
+
+end
+
+procedure exptie(expression, shafts, treadles) #: expression tie-up
+ local output, size, row, rows, values, input
+
+ size := shafts * treadles
+
+ output := open("/tmp/expr.icn", "w") | {
+ stop("*** cannot open file for tie-up expression")
+ fail
+ }
+
+ write(output, "$include \"/tmp/include.wvp\"")
+ write(output, "link seqfncs")
+ write(output, "procedure main()")
+ write(output, " every write(", expression, " % 2) \\ ", size)
+ write(output, "end")
+
+ close(output)
+
+# remove("/tmp/seqdraft.err")
+
+ if system("icont -s /tmp/expr >/dev/null 2>/tmp/seqdraft.err") ~= 0 then
+ fail
+
+ input := open("expr", "p")
+
+ values := ""
+ every values ||:= !input
+
+ close(input)
+
+ remove("expr")
+
+ rows := []
+
+ if *values < (shafts * treadles) then {
+ stop("*** short tie-up sequence")
+ fail
+ }
+
+ values ? {
+ while put(rows, move(shafts))
+ }
+
+ return rows
+
+end
diff --git a/ipl/gprocs/tile.icn b/ipl/gprocs/tile.icn
new file mode 100644
index 0000000..f66d314
--- /dev/null
+++ b/ipl/gprocs/tile.icn
@@ -0,0 +1,64 @@
+############################################################################
+#
+# File: tile.icn
+#
+# Subject: Procedure to tile window
+#
+# Author: Ralph E. Griswold
+#
+# Date: September 29, 1997
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# This procedure tiles a portion of win1 over the specified portion
+# of win2, doubling to reduce the number of copies required.
+#
+############################################################################
+#
+# Requires: Version 9 graphics
+#
+############################################################################
+
+procedure tile(win1, win2, x1, y1, w1, h1) #: tile area with image
+ local w, h, wmax, hmax
+
+ /win1 := &window
+ /win2 := &window
+ /x1 := 0
+ /y1 := 0
+ /w1 := WAttrib(win1, "width")
+ /h1 := WAttrib(win1, "height")
+ wmax := WAttrib(win2, "width")
+ hmax := WAttrib(win2, "height")
+
+ if (w1 | h1) = 0 then fail
+
+ if w1 < 0 then {
+ w1 := -w1
+ x1 -:= w1
+ }
+
+ if h1 < 0 then {
+ h1 := -h1
+ y1 -:= h1
+ }
+
+ CopyArea(win1, win2, x1, y1, w1, h1) # initial copy
+
+ while w1 < wmax do { # copy and double
+ CopyArea(win2, win2, 0, 0, w1, h1, w1, 0)
+ w1 *:= 2
+ }
+
+ while h1 < hmax do { # copy and double
+ CopyArea(win2, win2, 0, 0, w1, h1, 0, h1)
+ h1 *:= 2
+ }
+
+ return
+
+end
diff --git a/ipl/gprocs/tiler.icn b/ipl/gprocs/tiler.icn
new file mode 100644
index 0000000..dae1997
--- /dev/null
+++ b/ipl/gprocs/tiler.icn
@@ -0,0 +1,74 @@
+############################################################################
+#
+# File: tiler.icn
+#
+# Subject: Procedures to tile window with image
+#
+# Author: Ralph E. Griswold
+#
+# Date: December 18, 1997
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# tileimg(win, image) tiles win with copies of image.
+#
+# tileims(win, ims) tiles win with copies of the image specified by ims
+#
+# Note that tileimg() uses the gamma value of win.
+#
+############################################################################
+#
+# Requires: Version 9 graphics
+#
+############################################################################
+#
+# Links: imutils, tile
+#
+############################################################################
+
+link imutils
+link tile
+
+procedure tileimg(win, img) #: tile image
+ local hidden
+
+ hidden := WOpen("canvas=hidden", "image=" || img, "gamma=" ||
+ WAttrib(win, "gamma")) | {
+ write(&errout, "*** cannot open image ", img)
+ fail
+ }
+
+ tile(hidden, win)
+
+ WClose(hidden)
+
+ return
+
+end
+
+procedure tileims(win, ims) #: tile image string
+ local w, h
+
+ w := imswidth(ims)
+ h := imsheight(ims)
+
+ if ims ? {
+ tab(many(&digits)) & =",#"
+ } then {
+ WAttrib(win, "pattern=" || ims)
+ WAttrib(win, "fillstyle=textured")
+ FillRectangle(win)
+ }
+
+ else {
+ DrawImage(win, 0, 0, ims) | fail
+ tile(win, win, 0, 0, w, h)
+ }
+
+ return
+
+end
diff --git a/ipl/gprocs/turtle.icn b/ipl/gprocs/turtle.icn
new file mode 100644
index 0000000..d81c5f7
--- /dev/null
+++ b/ipl/gprocs/turtle.icn
@@ -0,0 +1,446 @@
+############################################################################
+#
+# File: turtle.icn
+#
+# Subject: Procedures for turtle-graphics interface
+#
+# Author: Gregg M. Townsend
+#
+# Date: August 8, 2000
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# These procedures provide a "turtle graphics" interface to Icon.
+# With this approach, popularized by the Logo programming language,
+# all drawing is done by a "turtle" that carries a pen over a drawing
+# surface under program control.
+#
+# TWindow(W) sets the turtle window.
+#
+# TDraw(n) moves forward and draws.
+#
+# TSkip(n) skips forward without drawing.
+#
+# TDrawto(x, y) draws to the point (x,y).
+#
+# TScale(n) sets or queries current scaling factor.
+#
+# TRight(d) turns right d degrees.
+#
+# TLeft(d) turns left d degrees.
+#
+# THeading(a) sets or queries the heading.
+#
+# TFace(x, y) sets or queries the heading.
+#
+# TX(x) sets or queries the current x position.
+#
+# TY(y) sets or queries the current y position.
+#
+# TGoto(x, y, a) sets the location and optionally changes the heading.
+#
+# THome() moves to the window center and turns to face upward.
+#
+# TReset() clears the window and reinitializes.
+#
+# TSave() saves the turtle state.
+#
+# TRestore() restores the turtle state.
+#
+# TRect(h, w) draws a rectangle centered at the turtle.
+#
+# TCircle(d) draws a circle centered at the turtle.
+#
+# TPoly(d, n) draws a polygon centered at the turtle.
+#
+# TFRect(h, w) draws a filled rectangle centered at the turtle.
+#
+# TFCircle(d) draws a filled circle centered at the turtle.
+#
+# TFPoly(d, n) draws a filled polygon centered at the turtle.
+#
+############################################################################
+#
+# In this package there is a single turtle which is itself invisible;
+# it is known only by the marks it leaves on the window. It remembers
+# its location and heading between calls.
+#
+# No explicit initialization is required. The turtle begins at the
+# center of the window with a heading of -90 degrees (that is, pointed
+# towards the top of the window).
+#
+# The turtle draws on &window unless a different window is specified by
+# calling TWindow(). If no window is provided and &window is null,
+# a 500x500 window is opened and assigned to &window.
+#
+# Distances are measured in pixels and are always multiplied by a
+# settable scaling factor, initially 1. Angles are measured in degrees;
+# absolute angles measure clockwise from the positive X axis.
+#
+############################################################################
+#
+# The procedures are as follows:
+#
+# TDraw(n) -- move forward and draw
+# TSkip(n) -- skip forward without drawing
+# The turtle moves forward n units. n can be negative to move
+# backwards.
+# Default: n = 1
+#
+# TDrawto(x, y) -- draw to the point (x,y)
+# The turtle turns and draws a line to the point (x,y).
+# The heading is also set as a consequence of this movement.
+# Default: center of window
+#
+# TScale(n) -- set or query current scaling factor.
+# If n is supplied, the scaling factor applied to TDraw and TSkip
+# arguments is *multiplied* (not replaced) by n. The resulting
+# (multiplied or unaltered) scaling factor is returned.
+# The turtle's heading and location do not change.
+#
+# TRight(d) -- turn right
+# TLeft(d) -- turn left
+# The turtle turns d degrees to the right or left of its current
+# heading. Its location does not change, and nothing is drawn.
+# The resulting heading is returned.
+# Default: d = 90
+#
+# THeading(a) -- set or query heading
+# The turtle's heading (in degrees) is returned. If a is supplied,
+# the heading is first set to that value. The location does not
+# change.
+#
+# TFace(x, y) -- set or query heading
+# The turtle turns to face directly towards the point (x,y).
+# If x and y are missing or the turtle is already at (x,y),
+# the heading does not change. The new heading is returned.
+# Default: center of window
+#
+# TX(x) -- set or query current x position
+# TY(y) -- set or query current y position
+# The unscaled x- or y-coordinate of the turtle's current location
+# is returned. If an argument is supplied, the coordinate value
+# is first set, moving the turtle without drawing. The turtle's
+# heading does not change.
+#
+# TGoto(x, y, a) -- set location and optionally change heading
+# The turtle moves to the point (x,y) without drawing.
+# The turtle's heading remains unaltered unless <a> is supplied,
+# in which case the turtle then turns to a heading of <a>.
+# Default: center of window
+#
+# THome() -- move to home (center of window) and point North
+# The turtle moves to the center of the window without drawing
+# and the heading is set to -90 degrees. The scaling factor
+# remains unaltered.
+#
+# TReset() -- clear window and reinitialize
+# The window is cleared, the turtle moves to the center of the
+# window without drawing, the heading is set to -90 degrees, the
+# scaling factor is reset to 1, and the TRestore() stack is
+# cleared. These actions restore the initial conditions.
+#
+# TSave() -- save turtle state
+# TRestore() -- restore turtle state
+# TSave saves the current turtle window, location, heading, and
+# scale on an internal stack. TRestore pops the stack and sets
+# those values, or fails if the stack is empty.
+#
+# TRect(h, w) -- draw a rectangle centered at the turtle
+# TCircle(d) -- draw a circle centered at the turtle
+# TPoly(d, n) -- draw an n-sided regular polygon centered at the turtle
+# These three procedures draw a figure centered at the turtle's
+# current location. The location and heading do not change.
+# The base of the figure, if any, is directly behind the turtle.
+#
+# TRect(h, w) draws a rectangle of height h and width w.
+# "width" is the dimension perpendicular to the turtle's path.
+# Default: h = 1
+# w = h
+#
+# TCircle(d) draws a circle of diameter d.
+# Default: d = 1
+#
+# TPoly(d, n) draws an n-sided regular polygon whose circumscribed
+# circle would have a diameter of d.
+# Default: d = 1
+# n = 3
+#
+# TFRect(h, w) -- draw a filled rectangle centered at the turtle
+# TFCircle(d) -- draw a filled circle centered at the turtle
+# TFPoly(d, n) -- draw an n-sided filled polygon centered at the turtle
+# These are like their counterparts above, but a solid figure is
+# drawn instead of just an outline.
+#
+# TWindow(win) -- set turtle window
+# The turtle is moved to the given window, retaining its
+# coordinates and heading.
+# Default: win = &window
+#
+# These procedures do not attempt to provide a complete graphics interface;
+# in particular, no control of color is provided. Missing functions can
+# be accomplished by calling the appropriate Icon routines.
+#
+# Unlike most turtle graphics environments, there are no commands to
+# lift and drop the pen. Instead, use TSkip() to move without drawing,
+# or set WAttrib("drawop=noop") if you really need a global "pen up"
+# state.
+#
+############################################################################
+#
+# Requires: Version 9 graphics
+#
+############################################################################
+
+global T_x, T_y # current location
+global T_deg # current heading
+global T_scale # current scaling
+global T_stack # turtle state stack
+global T_win # current window
+
+# TWindow(win) -- set turtle window
+
+procedure TWindow(win) #: set turtle window
+ /win := &window
+ if type(win) ~== "window" then
+ runerr(140, win)
+ T_win := win
+ return
+end
+
+# TInit() -- initialize turtle system, opening window if needed
+
+procedure TInit() #: initialize turtle system
+ TInit := 1 # suppress any subsequent calls
+ if /T_win then {
+ /&window := open("turtle", "g", "width=500", "height=500") |
+ stop("can't open window")
+ T_win := &window
+ }
+ T_stack := []
+ T_scale := 1.0
+ TGoto(, , -90.0)
+ return
+end
+
+# TReset() -- clear window and stack, reset scaling, go to center, head -90
+
+procedure TReset() #: reset turtle system
+ initial TInit()
+ T_stack := []
+ EraseArea(T_win, -WAttrib(T_win, "dx"), -WAttrib(T_win, "dy"))
+ T_scale := 1.0
+ return TGoto(, , -90.0)
+end
+
+# THome() -- go to center and set heading to 90 degrees
+
+procedure THome() #: return turtle to home
+ initial TInit()
+ return TGoto(, , -90.0)
+end
+
+# TScale(n) -- set / return scaling
+
+procedure TScale(n) #: turtle scaling
+ initial TInit()
+ if T_scale *:= (0.0 ~= \n) then
+ THeading(T_deg)
+ return T_scale
+end
+
+# THeading(d), TLeft(d), TRight(d), TFace(x, y) -- set / return heading
+
+procedure THeading(d) #: turtle heading
+ initial TInit()
+
+ T_deg := \d % 360 # set normalized heading
+ return T_deg
+end
+
+procedure TRight(d) #: turn turtle right
+ initial TInit()
+ return THeading(T_deg + (\d | 90.0))
+end
+
+procedure TLeft(d) #: turn turtle left
+ initial TInit()
+ return THeading(T_deg - (\d | 90.0))
+end
+
+procedure TFace(x, y) #: face turtle
+ initial TInit()
+ /x := WAttrib(T_win, "width") / 2 + 0.5
+ /y := WAttrib(T_win, "height") / 2 + 0.5
+ if not (x = \T_x & y = \T_y) then
+ return THeading(rtod(atan(y - T_y, x - T_x)))
+ else
+ return THeading()
+end
+
+# TX(x), TY(y) -- set or return current x / y location (unscaled).
+
+procedure TX(x) #: turtle x coordinate
+ initial TInit()
+ return (T_x := \x) | T_x
+end
+
+procedure TY(y) #: turtle y coordinate
+ initial TInit()
+ return (T_y := \y) | T_y
+end
+
+# TDraw(n) -- move forward n units while drawing a line
+
+procedure TDraw(n) #: draw with turtle
+ local rad
+ initial TInit()
+
+ /n := 1.0
+ rad := dtor(T_deg)
+ DrawLine(T_win, .T_x, .T_y,
+ T_x +:= T_scale * cos(rad) * n, T_y +:= T_scale * sin(rad) * n)
+ return
+end
+
+# TSkip(n) -- move forward n units without drawing
+
+procedure TSkip(n) #: skip with turtle
+ local rad
+ initial TInit()
+
+ /n := 1.0
+ rad := dtor(T_deg)
+ T_x +:= T_scale * cos(rad) * n
+ T_y +:= T_scale * sin(rad) * n
+ return
+end
+
+# TGoto(x, y, a) -- move to (x,y) without drawing, and set heading if given
+
+procedure TGoto(x, y, a) #: go to with turtle
+ initial TInit()
+ T_x := \x | WAttrib(T_win, "width") / 2 + 0.5
+ T_y := \y | WAttrib(T_win, "height") / 2 + 0.5
+ THeading(\a)
+ return
+end
+
+# TDrawto(x, y, a) -- draw line to (x,y), and set heading if given
+
+procedure TDrawto(x, y, a) #: draw to with turtle
+ initial TInit()
+ /x := WAttrib(T_win, "width") / 2 + 0.5
+ /y := WAttrib(T_win, "height") / 2 + 0.5
+ if /a then
+ TFace(x, y)
+ DrawLine(T_win, .T_x, .T_y, T_x := x, T_y := y)
+ THeading(\a)
+ return
+end
+
+# TSave() -- save turtle state
+
+procedure TSave() #: save turtle state
+ initial TInit()
+ push(T_stack, T_deg, T_y, T_x, T_scale, T_win)
+ return
+end
+
+# TRestore() -- restore turtle state
+
+procedure TRestore() #: restore turtle state
+ initial TInit()
+ T_win := pop(T_stack)
+ T_scale := pop(T_stack)
+ return TGoto(pop(T_stack), pop(T_stack), pop(T_stack))
+end
+
+
+############################################################################
+#
+# Higher level routines.
+# These do not depend on the internals of procs above.
+#
+############################################################################
+
+# TRect(h, w) -- draw a rectangle centered at the turtle
+# TFRect(h, w) -- draw a filled rectangle centered at the turtle
+
+procedure TRect(h, w) #: draw rectangle centered at turtle
+ return T_rectangle(h, w, DrawLine)
+end
+
+procedure TFRect(h, w) #: draw filled rectangle centered at turtle
+ return T_rectangle(h, w, FillPolygon)
+end
+
+procedure T_rectangle(h, w, xcall)
+ local l
+
+ /h := 1.0
+ /w := h
+ l := [T_win]
+ TSkip(h / 2.0); TRight()
+ TSkip(w / 2.0); put(l, TX(), TY()); TRight()
+ TSkip(h); put(l, TX(), TY()); TRight()
+ TSkip(w); put(l, TX(), TY()); TRight()
+ TSkip(h); put(l, TX(), TY()); TRight()
+ TSkip(w / 2.0); put(l, TX(), TY()); TLeft()
+ TSkip(-h / 2.0)
+ put(l, l[2], l[3])
+ xcall ! l
+ return
+end
+
+# TCircle(d) -- draw a circle centered at the turtle
+# TFCircle(d) -- draw a filled circle centered at the turtle
+
+procedure TCircle(d) #: draw circle centered at turtle
+ local r
+ d := TScale() * (abs(\d) | 1.0)
+ r := d / 2.0
+ DrawArc(T_win, TX() - r, TY() - r, d, d)
+ return
+end
+
+procedure TFCircle(d) #: draw filled circle centered at turtle
+ local r
+ d := TScale() * (abs(\d) | 1.0)
+ r := d / 2.0
+ FillArc(T_win, TX() - r, TY() - r, d, d)
+ return
+end
+
+# TPoly(d, n) -- draw an n-sided regular polygon centered at the turtle
+# TFPoly(d, n) -- draw an n-sided filled polygon centered at the turtle
+
+procedure TPoly(d, n) #: draw polygon centered at turtle
+ return T_polygon(d, n, DrawLine)
+end
+
+procedure TFPoly(d, n) #: draw filled polygon centered at turtle
+ return T_polygon(d, n, FillPolygon)
+end
+
+procedure T_polygon(d, n, xcall)
+ local r, a, da, cx, cy, x, y, l
+ r := TScale() * ((\d / 3.0) | 1.0)
+ n := abs(integer(\n + 0.5)) | 3.0
+ n <:= 2.0
+ da := dtor(360.0 / n)
+ a := dtor(THeading() + 180.0) + da / 2.0
+ x := (cx := TX()) + r * cos(a)
+ y := (cy := TY()) + r * sin(a)
+ l := [T_win, x, y]
+ every 1 to n do {
+ put(l, x := cx + r * cos(a+:=da))
+ put(l, y := cy + r * sin(a))
+ }
+ xcall ! l
+ return
+end
diff --git a/ipl/gprocs/twists.icn b/ipl/gprocs/twists.icn
new file mode 100644
index 0000000..bb1a4f4
--- /dev/null
+++ b/ipl/gprocs/twists.icn
@@ -0,0 +1,83 @@
+############################################################################
+#
+# File: twists.icn
+#
+# Subject: Procedures to produce traces of "twists"
+#
+# Author: Ralph E. Griswold
+#
+# Date: May 2, 2001
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# These procedures produce traces of twisting orbits. See
+#
+# Geometric and Artistic Graphics; Design Generation with
+# Microcomputers, Jean-Paul Delahaye, Macmillan, 1987, pp. 73-80.
+#
+# The arguments specify the starting positions, the extent of the
+# drawing, the number of segments, and various parameters that determine
+# the orbits.
+#
+############################################################################
+#
+# Links: gobject
+#
+############################################################################
+
+link gobject
+
+procedure twist1(x, y, extent, n, t1, t2, j1, j2, k1, k2, rscale1, rscale2,
+sfact, sscale, soff, yfact)
+ local radius1, radius2, angle, s, s1, s2, c1, c2, i
+ local jangle1, jangle2, kangle1, kangle2, sangle
+
+ radius1 := rscale1 * extent # scaling
+ radius2 := rscale2 * extent
+
+ jangle1 := 2 * &pi / n * j1 * t1
+ jangle2 := 2 * &pi / n * j2 * t1
+ kangle1 := 2 * &pi / n * k1 * t2
+ kangle2 := 2 * &pi / n * k2 * t2
+ sangle := sfact * &pi / n
+
+ every i := 0 to n do {
+ s := sscale * cos(sangle * i) + soff
+ c1 := cos(jangle1 * i)
+ s1 := sin(jangle2 * i)
+ c2 := s * cos(kangle1 * i)
+ s2 := s * sin(kangle2 * i)
+ suspend Point(x + radius1 * c1 + radius2 * (c1 * c2 - s1 * s2),
+ y + yfact * (radius1 * s1 + radius2 * (s1 * c2 + c1 * s2)))
+ }
+
+end
+
+procedure twist2(x, y, extent, n, t1, t2, j1, j2, k1, k2, rscale1, rscale2,
+sfact, yfact)
+ local radius1, radius2, angle, s1, s2, c1, c2, i
+ local jangle1, jangle2, kangle1, kangle2, sangle
+
+ radius1 := rscale1 * extent # scaling
+ radius2 := rscale2 * extent
+
+ jangle1 := 2 * &pi / n * j1 * t1
+ jangle2 := 2 * &pi / n * j2 * t1
+ kangle1 := 2 * &pi / n * k1 * t2
+ kangle2 := 2 * &pi / n * k2 * t2
+ sangle := sfact * &pi / n
+
+ every i := 0 to n do {
+ c1 := cos(jangle1 * i)
+ s1 := sin(jangle2 * i)
+ c2 := cos(kangle1 * i)
+ s2 := sin(kangle2 * i)
+ suspend Point(x + radius1 * c1 + radius2 * (c1 * c2 - s1 * s2),
+ y + yfact * (radius1 * s1 + radius2 * (s1 * c2 + c1 * s2)))
+ }
+
+end
diff --git a/ipl/gprocs/vbuttons.icn b/ipl/gprocs/vbuttons.icn
new file mode 100644
index 0000000..f6c2dd7
--- /dev/null
+++ b/ipl/gprocs/vbuttons.icn
@@ -0,0 +1,418 @@
+############################################################################
+#
+# File: vbuttons.icn
+#
+# Subject: Procedures for buttons
+#
+# Authors: Jon Lipp and Gregg M. Townsend
+#
+# Date: May 2, 2001
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# Vidgets defined in this file:
+#
+# Vbutton
+# Vtoggle
+# Vcheckbox (obsolete)
+# Vmessage
+# Vline
+#
+############################################################################
+#
+# Requires: Version 9 graphics
+#
+############################################################################
+#
+# Links: vstyle
+#
+############################################################################
+
+link vstyle
+
+############################################################################
+# Vbutton
+############################################################################
+record Vbutton_rec (win, s, callback, id, style, aw, ah, data,
+ ax, ay, uid, P, D, V)
+
+procedure Vbutton(params[])
+ local self, frame, x, y, ins
+ static procs, type
+
+ initial {
+ procs := Vstd(event_Vbutton, draw_Vbutton, outline_Vidget,
+ resize_Vbutton, inrange_Vpane, init_Vbutton, couplerset_Vbutton)
+ type := proc("type", 0) # protect attractive names
+ }
+ if ins := Vinsert_check(params) then {
+ frame := pop(params); x := pop(params); y:= pop(params)
+ }
+ self := Vbutton_rec ! params[1:8|0]
+ Vwin_check(self.win, "Vbutton()")
+ if type(\self.s) ~== "string" & not numeric(self.s) then
+ _Vbomb("invalid label passed to Vbutton()")
+ if (\self.aw, not numeric(self.aw) ) then
+ _Vbomb("invalid aw parameter to Vbutton()")
+ if (\self.ah, not numeric(self.ah) ) then
+ _Vbomb("invalid ah parameter to Vbutton()")
+
+ self.uid := Vget_uid()
+ Vset_style(self, self.style)
+ self.V := procs
+ self.P := Vstd_pos()
+ self.V.init(self)
+
+ if \ins then VInsert(frame, self, x, y)
+ return self
+end
+
+procedure draw_Vbutton(self)
+ self.D.draw_off(self)
+end
+
+procedure couplerset_Vbutton(self)
+ self.V.draw(self)
+end
+
+#
+# Dragging mouse over edge toggles mouse "on" or "off".
+#
+procedure event_Vbutton(self, e)
+ local out
+
+ if \self.callback.locked then fail
+ if e === (&lpress|&mpress|&rpress) then {
+ self.D.draw_on(self)
+ repeat {
+ e := Event(self.win)
+ if self.V.inrange(self, &x, &y) then {
+ if e === (&lrelease|&mrelease|&rrelease) then {
+ self.D.draw_off(self)
+ self.callback.V.set(self.callback, self)
+ return self.id
+ }
+ else if \out then {
+ self.D.draw_on(self)
+ out := &null
+ }
+ }
+ else
+ if e === (&ldrag|&mdrag|&rdrag) & /out then {
+ self.D.draw_off(self)
+ out := 1
+ }
+ else if e === (&lrelease|&mrelease|&rrelease) then {
+ self.D.draw_off(self)
+ break
+ }
+ }
+ return
+ }
+end
+
+procedure init_Vbutton (self)
+ local p
+
+ p := \self.callback
+ self.callback := Vbool_coupler()
+ add_clients_Vinit(self.callback, p, self)
+ self.D.init(self)
+end
+
+procedure resize_Vbutton(s, x, y, w, h)
+
+ resize_Vidget(s, x, y, w, h)
+ Vset_style(s, s.style)
+ s.D.init(s)
+end
+
+
+############################################################################
+# Vtoggle
+############################################################################
+
+procedure Vtoggle(params[])
+ local frame, x, y, ins, self
+ static procs, type
+
+ initial {
+ procs := Vstd(event_Vtoggle, draw_Vtoggle, outline_Vidget,
+ resize_Vidget, inrange_Vpane, init_Vbutton,
+ couplerset_Vbutton,,,,, set_value_Vtoggle)
+ type := proc("type", 0)
+ }
+
+ if ins := Vinsert_check(params) then {
+ frame := pop(params); x := pop(params); y:= pop(params)
+ }
+ self := Vbutton_rec ! params[1:8|0]
+ Vwin_check(self.win, "Vtoggle()")
+ if type(\self.s) ~== "string" & not numeric(self.s) then
+ _Vbomb("invalid label passed to Vtoggle()")
+ if (\self.aw, not numeric(self.aw) ) then
+ _Vbomb("invalid aw parameter to Vtoggle()")
+ if (\self.ah, not numeric(self.ah) ) then
+ _Vbomb("invalid ah parameter to Vtoggle()")
+
+
+ self.uid := Vget_uid()
+ Vset_style(self, self.style)
+ self.V := procs
+ self.P := Vstd_pos()
+ self.V.init(self)
+ if \ins then VInsert(frame, self, x, y)
+ return self
+end
+
+procedure draw_Vtoggle(self)
+ if \self.callback.value then
+ self.D.draw_on(self)
+ else
+ self.D.draw_off(self)
+end
+
+
+#
+# Basically same functionality as for Vbutton with the exception
+# of maintaining the state of the toggle between events.
+#
+procedure event_Vtoggle(self, e)
+ local out, new, original
+
+ if \self.callback.locked then fail
+ if e === (&lpress|&mpress|&rpress) then {
+ if /self.callback.value then {
+ new := self.D.draw_on
+ original := self.D.draw_off
+ }
+ else {
+ new := self.D.draw_off
+ original := self.D.draw_on
+ }
+ new(self)
+ repeat {
+ e := Event(self.win)
+ if self.V.inrange(self, &x, &y) then {
+ if e === (&lrelease|&mrelease|&rrelease) then {
+ self.callback.V.toggle(self.callback, self)
+ self.data := self.callback.value
+ return self.id
+ }
+ else if \out then {
+ new(self)
+ out := &null
+ }
+ }
+ else
+ if e === (&ldrag|&mdrag|&rdrag) & /out then {
+ original(self)
+ out := 1
+ }
+ else if e === (&lrelease|&mrelease|&rrelease) then {
+ original(self)
+ break
+ }
+ }
+ return
+ }
+end
+
+procedure set_value_Vtoggle(self, value)
+
+ if \value then
+ self.callback.V.set(self.callback)
+ else
+ self.callback.V.unset(self.callback)
+
+ self.data := self.callback.value
+ draw_Vtoggle(self)
+ return
+end
+
+############################################################################
+# Vcheckbox
+############################################################################
+record Vcheckbox_rec (win, callback, id, size, aw, ah, data,
+ ax, ay, cw, uid, P, V, D)
+
+procedure Vcheckbox(params[])
+ local frame, x, y, ins, self, p
+ static procs
+
+ initial {
+ procs := Vstd(event_Vtoggle, draw_Vtoggle, outline_Vidget,
+ resize_Vidget, inrange_Vpane, ,
+ couplerset_Vbutton,,,,, set_value_Vtoggle)
+ }
+
+ if ins := Vinsert_check(params) then {
+ frame := pop(params); x := pop(params); y:= pop(params)
+ }
+ self := Vcheckbox_rec ! params[1:5|0]
+ if ( \self.size, not numeric(self.size) ) then
+ _Vbomb("invalid size parameter to Vcheck_box()")
+ Vwin_check(self.win, "Vcheck_box()")
+ self.uid := Vget_uid()
+ self.V := procs
+ self.P := Vstd_pos()
+ self.D := Vstd_draw(draw_off_Vcheckbox, draw_on_Vcheckbox)
+
+## Init
+# PMIcon fix.
+# self.cw := Clone(self.win, "linewidth=2")
+ self.cw := WAttrib(self.win, "linewidth")
+ /self.size := 15
+ self.aw := self.ah := self.size
+
+ p := \self.callback
+ self.callback := Vbool_coupler()
+ add_clients_Vinit(self.callback, p, self)
+
+ if \ins then VInsert(frame, self, x, y)
+ return self
+end
+
+procedure draw_on_Vcheckbox(self)
+ local x, y, sz
+
+ x := self.ax
+ y := self.ay
+ sz := self.size
+# PMIcon fix.
+ WAttrib(self.win, "linewidth=2")
+ DrawSegment(self.win, x+1, y+1, x+sz-1, y+sz-1, x+1, y+sz-1, x+sz-1, y+1)
+# PMIcon fix.
+ WAttrib(self.win, "linewidth="||self.cw)
+ self.V.outline(self)
+end
+
+procedure draw_off_Vcheckbox(self)
+ local x, y, sz
+
+ x := self.ax
+ y := self.ay
+ sz := self.size
+# PMIcon fix.
+ WAttrib(self.win, "reverse=on", "linewidth=2")
+ DrawSegment(self.win, x+1, y+1, x+sz-1, y+sz-1, x+1, y+sz-1, x+sz-1, y+1)
+# PMIcon fix.
+ WAttrib(self.win, "reverse=off", "linewidth="||self.cw)
+ self.V.outline(self)
+end
+
+############################################################################
+# Vmessage
+############################################################################
+
+procedure Vmessage(params[])
+ static procs, type
+ local frame, x, y, ins, self
+
+ initial {
+ procs := Vstd(null_proc, draw_Vmessage, outline_Vidget,
+ resize_Vidget, null_proc, init_Vmessage, null_proc)
+ type := proc("type", 0) # protect attractive names
+ }
+
+ if ins := Vinsert_check(params) then {
+ frame := pop(params); x := pop(params); y:= pop(params)
+ }
+ self := Vbutton_rec ! params[1:3|0]
+ Vwin_check(self.win, "Vmessage()")
+ if type(\self.s) ~== "string" & not numeric(self.s) then
+ _Vbomb("invalid label passed to Vmessage()")
+
+ self.uid := Vget_uid()
+ self.V := procs
+ self.D := Vstd_draw()
+ self.P := Vstd_pos()
+ self.V.init(self)
+ if \ins then VInsert(frame, self, x, y)
+ return self
+end
+
+procedure draw_Vmessage(self)
+
+ GotoXY(self.win, self.ax+self.D.basex, self.ay+self.D.basey)
+ writes(self.win, self.s)
+# self.V.outline(self)
+end
+
+procedure init_Vmessage(self)
+ local TW, FH, ascent, descent
+
+ /self.s := ""
+ /self.aw := (TW := TextWidth(self.win, self.s))
+ ascent := WAttrib(self.win, "ascent")
+ descent := WAttrib(self.win, "descent")
+ /self.ah := FH := ascent + descent
+
+ self.D.basex := (self.aw - TW) / 2
+ self.D.basey := (self.ah - FH) / 2 + ascent
+end
+
+############################################################################
+# Vline
+#
+# I know, I know, this vidgie is not well designed or efficient.
+############################################################################
+record Vline_rec (win, ax1, ay1, ax2, ay2, aw, ah, id, uid, P, V)
+
+procedure Vline(params[])
+ local self
+ static procs
+
+ initial procs := Vstd(null_proc, draw_Vline, null_proc,
+ resize_Vline, null_proc, null_proc,
+ null_proc)
+ self := Vline_rec ! params[1:6|0]
+ Vwin_check(self.win, "Vline()")
+ if not numeric(self.ax1) then
+ _Vbomb("invalid coordinate parameter to Vline()")
+ if not numeric(self.ax2) then
+ _Vbomb("invalid coordinate parameter to Vline()")
+ if not numeric(self.ay1) then
+ _Vbomb("invalid coordinate parameter to Vline()")
+ if not numeric(self.ay2) then
+ _Vbomb("invalid coordinate parameter to Vline()")
+ self.uid := Vget_uid()
+ self.V := procs
+ self.P := Vstd_pos()
+
+ return self
+end
+
+procedure resize_Vline(frame, self)
+ local x, y, w, h, x1, y1, x2, y2
+
+ x := frame.ax
+ y := frame.ay
+ w := frame.aw
+ h := frame.ah
+ x1 := self.ax1
+ y1 := self.ay1
+ x2 := self.ax2
+ y2 := self.ay2
+
+ self.ax1 := x + ( (/x1, 0) | (x1 <= -1 , w+x1) |
+ (-1 < x1 < 0, w + x1*w) | (0 < x1 < 1, w*x1) | x1 )
+ self.ay1 := y + ( (/y1, 0) | (y1 <= -1 , h+y1) |
+ (-1 < y1 < 0, h + y1*h) | (0 < y1 < 1, h*y1) | y1 )
+ self.ax2 := x + ( (/x2, w) | (x2 <= -1 , w+x2) |
+ (-1 < x2 < 0, w + x2*w) | (0 < x2 < 1, w*x2) | x2 )
+ self.ay2 := y + ( (/y2, h) | (y2 <= -1 , h+y2) |
+ (-1 < y2 < 0, h + y2*h) | (0 < y2 < 1, h*y2) | y2 )
+end
+
+procedure draw_Vline(self)
+ DrawGroove(self.win, self.ax1, self.ay1, self.ax2, self.ay2)
+end
+
+procedure erase_Vline(self)
+ DrawGroove(self.win, self.ax1, self.ay1, self.ax2, self.ay2, 0)
+end
diff --git a/ipl/gprocs/vcoupler.icn b/ipl/gprocs/vcoupler.icn
new file mode 100644
index 0000000..c9172e9
--- /dev/null
+++ b/ipl/gprocs/vcoupler.icn
@@ -0,0 +1,327 @@
+############################################################################
+#
+# File: vcoupler.icn
+#
+# Subject: Procedures for coupler variables
+#
+# Author: Jon Lipp
+#
+# Date: April 1, 1997
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# Vidgets defined in this file:
+#
+# Vcoupler
+# Vrange_coupler
+# Vstrset_coupler
+# Vbool_coupler
+# Vtable_coupler
+# Vmenu_coupler
+#
+# Utility procedures in this file:
+#
+# add_clients_Vinit()
+#
+############################################################################
+#
+# Links: vidgets
+#
+############################################################################
+
+link vidgets
+
+record Vcoupler_rec(value, callers, clients, id, curr_id, old_id,
+ allowed, locked, uid, V)
+
+############################################################################
+# Vcoupler
+############################################################################
+
+procedure Vcoupler(params[])
+ local self
+ static procs
+
+ initial procs := Vstd_coupler(set_Vcoupler, add_client_Vcoupler,
+ init_Vcoupler, null_proc, null_proc,
+ null_proc)
+
+ self := Vcoupler_rec ! params
+ self.uid := Vget_uid()
+ self.V := procs
+ self.V.init(self)
+ return self
+end
+
+
+procedure call_clients_Vcoupler(s, caller, val)
+ local i, c
+ static type
+
+ initial type := proc("type", 0) # protect attractive name
+
+ every i := 1 to *s.clients do {
+ c := s.clients[i]
+ if type(c) == "procedure" then c(s.callers[i], val)
+ else if type(c) ? find("coupler") then c.V.set(c, caller, val)
+ else if type(c) == !Vrecset then {
+ # don't call yourself
+ if (type(\caller) == type(c) & \caller["uid"] === c["uid"]) then
+ next
+ c.V.couplerset(c, caller, val)
+ }
+ }
+end
+
+procedure set_Vcoupler(s, caller, val, call_clients)
+ if \s.locked then fail
+ s.value := val
+ if /call_clients then
+ call_clients_Vcoupler(s, caller, val)
+ return val
+end
+
+#
+# Client is the client of course; caller is the vidget record to be passed
+# to this client if type(client) == "procedure".
+#
+procedure add_client_Vcoupler(s, client, caller)
+local pl
+static image
+
+ initial image := proc("image", 0) # protect attractive name
+
+ image(client) ? { if ="function" then
+ _Vbomb("Icon function" || tab(0) || "() not allowed as callback")
+ }
+ put (s.clients, client)
+ put (s.callers, caller)
+end
+
+procedure init_Vcoupler(s)
+ /s.clients := []
+ /s.callers := []
+ s.id := V_COUPLER
+end
+
+############################################################################
+# Vrange_coupler
+# Range couplers are Vcouplers whose values are limited to a
+# particular range of legal values. Presently they must be numeric.
+# The default increment is 0.1.
+############################################################################
+record Vrange_coupler_rec(min, max, value, inc, callers, clients, real, id,
+ locked, uid, V)
+
+procedure Vrange_coupler(params[])
+ local self
+ static procs
+
+ initial procs := Vstd_coupler(set_Vrange_coupler,
+ add_client_Vcoupler,
+ init_Vrange_coupler, null_proc,
+ null_proc, null_proc)
+
+ self := Vrange_coupler_rec ! params
+ self.uid := Vget_uid()
+ self.V := procs
+ self.V.init(self)
+ return self
+end
+
+#
+# If the value passed is out of range, change caller
+procedure set_Vrange_coupler(s, caller, val, call_clients)
+ local theMax
+ static type
+
+ initial type := proc("type", 0) # protect attractive name
+
+ if \s.locked then fail
+ theMax := numeric(s.max) | (type(s.max) == !Vcoupler_recset, s.max.value) |
+ _Vbomb("illegal value in Vrange_coupler set")
+ val := (s.min > val, s.min) | (theMax < val, theMax)
+ s.value := val
+ if /s.real then val := integer(val)
+ if /call_clients then
+ call_clients_Vcoupler(s, caller, val)
+ return val
+end
+
+procedure init_Vrange_coupler(s)
+ static type
+
+ initial type := proc("type", 0) # protect attractive name
+
+ /s.min := 0; /s.max := 1.0
+ if \s.value < s.min | \s.value > s.max then s.value := s.min
+
+ /s.value := \ s.min
+ s.real := (type(s.min|s.max) == "real", 1)
+
+ /s.inc := 0.1*(s.max-s.min)
+ if /s.real then s.inc := integer(s.inc)
+ init_Vcoupler(s)
+end
+
+############################################################################
+# strset_coupler
+############################################################################
+
+procedure Vstrset_coupler(params[])
+ local self
+ static procs
+
+ initial procs := Vstd_coupler(set_Vstrset_coupler,
+ add_client_Vcoupler,
+ init_Vstrset_coupler, null_proc,
+ null_proc, null_proc)
+
+ self := Vcoupler_rec ! params
+ self.uid := Vget_uid()
+ self.V := procs
+ self.V.init(self)
+ return self
+end
+
+procedure set_Vstrset_coupler(s, id, val)
+ if \s.locked then fail
+ if !s.allowed === val then
+ return set_Vcoupler(s, id, val)
+end
+
+procedure init_Vstrset_coupler(s)
+ /s.allowed := []
+ init_Vcoupler(s)
+end
+
+############################################################################
+# Vbool_coupler
+############################################################################
+
+procedure Vbool_coupler(params[])
+ local self
+ static procs
+
+ initial procs := Vstd_coupler(set_Vbool_coupler,
+ add_client_Vcoupler,
+ init_Vcoupler, unset_Vbool_coupler,
+ toggle_Vbool_coupler, eval_Vbool_coupler)
+
+ self := Vcoupler_rec ! params
+ self.uid := Vget_uid()
+ self.V := procs
+ self.V.init(self)
+ return self
+end
+
+procedure eval_Vbool_coupler(s)
+ return \s.value
+end
+
+procedure set_Vbool_coupler(s, caller)
+ if \s.locked then fail
+ s.value := 1
+ call_clients_Vcoupler(s, caller, 1)
+ return s.value
+end
+
+procedure unset_Vbool_coupler(s, caller)
+ s.value := &null
+ call_clients_Vcoupler(s, caller, &null)
+ return s.value
+end
+
+procedure toggle_Vbool_coupler(s, caller)
+ local newstate
+
+ newstate := (/s.value, 1)
+ return set_Vcoupler(s, caller, newstate)
+end
+
+############################################################################
+# Vtable_coupler
+############################################################################
+
+procedure Vtable_coupler(params[])
+ local self
+ static procs
+
+ initial procs := Vstd_coupler(set_Vtable_coupler,
+ add_client_Vcoupler,
+ init_Vtable_coupler, null_proc,
+ null_proc, null_proc)
+
+ self := Vcoupler_rec ! params
+ self.uid := Vget_uid()
+ self.V := procs
+ self.V.init(self)
+ return self
+end
+
+procedure set_Vtable_coupler(s, id, key, val)
+ s.value[key] := val
+ call_clients_Vcoupler(s, id, val)
+end
+
+procedure init_Vtable_coupler(s)
+ s.value := table()
+ init_Vcoupler(s)
+end
+
+############################################################################
+# Vmenu_coupler
+############################################################################
+
+procedure Vmenu_coupler(params[])
+ local self
+ static procs
+
+ initial procs := Vstd_coupler(set_Vmenu_coupler,
+ null_proc,
+ null_proc, null_proc,
+ null_proc, null_proc)
+
+ self := Vcoupler_rec ! params
+ self.uid := Vget_uid()
+ self.V := procs
+ self.V.init(self)
+ return self
+end
+
+
+procedure set_Vmenu_coupler(s, id, val)
+ if \s.locked then fail
+ s.old_id := s.curr_id
+ s.curr_id := id
+ s.value := val
+ (\s.old_id).V.couplerset(s.old_id, , val)
+ return val
+end
+
+
+############################################################################
+# Utilities
+############################################################################
+
+#
+# Takes the callback parameter passed in upon creation of a vidget and
+# adds them to the client list of the coupler variable, checking if it
+# is a list or not.
+#
+procedure add_clients_Vinit(cv, callbacks, vid)
+ local cb
+ static type
+
+ initial type := proc("type", 0) # protect attractive name
+
+ if type(\callbacks) == "list" then
+ every cb := !callbacks do cv.V.add_client(cv, \cb, vid)
+ else
+ cv.V.add_client(cv, \callbacks, vid)
+end
+
diff --git a/ipl/gprocs/vdialog.icn b/ipl/gprocs/vdialog.icn
new file mode 100644
index 0000000..e83833e
--- /dev/null
+++ b/ipl/gprocs/vdialog.icn
@@ -0,0 +1,296 @@
+############################################################################
+#
+# File: vdialog.icn
+#
+# Subject: Procedures for dialog boxes
+#
+# Author: Jon Lipp
+#
+# Date: November 5, 1997
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# Vidgets defined in this file:
+#
+# Vdialog
+#
+############################################################################
+#
+# Requires: Version 9 graphics
+#
+############################################################################
+#
+# Links: vbuttons, vtext
+#
+############################################################################
+
+link vbuttons
+link vtext
+
+record DL_pos_rec(x,y) # dialog position record
+
+############################################################################
+# Vdialog - allows a pop-up menu_frame to be associated with a button.
+#
+# Open the dialogue, let the user edit fields, one entry per field.
+# returns a list containing the values of the fields.
+#
+############################################################################
+record Vdialog_frame_rec(win, padx, pady, callback, aw, ah, lookup,
+ draw, id, ax, ay, uid, F, P, V)
+
+procedure Vdialog(params[])
+ local self
+ static procs
+
+ initial {
+ procs := Vstd(event_Vframe, draw_Vframe, 1,
+ resize_Vframe, inrange_Vpane, init_Vdialog,
+ couplerset_Vpane, insert_Vdialog, remove_Vframe,
+ lookup_Vframe, set_abs_Vframe)
+ if /V_OK then VInit()
+ }
+
+ self := Vdialog_frame_rec ! params[1:5|0]
+ Vwin_check(self.win, "Vdialog()")
+ if (\self.padx, not numeric(self.padx) ) then
+ _Vbomb("invalid padx parameter to Vdialog()")
+ if (\self.pady, not numeric(self.pady) ) then
+ _Vbomb("invalid pady parameter to Vdialog()")
+
+ self.uid := Vget_uid()
+ self.V := procs
+ self.F := Vstd_dialog(open_dialog_Vdialog, register_Vdialog,
+ format_Vdialog, unregister_Vdialog)
+ self.P := Vstd_pos()
+ self.V.init(self)
+ return self
+end
+
+procedure open_dialog_Vdialog(self, x, y, values, def_str)
+ local i, c, e, newfocus, tid, rv, now, val
+ local entry, r, def, sel, v, args, parent, posn
+ static xytable, type
+
+ initial {
+ xytable := table()
+ type := proc("type", 0) # protect attractive name
+ }
+
+## Check ID and determine x and y values.
+ if \x then {
+ if WAttrib(self.win, "canvas") == ("normal" | "maximal") then {
+ x +:= WAttrib(self.win, "posx")
+ y +:= WAttrib(self.win, "posy")
+ }
+ }
+ else if \y then {
+ /xytable[y] := DL_pos_rec()
+ posn := xytable[y]
+ x := posn.x
+ y := posn.y
+ }
+
+ if WAttrib(self.win,"canvas") == ("normal" | "maximal") then {
+ /x := WAttrib(self.win,"posx") + (WAttrib(self.win,"width")-self.aw) / 2
+ /y := WAttrib(self.win,"posy") + (WAttrib(self.win,"height")-self.ah) / 2
+ /x <:= 20
+ /y <:= 10
+ }
+
+## Sort text entry list.
+ self.F.text_entries := sort(self.F.text_entries)
+ every i := 1 to *self.F.text_entries do
+ self.F.text_lu[self.F.text_entries[i]] := i
+
+## Build arg list and open window
+ args := []
+ put(args, "size=" || self.aw || "," || self.ah)
+ put(args, "pos=" || \x || "," || \y)
+ put(args, "display=" || WAttrib(self.win, "display"))
+ put(args, "label=" || ("" ~== WAttrib(self.win, "label")))
+ put(args, "font=" || WAttrib(self.win, "font"))
+ put(args, "gamma=" || WAttrib(self.win, "gamma"))
+ if (c := Fg(self.win))[1] ~== "-" then
+ put(args, "fg=" || c)
+ if (c := Bg(self.win))[1] ~== "-" then
+ put(args, "bg=" || c)
+ parent := self.win
+ if not (self.win := WOpen ! args) then {
+ write(&errout, "can't open window for dialog")
+ writes(&errout, "window arguments:")
+ every writes(&errout, " ", !args | "\n")
+ stop()
+ }
+
+ every v := !self.draw do {
+ v.win := self.win
+ if type(v) == ("Vradio_frame_rec" | "Vscrollbar_frame_rec") then
+ every (!v.draw).win := self.win
+ }
+ self.V.resize(self, 0, 0, self.aw, self.ah)
+
+## Make a sorted list of self.F.entries
+ sel := sort(self.F.entries, 1)
+## set values of fields to value list, or default if entry is &null
+ every i := 1 to *sel do {
+ entry := sel[i][2]
+ val := values[i] | &null
+ (\entry).V.set_value(entry, val)
+ }
+ self.F.focus := &null
+ self.V.draw(self)
+
+## Find default button according to def_str.
+ if \def_str then
+ every i := !self.lookup do
+ if def_str == \i["s"] then {
+ def := i
+ break
+ }
+
+ self.F.focus := self.F.entries[self.F.text_entries[1]]
+ newfocus := \self.F.focus | \sel[1][2] | &null
+ (\self.F.focus).T.block(self.F.focus)
+
+## Call the user initialization callback, if any.
+ (\self.callback)(self)
+
+ repeat {
+ # outline the default button every time around, in case the outline was
+ # erased by a redraw call for the dialog (e.g. in ColorDialog())
+ BevelRectangle((\def).win, def.ax-5, def.ay-5, def.aw+10, def.ah+10,-2)
+
+ e := Event(self.win)
+ if e === "\r" then {
+ if \def then {
+ e := &lpress
+ &x := def.ax + 1
+ &y := def.ay + 1
+ Enqueue(def.win, &lrelease, def.ax + 1, def.ay + 1)
+ }
+ else next
+ }
+ if integer(e) < 0 then {
+ newfocus := self.V.lookup(self, &x, &y) | self.F.focus
+ if ((\newfocus).id) ~=== ((\self.F.focus).id) then
+ switch_focus_Vdialog(self, newfocus)
+ }
+ r := (\newfocus).V.event(newfocus, e, &x, &y) | &null
+ case r of {
+ V_NEXT: { #move to next entry
+ now := self.F.text_lu[self.F.focus.id]
+ tid := ((*self.F.text_entries >= now + 1) | 1)
+ switch_focus_Vdialog(self, self.F.entries[self.F.text_entries[tid]])
+ }
+ V_PREVIOUS: { #move to previous entry
+ now := self.F.text_lu[self.F.focus.id]
+ tid := ((1 <= now - 1) | *self.F.text_entries)
+ switch_focus_Vdialog(self, self.F.entries[self.F.text_entries[tid]])
+ }
+ V_OK: { # done, quit with changes
+ rv := []
+ every e := !sel do put(rv, e[2].data)
+ break
+ }
+ V_CANCEL: { # cancel changes, quit.
+ break
+ }
+ }
+ newfocus := self.F.focus
+ } # end repeat
+
+## close temporary window after saving its location for next time
+ (\posn).x := WAttrib(self.win, "posx")
+ (\posn).y := WAttrib(self.win, "posy")
+ WClose(self.win)
+
+## restore window fields
+ self.win := parent
+ every v := !self.draw do {
+ v.win := self.win
+ if type(v) == ("Vradio_frame_rec" | "Vscrollbar_frame_rec") then
+ every (!v.draw).win := self.win
+ }
+
+## flush pending events that may have accumulated on the parent window
+ while *Pending(self.win) > 0 do
+ Event(self.win)
+
+## For Vtext vidgies, tell them to turn off their cursors.
+ every tid := !self.F.text_entries do
+ \(self.F.entries[tid]).T.CursorOn := &null
+
+ return \rv
+end
+
+procedure switch_focus_Vdialog(self, newfocus)
+ if (newfocus.id === !self.F.text_entries) then {
+ self.F.focus.T.unblock(self.F.focus)
+# self.F.focus.T.erase_cursor(self.F.focus)
+ newfocus.T.block(newfocus)
+ self.F.focus := newfocus
+ }
+end
+
+procedure insert_Vdialog(self, vidget, x, y)
+ if /self | /vidget | /x | /y then
+ _Vbomb("incomplete or &null parameters to VInsert() for dialogs")
+ pad_and_send_Vdialog(self, vidget, x, y)
+end
+
+procedure register_Vdialog(self, vidget, x, y)
+ static type
+
+ initial type := proc("type", 0) # protect attractive name
+
+ if /self | /vidget | /x | /y then
+ _Vbomb("incomplete or &null parameters to VRegister()")
+ self.F.entries[vidget.id] := vidget
+ if type(vidget) ? find("text") then
+ put(self.F.text_entries, vidget.id)
+ pad_and_send_Vdialog(self, vidget, x, y)
+end
+
+procedure unregister_Vdialog(self, kid)
+local new, i
+
+ if (kid.id === !self.F.text_entries) then {
+ new := []
+ every i := !self.F.text_entries do if kid.id ~=== i then put(new, i)
+ self.F.text_entries := new
+ }
+ delete(self.F.entries, kid.id)
+ every i := 1 to *self.F.text_entries do
+ self.F.text_lu[self.F.text_entries[i]] := i
+ self.V.remove(self, kid, 1)
+end
+
+procedure pad_and_send_Vdialog(self, vidget, x, y)
+ static type
+
+ initial type := proc("type", 0) # protect attractive name
+
+ if (x|y) < 0 | type(x|y) == "real" then
+ _Vbomb("must VRegister() or VInsert() a vidget to a dialog with absolute coordinates")
+ insert_Vframe(self, vidget, x+self.padx, y+self.pady)
+end
+
+procedure format_Vdialog(self)
+ self.V.resize(self, 0, 0,
+ Vmin_frame_width(self)+self.padx-1,
+ Vmin_frame_height(self)+self.pady-1)
+end
+
+procedure init_Vdialog(self)
+ init_Vframe(self)
+ /self.padx := 20
+ /self.pady := 20
+ self.F.entries := table()
+ self.F.text_entries := []
+ self.F.text_lu := table()
+end
diff --git a/ipl/gprocs/vfilter.icn b/ipl/gprocs/vfilter.icn
new file mode 100644
index 0000000..a79d1ae
--- /dev/null
+++ b/ipl/gprocs/vfilter.icn
@@ -0,0 +1,40 @@
+############################################################################
+#
+# File: vfilter.icn
+#
+# Subject: Procedure to change filter mode in sliders and scrollbars
+#
+# Author: Ralph E. Griswold
+#
+# Date: March 3, 1998
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# VSetFilter(vidget, value) sets the appropriate field in the structure for
+# vidget to change the filtering mode (null for no filtering, "1" for
+# filtering).
+#
+############################################################################
+#
+# Requires: Version 9 graphics
+#
+############################################################################
+
+procedure VSetFilter(vidget, value) #: filter mode of slider/scrollbar
+ local t
+
+ t := type(vidget)
+
+ case t of {
+ "Vscrollbar_frame_rec" : vidget.callback.callers[2].discont := value
+ "Vslider_rec" : vidget.discont := value
+ default : stop("*** invalid type to VSetFilter: ", t)
+ }
+
+ return
+
+end
diff --git a/ipl/gprocs/vframe.icn b/ipl/gprocs/vframe.icn
new file mode 100644
index 0000000..0ac2a1a
--- /dev/null
+++ b/ipl/gprocs/vframe.icn
@@ -0,0 +1,355 @@
+############################################################################
+#
+# File: vframe.icn
+#
+# Subject: Procedures for pane frame vidgets
+#
+# Author: Jon Lipp
+#
+# Date: May 2, 2001
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# Vidgets defined in this file:
+#
+# Vframe
+# Vroot_frame
+#
+# Utility procedures in this file:
+#
+# Vmin_frame_width()
+# Vmin_frame_height()
+#
+############################################################################
+#
+# Requires: Version 9 graphics
+#
+############################################################################
+#
+# Links: vidgets
+#
+############################################################################
+
+link vidgets
+
+############################################################################
+# frame vidget -
+# Keeps track of panes. Frames can contain
+# sub-frames in a hierarchy. Frames know their own absolute
+# coordinates and the relative sizes and positions of their
+# children (panes and sub-frames). They determine positioning
+# and size of each child, and route events.
+############################################################################
+
+record Vframe_rec(win, aw, ah, callback, id, lookup, draw, ax, ay,
+ uid, P, F, V)
+
+#
+# Creation procedure for a Vframe.
+# Specify its "own" utility procedures (V field).
+# Specify "special" procedures (format, in F field).
+# Get a unique id (uid).
+# check implicit insertion, insert if necessary.
+#
+procedure Vframe(params[])
+ local self, procs, spec_procs, frame, x, y, ins
+
+ procs := Vstd(event_Vframe, draw_Vframe, outline_Vidget,
+ resize_Vframe, inrange_Vpane, init_Vframe,
+ couplerset_Vpane, insert_Vframe, remove_Vframe,
+ lookup_Vframe, set_abs_Vframe)
+ spec_procs := Vstd_dialog( , , format_Vframe)
+
+ if ins := Vinsert_check(params) then {
+ frame := pop(params); x := pop(params); y:= pop(params)
+ }
+ self := Vframe_rec ! params[1:6|0]
+ Vwin_check(self.win, "Vframe()")
+ if (\self.aw, not numeric(self.aw) ) then
+ _Vbomb("invalid aw parameter to Vframe()")
+ if (\self.ah, not numeric(self.ah) ) then
+ _Vbomb("invalid ah parameter to Vframe()")
+
+ self.uid := Vget_uid()
+ self.V := procs
+ self.F := spec_procs
+ self.P := Vstd_pos()
+ self.V.init(self)
+ if \ins then VInsert(frame, self, x, y)
+ return self
+end
+
+#
+# Initialize procedure for Vframe. Other frame types call this.
+#
+procedure init_Vframe(s)
+ s.lookup := []
+ s.draw := []
+end
+
+#
+# draw the contents of the frame.
+#
+procedure draw_Vframe(s, erased)
+local p
+
+# PMIcon: fixed bug; drawig before resize.
+ if /s.aw | /s.ah then _Vbomb("frame not resized yet")
+ /erased & EraseArea(s.win, s.ax, s.ay, s.aw, s.ah)
+ every p := !s.draw do p.V.draw(p, "erased")
+ s.V.outline(s)
+end
+
+#
+# Set the absolute coordinates of everything on the draw list;
+# Don't do it for Vline, it is special.
+# It used to be that if the vidget is a Vpane,
+# a resize event was sent, so that it would notify its callback.
+# That "feature" has been commented out in the code below.
+#
+procedure resize_Vframe(s, x,y,wid,h)
+ local w, slots
+ static type
+
+ initial type := proc("type", 0) # protect attractive name
+
+ resize_Vidget(s, x, y, wid, h)
+ every w := !s.draw do {
+ if (type(w) == "Vline_rec") then
+ w.V.resize(s, w)
+ else s.V.set_abs(s, w)
+# if type(w) == "Vpane_rec" then
+# w.V.event(w, -10)
+ }
+end
+#
+# Determine the absolute coordinates of a vdiget based on its parent
+# frame's absolute coordinates, and the "virtual" coordinates passed
+# in upon creation.
+# Allows for the fact that a pane can have relative
+# position and size constraints intertwined with absolute.
+#
+procedure set_abs_Vframe(s, vid)
+local ax,ay,aw,ah, a, b, w, h, vx, vy, vw, vh
+ static type
+
+ initial type := proc("type", 0) # protect attractive name
+
+ w := s.aw; h := s.ah
+ vx := vid.P.x; vy := vid.P.y
+ vw := vid.P.w; vh := vid.P.h
+
+ ax := s.ax + ( (vx <= -1, w + vx - (\vid.aw | 0)) |
+ (type(vx) == "real",
+ (-1 <= vx < 0, w - vx*w) |
+ (0 < vx <= 1, vx*w) ) | vx )
+ ay := s.ay + ( (vy <= -1, h + vy - (\vid.ah | 0)) |
+ (type(vy) == "real",
+ (-1 <= vy < 0, h - vy*h) |
+ (0 < vy <= 1, vy*h) ) | vy )
+
+ aw := (\vw, (type(vw) == "real", 0 < vw <= 1, vw*w) |
+ vw) | \vid.aw | w
+ ah := (\vh, (type(vh) == "real", 0 < vh <= 1, vh*h) |
+ vh) | \vid.ah | h
+ aw := integer(aw)
+ ah := integer(ah)
+
+## don't let kid be bigger than the frame.
+ if (a := aw + ax) > (b := s.aw + s.ax) then aw -:= (a-b)
+ if (a := ah + ay) > (b := s.ah + s.ay) then ah -:= (a-b)
+ vid.V.resize(vid, ax, ay, aw, ah)
+end
+
+#
+# Don't erase the vidget if erase is non-&null.
+#
+procedure remove_Vframe(s, pane, erase)
+local new, k
+
+ new := []
+ every k := !s.lookup do if k ~=== pane then put(new,k)
+ s.lookup := new
+ new := []
+ every k := !s.draw do if k ~=== pane then put(new,k)
+ s.draw := new
+
+ if /erase then VErase(pane)
+end
+
+#
+# Insert a vidget into a frame.
+#
+procedure insert_Vframe(s, pane, x, y, w, h)
+local wc
+static image
+
+ initial image := proc("image", 0) # protet attractive name
+
+#defaults
+ /x := 0
+ /y := 0
+ /w := \pane.aw
+ /h := \pane.ah
+ pane.P.x := x
+ pane.P.y := y
+ pane.P.w := w
+ pane.P.h := h
+ put(s.draw, pane)
+ if not (image(pane.V.event) ? find("null_proc") ) then
+ put(s.lookup, pane)
+ if (\s.ax, \s.ay, \s.aw, s.ah) then { # is this frame sized yet
+ if (type(pane) == "Vline_rec") then
+ pane.V.resize(s, pane)
+ else
+ s.V.set_abs(s, pane)
+ }
+end
+
+#
+# Get events, lookup vidget based on (x, y), call its event loop.
+#
+procedure event_Vframe(s, e, x, y)
+local dest
+
+ if dest := s.V.lookup(s, x, y) then {
+ return dest.V.event(dest, e, x, y)
+ }
+end
+
+#
+# For every vidget on lookup list, check if (x, y) lie within its
+# boundaries. Doesn't address overlapping vidgets.
+#
+procedure lookup_Vframe(s, x, y)
+local w
+
+ every w := !s.lookup do
+ if w.V.inrange(w, x, y) then
+ return w
+end
+
+#
+# Determine and set the minimum bounding rectangle which encompasses
+# all vidgets within the frame. Restriction is that all vidgies must have
+# been inserted with absolute coordinates and sizes.
+#
+procedure format_Vframe(self)
+ resize_Vidget(self, , , Vmin_frame_width(self), Vmin_frame_height(self))
+end
+
+
+############################################################################
+# Vroot_frame -
+# Root of the X-Idol event window demultiplexing recordes.
+# The root_frame record serves as the root for windows that are
+# subdivided.
+############################################################################
+
+procedure Vroot_frame(params[])
+ local self
+ static procs, spec_procs
+
+ initial {
+ procs := Vstd(event_Vroot_frame, draw_Vframe, null_proc,
+ resize_Vroot_frame, inrange_Vpane, init_Vroot_frame,
+ couplerset_Vpane, insert_Vframe, remove_Vframe,
+ lookup_Vframe, set_abs_Vframe)
+ spec_procs := Vstd_dialog( , , format_Vframe)
+
+ VInit()
+ }
+
+ self := Vframe_rec ! params[1:2|0]
+ Vwin_check(self.win, "Vroot_frame()")
+ self.uid := Vget_uid()
+ self.V := procs
+ self.F := spec_procs
+ self.P := Vstd_pos()
+ self.V.init(self)
+ return self
+end
+
+procedure init_Vroot_frame(s)
+ s.ax := s.ay := 0
+ init_Vframe(s)
+end
+
+#
+# Process events (same as for a frame). Difference, is if we get a resize,
+# resize all vidgets within, and redraw screen (no lookup performed).
+#
+procedure event_Vroot_frame(s,e,x,y)
+local dest
+
+ if e === &resize then {
+ s.V.resize(s)
+ return &null
+ }
+ else {
+ if dest:= s.V.lookup(s,x,y) then
+ return dest.V.event(dest,e,x,y)
+ else fail
+ }
+end
+
+#
+# The window was resized! Well... reconfigure all the absolute
+# position and sizes for all panes. This benefits relative values
+# the most.
+#
+procedure resize_Vroot_frame(s)
+
+ s.aw := WAttrib(s.win, "width")
+ s.ah := WAttrib(s.win, "height")
+ resize_Vframe(s, s.ax, s.ay, s.aw, s.ah)
+ s.V.draw(s)
+end
+
+############################################################################
+# Utility procedures for frames.
+############################################################################
+
+#
+# Min--- returns the minimum size of the frame that will encase all
+# children. NOTE - this can only be determined if all the children
+# were inserted with absolute co-ords and sizes. I.e. positive and
+# integral x, y, w, & h.
+#
+procedure Vmin_frame_width(s)
+ local max, vid
+ static type
+
+ initial type := proc("type", 0) # protect attractive name
+
+ max := 2
+ every vid := (!s.draw) do
+ if (type(vid) ~== "Vline_rec") then {
+ if type(vid.P.x) == "real" | type(vid.P.w) == "real" |
+ vid.P.x < 0 | vid.P.w < 0 then
+ _Vbomb("attempt to format a frame with non-absolute sized and positioned children")
+ max <:= (vid.P.x + vid.P.w )
+ }
+ return max
+end
+
+procedure Vmin_frame_height(s)
+ local max, vid
+ static type
+
+ initial type := proc("type", 0) # protect attractive name
+
+ max := 2
+ every vid := (!s.draw) do
+ if (type(vid) ~== "Vline_rec") then {
+ if type(vid.P.y) == "real" | type(vid.P.h) == "real" |
+ vid.P.y < 0 | vid.P.h < 0 then
+ _Vbomb("attempt to format a frame with non-absolute sized and positioned children")
+ max <:= (vid.P.y + vid.P.h )
+ }
+ return max
+end
diff --git a/ipl/gprocs/vgrid.icn b/ipl/gprocs/vgrid.icn
new file mode 100644
index 0000000..2bc2367
--- /dev/null
+++ b/ipl/gprocs/vgrid.icn
@@ -0,0 +1,143 @@
+############################################################################
+#
+# File: vgrid.icn
+#
+# Subject: Procedures for vidget grids
+#
+# Author: Jon Lipp
+#
+# Date: March 23, 1995
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# Links: vidgets
+#
+############################################################################
+
+link vidgets
+
+record Vgrid_rec(win, callback, id, aw, ah, rows, cols, Hpos, Vpos, hpad, vpad,
+ ax, ay, uid, P, V)
+
+procedure Vgrid(params[])
+ local self, i, frame, x, y, ins
+ static procs
+
+ initial procs := Vstd(event_Vgrid, draw_Vgrid, outline_Vidget,
+ resize_Vgrid, inrange_Vpane, init_Vgrid)
+
+ if ins := Vinsert_check(params) then {
+ frame := pop(params); x := pop(params); y:= pop(params)
+ }
+ self := Vgrid_rec ! params[1:8|0]
+ Vwin_check(self.win, "Vgrid()")
+ if (\self.aw, not numeric(self.aw) ) then
+ _Vbomb("invalid aw parameter to Vgrid()")
+ if (\self.ah, not numeric(self.ah) ) then
+ _Vbomb("invalid ah parameter to Vgrid()")
+ if (\self.rows, not numeric(self.rows) ) then
+ _Vbomb("invalid rows parameter to Vgrid()")
+ if (\self.cols, not numeric(self.cols) ) then
+ _Vbomb("invalid cols parameter to Vgrid()")
+
+ self.V := procs
+ self.P := Vstd_pos()
+ self.uid := Vget_uid()
+
+ self.V.init(self)
+ if \ins then VInsert(frame, self, x, y)
+ return self
+end
+
+procedure init_Vgrid(self)
+ local p
+
+ self.Hpos := table()
+ self.Vpos := table()
+ /self.aw := 100
+ /self.ah := 100
+ /self.rows := 10
+ /self.cols := 10
+
+ p := \self.callback
+ self.callback := Vcoupler()
+ add_clients_Vinit(self.callback, p, self)
+ return self
+end
+
+procedure draw_Vgrid(self)
+ local i
+
+ # draw vertical lines
+ every i := 0 to self.cols do
+ DrawLine(self.win, self.ax+self.Hpos[i], self.ay,
+ self.ax+self.Hpos[i], self.ay+self.ah)
+
+ # draw horizontal lines.
+ every i := 0 to self.rows do
+ DrawLine(self.win, self.ax, self.ay+self.Vpos[i],
+ self.ax+self.aw, self.ay+self.Vpos[i])
+end
+
+procedure event_Vgrid(self, e)
+ local row, col
+
+ if \self.callback.locked then fail
+ col := VGetCol(self, &x)
+ row := VGetRow(self, &y)
+ return self.callback.V.set(self.callback, self, [row, col, e])
+end
+
+procedure resize_Vgrid(self, x, y, w, h)
+ local i
+
+ resize_Vidget(self, x, y, w, h)
+
+ self.hpad := 1 <= self.aw / real(self.cols) | 1
+ self.vpad := 1 <= self.ah / real(self.rows) | 1
+
+ every i := 0 to self.cols do
+ self.Hpos[i] := integer (i * self.hpad )
+
+ every i := 0 to self.rows do
+ self.Vpos[i] := integer(i * self.vpad )
+end
+
+procedure VFillGrid(self, row, col)
+
+ FillRectangle(self.win, self.ax+self.Hpos[col], self.ay+self.Vpos[row],
+ 1 <= self.Hpos[col+1] - self.Hpos[col] | 1,
+ 1 <= self.Vpos[row+1] - self.Vpos[row] | 1 )
+end
+
+procedure check_Vgrid(self, row, col)
+
+end
+
+procedure VEraseGrid(self, row, col)
+
+ EraseArea(self.win, self.ax+self.Hpos[col]+1, self.ay+self.Vpos[row]+1,
+ 1 <= ( self.Hpos[col+1] - self.Hpos[col] - 1) | 1,
+ 1 <= ( self.Vpos[row+1] - self.Vpos[row] - 1) | 1 )
+end
+
+procedure VGetRow(self, y)
+ local row
+
+ row := integer( (y - self.ay) / real(self.vpad) )
+ row := row < 0 | row > self.rows - 1
+ return row
+end
+
+procedure VGetCol(self, x)
+ local col
+
+ col := integer( (x - self.ax) / real(self.hpad) )
+ col := col < 0 | col > self.cols - 1
+ return col
+end
+
diff --git a/ipl/gprocs/vidgets.icn b/ipl/gprocs/vidgets.icn
new file mode 100644
index 0000000..59819c3
--- /dev/null
+++ b/ipl/gprocs/vidgets.icn
@@ -0,0 +1,28 @@
+############################################################################
+#
+# File: vidgets.icn
+#
+# Subject: Procedures for vidgets
+#
+# Author: Jon Lipp
+#
+# Date: September 17, 1997
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# Links to basic vidget files needed to use the library.
+#
+############################################################################
+
+link graphics
+link vcoupler
+link vframe
+link viface
+link vlist
+link vmenu
+link vpane
+link vstd
diff --git a/ipl/gprocs/viface.icn b/ipl/gprocs/viface.icn
new file mode 100644
index 0000000..6047cc7
--- /dev/null
+++ b/ipl/gprocs/viface.icn
@@ -0,0 +1,421 @@
+############################################################################
+#
+# File: viface.icn
+#
+# Subject: Procedures for interfacing vidgets
+#
+# Authors: Jon Lipp and Gregg M. Townsend
+#
+# Date: April 1, 1997
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# Utility procedures in this file:
+# VDraw()
+# VErase()
+# VOutline()
+# VResize()
+# VRemove()
+# VInsert()
+# VEvent()
+# VRegister()
+# VUnregister()
+# VOpenDialog()
+# VFormat()
+# VAddClient()
+# VToggle()
+# VUnSet()
+# VSetState() [formerly SetVidget() and VSet()]
+# VGetState()
+# VSetItems()
+# VGetItems()
+# ProcessEvent()
+# GetEvents()
+# VEcho()
+# VSetFont()
+#
+############################################################################
+#
+# Includes: vdefns
+#
+############################################################################
+#
+# Links: vidgets
+#
+############################################################################
+
+link vidgets
+
+$include "vdefns.icn"
+
+procedure VDraw(vid, code)
+ static type
+
+ initial type := proc("type", 0) # protect attractive name
+ if not (type(vid) == !Vrecset ) then
+ _Vbomb("invalid vidget parameter to VDraw()")
+
+ vid.V.draw(vid, code)
+end
+
+procedure VErase(vid)
+ static type
+
+ initial type := proc("type", 0) # protect attractive name
+ if not (type(vid) == !Vrecset) then
+ _Vbomb("invalid vidget parameter to VErase()")
+ if type(vid) == "Vline_rec" then
+ erase_Vline(vid)
+ else
+ EraseArea(vid.win, vid.ax, vid.ay, vid.aw, vid.ah)
+end
+
+procedure VOutline(vid)
+ static type
+
+ initial type := proc("type", 0) # protect attractive name
+ if not (type(vid) == !Vrecset ) then
+ _Vbomb("invalid vidget parameter to VOutline()")
+
+ vid.V.outline(vid)
+end
+
+procedure VResize(vid, x, y, w, h)
+ static type
+
+ initial type := proc("type", 0) # protect attractive name
+ if not (type(vid) == !Vrecset ) then
+ _Vbomb("invalid vidget parameter to VResize()")
+ if type(vid) == "Vline_rec" then {
+ vid.ax1 := \x
+ vid.ay1 := \y
+ vid.ax2 := vid.ax1 + \w
+ vid.ay2 := vid.ay1 + \h
+ }
+ else {
+ vid.ax := \x
+ vid.ay := \y
+ vid.aw := \w
+ vid.ah := \h
+ }
+ vid.V.resize(vid)
+end
+
+procedure VRemove(frame, vid, erase)
+ if not (type(frame) ? find("frame") ) then
+ _Vbomb("invalid frame parameter to VRemove()")
+ else if not (type(vid) == !Vrecset ) then
+ _Vbomb("invalid vidget parameter to VRemove()")
+
+ frame.V.remove(frame, vid, erase)
+end
+
+procedure VInsert(frame, vid, x, y, w, h)
+ static image
+
+ initial image := proc("image", 0) # protect attractive name
+
+ if not (type(frame) ? find("frame") ) then
+ _Vbomb("invalid frame parameter to VInsert()")
+ else if not (type(vid) == !Vrecset ) then
+ _Vbomb("invalid vidget parameter to VInsert(): " || image(vid))
+ else if (\x, not numeric(x) ) then
+ _Vbomb("non-numeric x parameter to VInsert()")
+ else if (\y, not numeric(y) ) then
+ _Vbomb("non-numeric y parameter to VInsert()")
+ else if (\w, not numeric(w) ) then
+ _Vbomb("non-numeric w parameter to VInsert()")
+ else if (\h, not numeric(h) ) then
+ _Vbomb("non-numeric y parameter to VInsert()")
+ frame.V.insert(frame, vid, x, y, w, h)
+end
+
+procedure VEvent(vid, e, x, y)
+ if not (type(vid) == !Vrecset ) then
+ _Vbomb("invalid vidget parameter to VEvent()")
+
+ return vid.V.event(vid, e, x, y)
+end
+
+############################################################################
+# The following two procedure are only for use with dialog box frames
+# and menu_frames.
+#
+# VRegister is analogous to VInsert, except, it tells the dialog box that
+# this is an editable field.
+############################################################################
+procedure VRegister(dialog, vid, x, y, w, h)
+ if not (type(dialog) ? find("dialog_frame") ) then
+ _Vbomb("invalid dialog parameter to VRegister()")
+ else if not (type(vid) == !Vrecset ) then
+ _Vbomb("invalid vidget parameter to VRegister()")
+ else if (\x, not numeric(x) ) then
+ _Vbomb("Non-numeric x parameter to VRegister()")
+ else if (\y, not numeric(y) ) then
+ _Vbomb("Non-numeric y parameter to VRegister()")
+ else if (\w, not numeric(w) ) then
+ _Vbomb("Non-numeric w parameter to VRegister()")
+ else if (\h, not numeric(h) ) then
+ _Vbomb("Non-numeric y parameter to VRegister()")
+
+ dialog.F.register(dialog, vid, x, y, w, h)
+end
+
+procedure VUnregister(dialog, vid)
+ if not (type(dialog) ? find("dialog_frame") ) then
+ _Vbomb("invalid dialog parameter to VUnregister()")
+ else if not (type(vid) == !Vrecset ) then
+ _Vbomb("invalid vidget parameter to VUnregister()")
+
+ dialog.F.unregister(dialog, vid)
+end
+
+#
+# Vopen_dialog
+# Opens a dialog for input. Returns the list of new objects, or the
+# original data if "cancel" was picked.
+#
+# open a dialog box at (x, y); dialog contains a record of type
+# 'dialog', data is a list of initial values corresponding to the
+# objects "registered" with the dialog; default_string is the label
+# of the control button to press upon hitting a return.
+#
+# If x is null and y is not, y is an "ID" for the dialog box, which
+# opens at the default location but can be moved by the user. The
+# location is remembered and applied to subsequent opens.
+#
+procedure VOpenDialog(dialog, x, y, data, default_string)
+ if not (type(dialog) ? find("dialog_frame") ) then
+ _Vbomb("invalid dialog parameter to VOpenDialog()")
+ if \x & not (numeric(x) & numeric(y)) then
+ _Vbomb("invalid x or y parameter passed to VOpenDialog()")
+ /data := []
+ return \(dialog.F.open_dialog(dialog, x, y, data, default_string)) | data
+end
+
+
+#
+# VFormat resizes the frame, and figures out the width and height
+# automatically, contingent on all vidgets being inserted or registered
+# with absolute coordinates.
+#
+procedure VFormat(frame)
+ if not (type(frame) ? find("frame") ) then
+ _Vbomb("invalid frame parameter to VFormat()")
+
+ frame["F"].format(frame)
+end
+
+############################################################################
+# The following procedure is only for use with couplers.
+############################################################################
+
+procedure VAddClient(coupler, client, caller)
+ if not (type(coupler) ? find("coupler") ) then
+ _Vbomb("invalid coupler parameter to VAddClient()")
+
+ coupler.V.add_client(coupler, client, caller)
+end
+
+procedure VToggle(coupler)
+ if not (type(coupler) ? find("coupler") ) then
+ _Vbomb("invalid coupler parameter to VToggle()")
+
+ coupler.V.toggle(coupler)
+end
+
+procedure VUnSet(coupler)
+ if not (type(coupler) ? find("coupler") ) then
+ _Vbomb("invalid coupler parameter to VUnSet()")
+
+ coupler.V.unset(coupler)
+end
+
+procedure VLock(coupler)
+ if not (type(coupler) ? find("coupler") ) then
+ _Vbomb("invalid coupler parameter to VLock()")
+
+ coupler.locked := 1
+end
+
+procedure VUnLock(coupler)
+ if not (type(coupler) ? find("coupler") ) then
+ _Vbomb("invalid coupler parameter to VUnLock()")
+
+ coupler.locked := &null
+end
+
+############################################################################
+# VSetState sets the vidget | coupler to the value.
+############################################################################
+procedure VSetState(vid, val, code)
+ if type(vid) ? find("coupler") then
+ return (\(\vid).V.set)(vid, , val, code)
+ else if type(vid) == !Vrecset then
+ return (\(\vid).V.set_value)(vid, val, code)
+ else
+ _Vbomb("invalid vidget parameter to VSetState()")
+end
+
+procedure SetVidget(vid, val, code) # old name
+ SetVidget := VSetState
+ return VSetState(vid, val, code)
+end
+
+procedure VSet(vid, val, code) # older name
+ VSet := VSetState
+ return VSetState(vid, val, code)
+end
+
+############################################################################
+# VGetState returns the value of the vidget state.
+############################################################################
+procedure VGetState(vid)
+ if type(vid) ? find("scroll" | "slide" | "radio" | "text") then
+ return (\vid.callback).value
+ else if vid.V.set_value === set_value_Vlist then # list vidget
+ return get_value_Vlist(vid)
+ else if type(vid) == "Vbutton_rec" &
+ (vid.V.event === event_Vtoggle) then return(\vid.callback).value
+ else
+ fail
+end
+
+############################################################################
+# VSetItems sets the items displayed by a list vidget.
+############################################################################
+procedure VSetItems(vid, val)
+ if vid.V.set_value === set_value_Vlist then # list vidget
+ return set_items_Vlist(vid, val)
+ else if type(vid) == "Vframe_rec" & type(vid.lookup) == "list" &
+ type(vid.lookup[1]) == "Vmenu_item_rec" then
+ return Vmenu_set_items(vid, val)
+ else
+ fail
+end
+
+############################################################################
+# VGetItems returns the items displayed by a list vidget.
+############################################################################
+procedure VGetItems(vid)
+ if vid.V.set_value === set_value_Vlist then # list vidget
+ return get_items_Vlist(vid)
+ else if type(vid) == "Vframe_rec" & type(vid.lookup) == "list" &
+ type(vid.lookup[1]) == "Vmenu_item_rec" then
+ return Vmenu_get_items(vid)
+ else
+ fail
+end
+
+
+############################################################################
+# Event handlers.
+############################################################################
+
+procedure GetEvents(vidget, missed, all, resize)
+ repeat ProcessEvent(vidget, missed, all, resize)
+end
+
+procedure ProcessEvent(vidget, missed, all, resize)
+ local event, lrv
+
+ type(vidget) ? {
+ if not find("frame")
+ then _Vbomb("invalid frame argument to ProcessEvent()")
+ }
+
+ event := Event(vidget.win)
+
+ if event === &resize then {
+ (\resize)(vidget, event, &x, &y)
+ VEvent(vidget, event, &x, &y)
+ }
+
+ (\(lrv := vidget.V.lookup(vidget,&x,&y)) & lrv.V.event(lrv,event,&x,&y)) |
+ (\missed)(event, &x, &y)
+
+ (\all)(event, &x, &y)
+
+ return event
+
+end
+
+
+############################################################################
+# VEcho(v, x) -- echoing callback routine
+#
+# VEcho can be used as the default callback routine passed to vsetup.
+# It just prints a message on standard output giving the value of x.
+############################################################################
+
+procedure vecho(v, x) # old name
+ vecho := VEcho
+ return VEcho(v, x)
+end
+
+procedure VEcho(v, x)
+ static image
+
+ initial image := proc("image", 0) # protect attractive name
+
+ writes("callback: id=", v.id, ", value=")
+ if type(x) == "list" then {
+ writes("[")
+ writes(image(x[1]))
+ every writes(",", image(x[2 to *x]))
+ writes("]")
+ }
+ else
+ writes(image(x))
+ write()
+ return
+end
+
+
+############################################################################
+# VSetFont(win) -- set vidget font in window.
+#
+# VSetFont tries to set a 7-pixel-wide font for use by VIB and vidgets.
+############################################################################
+
+procedure vsetfont(win) # old name
+ vsetfont := VSetFont
+ return VSetFont(win)
+end
+
+procedure VSetFont(win)
+ local spec, maybe
+
+ /win := &window
+ if WAttrib(win, "fwidth") = VFWidth then
+ return win # existing font is acceptable
+
+ every spec :=
+
+$ifdef _X_WINDOW_SYSTEM
+ "lucidasanstypewriter-bold-12" |
+ "-*-lucidatypewriter-bold-r-*-*-12-*-*-*-*-70-iso8859-1" |
+ "-*-lucidatypewriter-bold-r-*-*-*-*-*-*-*-70-iso8859-1" |
+ "-*-*-r-*-sans-*-*-*-*-m-70-iso8859-1" |
+ "-*-*-r-*-*-*-*-*-*-m-70-iso8859-1" |
+ "-*-*-r-*-*-*-*-*-*-c-70-iso8859-1"
+$else
+ ("mono,bold," | "mono," | "typewriter,") || (12 | 11 | 13 | 10 | 14)
+$endif
+
+ do {
+ Font(win, spec) | next # try a font
+ /maybe := spec # remember first success
+ if WAttrib(win, "fwidth") = VFWidth then
+ return win # this font is right size
+ }
+
+ # No font was the right size. Go back to the first one that was legal.
+ # If nothing works, return with the font unchanged.
+ Font(win, \maybe)
+ return win
+end
diff --git a/ipl/gprocs/vlist.icn b/ipl/gprocs/vlist.icn
new file mode 100644
index 0000000..d0820a9
--- /dev/null
+++ b/ipl/gprocs/vlist.icn
@@ -0,0 +1,964 @@
+############################################################################
+#
+# File: vlist.icn
+#
+# Subject: Procedures for a scrollable list vidget
+#
+# Author: Jason Peacock and Gregg Townsend
+#
+# Date: June 23, 2000
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# Vidgets defined in this file:
+#
+# Vlist
+#
+# Utility procedures in this file:
+#
+# storage_Vlist()
+# set_items_Vlist()
+# get_items_Vlist()
+# set_value_Vlist()
+# get_value_Vlist()
+# coupler_Vlist()
+# drawlist_Vlist()
+# Vframe_Vlist()
+# outline_listframe()
+# resize_listframe()
+# Vpane_Vlist()
+# event_Vlist()
+# vlist_selection()
+# outline_listpane()
+#
+############################################################################
+#
+# Includes: vdefns
+#
+############################################################################
+#
+# Requires: Version 9 graphics
+#
+############################################################################
+#
+# Links: graphics, vidgets, vscroll, vcoupler
+#
+############################################################################
+
+# DEFICIENCIES TO REMEDY LATER:
+#
+# don't clone two new windows every time the vidget is redrawn
+# don't insist on string-valued ID
+# toss out storage_Vlist()
+#
+# dragging fast can skip items
+
+
+$include "vdefns.icn"
+
+link graphics, vidgets, vscroll, vcoupler
+
+$define V_READONLY "r"
+$define V_SELECT "w"
+$define V_MULTISELECT "a"
+
+############################################################################
+#
+# list vidget -
+#
+# Creates a vidget that displays a list of strings within a region,
+# can be scrolled to view the entire list a section at a time, and
+# can call a callback procedure if an item(s) in the list is selected.
+#
+############################################################################
+
+
+
+############################################################################
+#
+# PROCEDURE
+# Vlist(frame, x, y, win, cb, id, dl, c, w, h, m)
+# vidget := Vlist(win, cb, id, dl, c, w, h, m)
+#
+# DESCRIPTION
+# Create a list vidget. A vlist is simply a square region
+# in which lines of text are displayed. Since the number of lines
+# to be displayed can exceed the number of lines the region can
+# display, a vertical scrollbar, set to the right of the region,
+# is used to allow the user to scroll the list through the region.
+#
+# It has been implemented by using a standard vframe vidget form
+# with a few callbacks altered since a vlist is not a normal
+# vframe. Into the frame are placed two vidgets: a vpane, and
+# a vvert_scrollbar. The scrollbar's callback is a coupler
+# variable that is used to link the scrollbar and the pane
+# together.
+#
+# INPUTS
+# frame - The frame the vlist will be inserted into
+# x - The x coordinate of the insertion
+# y - The y coordinate of the insertion
+#
+# The above three parameters are optional. If used, all three
+# parameters must be given.
+#
+# win - the window the vidget is created in
+# cb - the procedure that will serve as the callback
+# id - the id of the vidget
+# dl - the initial list (of strings) that will be displayed
+# c - Is 1 for discontinuous scroll or &null for continuous scrolling
+# w - the width of the vidget
+# h - the height of the vidget
+# m - how the list will be displayed
+#
+# These are the mode parameter values:
+#
+# V_READONLY - Instructs Vlist that the list will be a
+# display only. No lines can be highlighted.
+#
+# V_SELECT - Only one line can be highlighted at a time.
+# The callback is not not executed until the
+# mouse button is released.
+#
+# V_MULTISELECT - Several lines may be highlighted at once.
+# The callback is executed every time the
+# mouse button is released. A list of the
+# currently highlighted items is sent.
+#
+# OUTPUT
+# vidget - A Vframe_rec record containing the list vidget
+#
+# EXAMPLE
+# To create a vlist that will display the contents of the
+# list (of strings) variable, datalist, in a region measuring
+# 640 pixels across and 480 pixels high, allow no selection,
+# and have no callback procedure, make this call:
+#
+# lv := Vlist(win, , "lv_id", datalist, 1, 640, 480, V_READONLY)
+#
+# where win is the window variable and "lv_id" is the id value.
+#
+# BUGS
+# The are no defaults for the win, id, dl, x, and y parameters.
+
+procedure Vlist(params[])
+
+## ins - This flag is set if the vidget is to implicitly inserted
+## into a frame (that was also passed as a parameter).
+## self - The record containing the frame which is the list vidget
+## fh - The height of the font used in the list
+## viewport - The vpane vidget, to be inserted into 'self'
+## cv - The coupler variable
+## sb - The scrollbar vidget, to be inserted into 'self'
+## line - Temporary storage for each line in 'dl'
+## window_sz - The number of lines the list can display at a time
+
+local frame, x, y, win, cb, id, dl, c, w, h, mode
+local self, ins, fh, viewport, cv, sb, line, window_sz
+local datalist
+ static type
+
+ initial type := proc("type", 0) # protect attractive name
+
+## CHECK FOR IMPLICIT INSERT INTO GIVEN FRAME ##############################
+
+ if ins := Vinsert_check(params) then {
+ frame := pop(params); x := pop(params); y:= pop(params)
+ }
+
+## CHECK THE INPUT VALUES ##################################################
+
+ if type(params[1]) == "window" then win := pop(params)
+ else _Vbomb("improper window parameter given to Vlist")
+ if type(params[1]) == ("procedure" | "null") then cb := pop(params)
+ else _Vbomb("improper callback parameter given to Vlist")
+ id := pop(params)
+ if type(params[1]) == ("list" | "null") then dl := pop(params)
+ else _Vbomb("improper list parameter given to Vlist")
+ if type(params[1]) == ("integer" | "null") then c := pop(params)
+ else _Vbomb("improper scrollmode parameter given to Vlist")
+
+ if \params[1] & \params[2] then {
+ w := pop(params); h := pop(params)
+ }
+ else _Vbomb("improper width and height values given to Vlist")
+
+ case \params[1] of {
+ V_READONLY | V_SELECT | V_MULTISELECT :
+ mode := pop(params)
+ default :
+ _Vbomb("improper mode parameter given to Vlist")
+ }
+
+ /mode := V_SELECT ## DEFAULT SELECT MODE IS SELECT ONE LINE ONLY
+ /dl := [] ## DEFAULT LIST IS EMPTY LIST
+
+
+## CREATE THE VLIST ########################################################
+
+ self := Vframe_Vlist(win)
+ self.id := id
+
+ storage_Vlist(id, "write", "mode", mode)
+
+ viewport := Vpane_Vlist(win, cb, id, "sunken", w - VSlider_DefWidth - 2, h)
+ VInsert(self, viewport, 0, 0)
+
+ fh := WAttrib(viewport.win, "fheight")
+ window_sz := integer((h - 4) / fh) - 1
+
+ cv := Vcoupler()
+ VAddClient(cv, coupler_Vlist, viewport)
+
+ sb := Vvert_scrollbar(win, cv, id,
+ h, VSlider_DefWidth, *dl, 1, 1, window_sz, c)
+
+ VInsert(self, sb, w - VSlider_DefWidth, 0)
+ VFormat(self)
+
+ datalist := []
+ every line := !dl do put(datalist, "N" || line)
+
+ storage_Vlist(id, "write", "datalist", datalist)
+ storage_Vlist(id, "write", "top_line", 1)
+ storage_Vlist(id, "write", "selection", &null)
+ storage_Vlist(id, "write", "continuous", c)
+
+ if \ins then VInsert(frame, self, x, y)
+ return self
+
+end
+
+
+############################################################################
+#
+# PROCEDURE
+# storage_Vlist(id, op, var, val)
+# val := storage_Vlist(id, op, var)
+#
+# DESCRIPTION
+# Used to store variables that are needed but can't be stored
+# within a vframe_rec, vpane_rec, or vscrollbar_rec.
+#
+# This procedure performs its magic by keeping a static table
+# of data. Information is indexed by using the vlist's id
+# following by a "@" character and then the variable name as
+# the suffix.
+#
+# INPUTS
+# id - The id of the vlist doing the storing
+# op - Which operation? "write" or "read"? If "read", then the
+# val parameter is ignored.
+# var - The name to store the value under
+# val - The value to be stored
+#
+# OUTPUT
+# val - The value that was stored under the name var.
+#
+# EXAMPLES
+#
+# If there is a vlist with an id of "lv_1" and the list of
+# strings it is displaying is stored in variable "datalist", then
+# that list can be stored with this call:
+#
+# storage_Vlist("lv_1", "write", "datalist", datalist)
+#
+# To retrieve that information:
+#
+# datalist := storage_Vlist("lv_1", "read", "datalist")
+#
+# BUGS
+# Since the table is static, it is possible for newly created
+# vlists to "remember" the data from older vlists if
+# the both and the new and old vlist have the same id.
+#
+# This procedure requires that the vidget ID be a string,
+# an additional restriction not usually imposed.
+
+procedure storage_Vlist(id, op, var, val)
+local k
+static var_table
+
+initial var_table := table()
+
+ k := id || "@" || var
+
+ case op of {
+ "read" : return var_table[k]
+ "write" : var_table[k] := val
+ }
+
+ return
+
+end
+
+
+############################################################################
+#
+# PROCEDURE
+# set_items_Vlist(self, slist)
+#
+# DESCRIPTION
+# Set list of displayed lines.
+# State is reset to no lines selected, scrolling at top.
+#
+# INPUTS
+# self - the vidget record
+# slist - list of strings
+
+procedure set_items_Vlist(self, slist)
+ local dl, tl, lv, sb, cv, c, s, window_sz
+
+ # build new datalist
+ tl := 1
+ dl := []
+ every s := !slist do
+ put(dl, "N" || string(s)) |
+ _Vbomb("list entry for VSetItems() is not a string")
+
+ # replace datalist and reset top_line
+ lv := self.lookup[1]
+ storage_Vlist(lv.id, "write", "datalist", dl)
+ storage_Vlist(lv.id, "write", "top_line", tl)
+ storage_Vlist(lv.id, "write", "selection", 0)
+
+ # replace scrollbar with a new one
+ sb := self.lookup[2]
+ VRemove(self, sb)
+ cv := Vcoupler()
+ c := storage_Vlist(lv.id, "read", "continuous")
+ VAddClient(cv, coupler_Vlist, lv)
+ window_sz := integer(lv.uh / WAttrib(lv.win, "fheight")) - 1
+ sb := Vvert_scrollbar(self.win, cv, self.id, self.ah, VSlider_DefWidth,
+ *dl, 1, 1, window_sz, c)
+ VInsert(self, sb, self.aw - VSlider_DefWidth, 0)
+ VFormat(self)
+
+ # redraw everything
+ VDraw(self)
+ return
+end
+
+
+############################################################################
+#
+# PROCEDURE
+# get_items_Vlist(self)
+#
+# DESCRIPTION
+# Returns the list of displayed lines.
+#
+# INPUT
+# self - the vidget record
+#
+# OUTPUT
+# items - list of strings
+
+procedure get_items_Vlist(self)
+ local lv, dl, items
+
+ lv := self.lookup[1]
+ dl := storage_Vlist(lv.id, "read", "datalist")
+ items := []
+ every put(items, (!dl)[2:0])
+ return items
+end
+
+
+############################################################################
+#
+# PROCEDURE
+# set_value_Vlist(self, state)
+#
+# DESCRIPTION
+# This procedure sets the state of the vidget.
+#
+# INPUT
+# self - the vidget record
+# state - a list of integers:
+# the first integer gives the index of the first viewable line
+# any addition integers are indices of selected lines
+
+procedure set_value_Vlist(self, state)
+ local c, i, lv, sb, dl, tl, mode, window_sz, iset, val
+
+## lv - the Vpane vidget of the vlist frame vidget
+## sb - the scrollbar vidget of the vlist frame vidget
+## dl - The list being displayed
+## tl - The line in the list which is at the top of the display
+
+ lv := self.lookup[1]
+ sb := self.lookup[2]
+ dl := storage_Vlist(lv.id, "read", "datalist")
+ mode := storage_Vlist(lv.id, "read", "mode")
+ window_sz := integer(lv.uh / WAttrib(lv.win, "fheight")) - 1
+
+ if type(state) ~== "list" then
+ state := [state]
+ tl := state[1] | &null
+ /tl := 1
+ tl := integer(tl) | _Vbomb("non-integer value in VSetState() of a list")
+ tl >:= *dl - window_sz
+ tl <:= 1
+ storage_Vlist(lv.id, "write", "top_line", tl)
+ VSetState(sb, tl)
+
+ if *state > 1 & mode === V_READONLY then
+ _Vbomb("VSetState() cannot select lines of read-only list")
+ else if *state > 2 & mode ~=== V_MULTISELECT then
+ _Vbomb("VSetState() cannot select multiple lines of this list")
+
+ val := list() # list of values for callback
+ iset := set() # make set of indices
+ every i := state[2 to *state] do
+ insert(iset, integer(i)) |
+ _Vbomb("non-integer value in VSetState() of a list")
+
+ every i := 1 to *dl do {
+ if member(iset, i) then { # S is selected, N is not
+ c := "S"
+ put(val, dl[i][2:0])
+ }
+ else
+ c := "N"
+ dl[i][1] ~==:= c
+ }
+
+ drawlist_Vlist(lv, dl, tl) # redraw vidget
+
+ case mode of { # invoke callback
+ V_SELECT: {
+ storage_Vlist(lv.id, "write", "selection", !iset | 0)
+ (\lv.callback)(self, val[1] | &null, !iset | 0)
+ }
+ V_MULTISELECT: {
+ (\lv.callback)(self, val, sort(iset))
+ }
+ }
+
+ return
+end
+
+
+############################################################################
+#
+# PROCEDURE
+# get_value_Vlist(self)
+#
+# DESCRIPTION
+# This procedure returns the state of the vidget.
+#
+# INPUT
+# self - the vidget record
+#
+# OUTPUT
+# state - a list of integers:
+# the first integer gives the index of the first viewable line
+# any addition integers are indices of selected lines
+
+procedure get_value_Vlist(self)
+ local i, lv, dl, tl, state
+
+## lv - the Vpane vidget of the vlist frame vidget
+## dl - The list being displayed
+## tl - The line in the list which is at the top of the display
+
+ lv := self.lookup[1]
+ dl := storage_Vlist(lv.id, "read", "datalist")
+ tl := storage_Vlist(lv.id, "read", "top_line")
+ state := [tl]
+ every i := 1 to *dl do
+ if dl[i] ? ="S" then
+ put(state, i)
+ return state
+end
+
+
+############################################################################
+#
+# PROCEDURE
+# coupler_Vlist(self, val)
+#
+# DESCRIPTION
+# This function is the callback used by the coupler which connects the
+# scrollbar to the pane. Whenever the scrollbar is moved, this function
+# gets called with the pane's record and the scrollbar's new value so
+# that the display can be updated appropriately.
+#
+# The scrollbar changes the current value of topline so the list must be
+# redisplayed with the new topline position in the list as the top line.
+#
+# INPUTS
+# self - the pane vidget which displays the list
+# val - the new value for topline
+
+procedure coupler_Vlist(self, val)
+local tl, dl, sl, dh, fh, fw
+
+ tl := storage_Vlist(self.id, "read", "top_line")
+ if tl === val then fail
+
+ dl := storage_Vlist(self.id, "read", "datalist")
+ fh := WAttrib(self.win, "fheight")
+ fw := WAttrib(self.win, "fwidth")
+
+ tl := val
+ storage_Vlist(self.id, "write", "top_line", tl)
+
+ drawlist_Vlist(self, dl, tl)
+
+ return
+
+end
+
+
+############################################################################
+#
+# PROCEDURE
+# drawlist_Vlist(pane, dl, tl)
+#
+# DESCRIPTION
+# Draw a list of strings within the specified region of the window
+#
+# INPUTS
+# pane - the pane vidget the strings are drawn in
+# dl - the list of strings
+# tl - the first line in the list to be drawn
+
+procedure drawlist_Vlist(pane, dl, tl)
+local win, x, y, w, h
+local fh, fw, ds, z, col, rev, non, mode, margin
+
+##
+## z - Serves as the counter through the list
+## col - The number of columns that can be displayed in the vpane
+## non - The normal draw mode
+## rev - Draw with "reverse=on"
+##
+
+ win := pane.win
+ x := pane.ux
+ y := pane.uy
+ w := pane.uw
+ h := pane.uh
+
+ fh := WAttrib(win, "fheight")
+ fw := WAttrib(win, "fwidth")
+ ds := WAttrib(win, "descent")
+
+ rev := Clone(win, "reverse=on", "clipx="||x, "clipy="||y,
+ "clipw="||w, "cliph="||h)
+ non := Clone(rev, "reverse=off")
+
+ case storage_Vlist(pane.id, "read", "mode") of {
+ V_READONLY: {
+ margin := 4
+ EraseArea(non, x, y, margin, h)
+ }
+ V_SELECT: {
+ margin := 8
+ EraseArea(non, x, y, margin, h)
+ DrawGroove(non, x + 4, y + 1, x + 4, y + h - 2)
+ }
+ V_MULTISELECT: {
+ margin := 12
+ EraseArea(non, x, y, margin, h)
+ DrawGroove(non, x + 4, y + 1, x + 4, y + h - 2)
+ DrawGroove(non, x + 8, y + 1, x + 8, y + h - 2)
+ }
+ }
+
+ z := tl
+ h +:= (y - fh)
+ y -:= ds
+ col := integer((w - 2) / fw)
+
+ while ((y < h) & (z <= *dl)) do
+ {
+ GotoXY(win, x + margin, y + fh)
+ if dl[z][1] == "S" then
+ WWrites(rev, left(dl[z][2:0], col))
+ else
+ WWrites(non, left(dl[z][2:0], col))
+
+ y +:= fh
+ z +:= 1
+ }
+
+ EraseArea(non, x + margin, y + ds)
+ return
+
+end
+
+
+############################################################################
+#
+# PROCEDURE
+# Vframe_Vlist([frame, x, y], win, aw, ah)
+#
+# DESCRIPTION
+# Creates the frame for the list vidget. The only differences
+# between this procedure and the normal Vframe() procedure is that the
+# outline_Vframe callback has been changed to outline_listframe() and
+# there is now a set_value_Vlist() procedure callback that can
+# respond to calls from VSetState().
+#
+# INPUTS
+# frame - (optional) the frame to insert this vidget in
+# x - (optional) the x coordinate to insert the vidget at
+# y - (optional) the y coordinate to insert the vidget at
+#
+# These three parameters listed above are optional. However, they must
+# all be present if you plan to use them.
+#
+# win - the window the vidget will appear in
+# aw - (optional) the width of the vidget
+# ah - (optional) the height of the vidget
+#
+# The aw and ah parameters are usually not given because they are
+# set later with a call to VFormat().
+#
+# OUTPUT
+# A frame vidget
+
+procedure Vframe_Vlist(params[])
+local self, procs, spec_procs, frame, x, y, ins
+
+ procs := Vstd(event_Vframe, draw_Vframe, outline_listframe,
+ resize_listframe, inrange_Vpane, init_Vframe,
+ couplerset_Vpane, insert_Vframe, remove_Vframe,
+ lookup_Vframe, set_abs_Vframe, set_value_Vlist)
+ spec_procs := Vstd_dialog(, , format_Vframe)
+
+ if ins := Vinsert_check(params) then {
+ frame := pop(params); x := pop(params); y:= pop(params)
+ }
+ self := Vframe_rec ! params[1:6|0]
+ Vwin_check(self.win, "Vframe()")
+ if (\self.aw, not numeric(self.aw)) then
+ _Vbomb("invalid aw parameter to Vframe()")
+ if (\self.ah, not numeric(self.ah)) then
+ _Vbomb("invalid ah parameter to Vframe()")
+
+ self.uid := Vget_uid()
+ self.V := procs
+ self.F := spec_procs
+ self.P := Vstd_pos()
+ self.V.init(self)
+ if \ins then VInsert(frame, self, x, y)
+ return self
+end
+
+
+############################################################################
+#
+# PROCEDURE
+# outline_listframe()
+#
+# DESCRIPTION
+# This is a dummy function to prevent the list frame from drawing
+# any kind of a border around the vidget.
+
+procedure outline_listframe()
+end
+
+
+############################################################################
+#
+# PROCEDURE
+# resize_listframe(s, x, y, w, h) #
+# DESCRIPTION
+# Handle resizing of a Vlist.
+
+procedure resize_listframe(s, x, y, w, h)
+ /x := s.ax
+ /y := s.ay
+ /w := s.aw
+ /h := s.ah
+ resize_Vidget(s, x, y, w, h)
+ VResize(s.draw[1], x, y, w - VSlider_DefWidth - 2, h)
+ VResize(s.draw[2], x + w - VSlider_DefWidth, y, VSlider_DefWidth, h)
+end
+
+
+############################################################################
+#
+# PROCEDURE
+# pane := Vpane_Vlist(win, cb, id, style, aw, ah)
+# Vpane_Vlist(frame, x, y, win, cb, id, style, aw, ah)
+#
+# DESCRIPTION
+# Create a specialized Vpane that has been modified to display a list
+# of strings.
+#
+# INPUTS
+# frame - (optional) the frame to insert this vidget in
+# x - (optional) the x coordinate to insert the vidget at
+# y - (optional) the y coordinate to insert the vidget at
+#
+# These three parameters listed above are optional. However, they must
+# all be present if you plan to use them.
+#
+# win - the window the vidget will appear in
+# cb - the callback procedure to handle events
+# id - the id of the vidget
+# style - which outline style to use: "grooved", "sunken", or "raised"
+# aw - (optional) the width of the vidget
+# ah - (optional) the height of the vidget
+#
+# OUTPUT
+# pane - the Vpane vidget (record)
+
+procedure Vpane_Vlist(params[])
+local self, frame, x, y, ins
+static procs
+
+ initial procs := Vstd(event_Vlist, draw_Vpane_Vlist,
+ outline_listpane, resize_Vpane,
+ inrange_Vpane, init_Vpane, couplerset_Vpane)
+
+ if ins := Vinsert_check(params) then {
+ frame := pop(params); x := pop(params); y:= pop(params)
+ }
+ self := Vpane_rec ! params[1:7|0]
+ Vwin_check(self.win, "Vpane()")
+ if (\self.aw, not numeric(self.aw)) then
+ _Vbomb("invalid aw parameter to Vpane()")
+ if (\self.ah, not numeric(self.ah)) then
+ _Vbomb("invalid ah parameter to Vpane()")
+
+ /self.style := "invisible"
+ if integer(self.style) then
+ if self.style > 0 then
+ self.style := "grooved"
+ else
+ self.style := "invisible"
+
+ self.uid := Vget_uid()
+ self.V := procs
+ self.P := Vstd_pos()
+
+ if \ins then VInsert(frame, self, x, y)
+ return self
+
+end
+
+
+############################################################################
+#
+# PROCEDURE
+# draw_Vpane_Vlist(self)
+#
+# DESCRIPTION
+# Call the drawlist_Vlist() procedure using the current list and
+# top line values.
+#
+# This function is called whenever the vlist is asked to
+# draw itself.
+#
+# INPUTS
+# self - the Vpane vidget (record)
+
+procedure draw_Vpane_Vlist(self)
+local dl, tl
+
+ self.V.outline(self)
+
+ dl := storage_Vlist(self.id, "read", "datalist")
+ tl := storage_Vlist(self.id, "read", "top_line")
+ drawlist_Vlist(self, dl, tl)
+
+ return
+
+end
+
+
+############################################################################
+#
+# PROCEDURE
+# event_Vlist(self, e, x, y)
+#
+# DESCRIPTION
+# Handles events in the Vpane containing the list. This amounts to
+# highlighting the line that was selected by the user with the mouse
+# or by the programmer using VSetState(). Only one line at a time
+# can be highlighted. The list vidget callback is not called until
+# a &lrelease event is detected (releasing the mouse button implies
+# the user has made a selection). It also supports dragging the mouse
+# across the list, highlighting and unhighlighting each line in turn.
+#
+# INPUTS
+# self - the Vpane vidget record
+# e - the event that triggered with callback
+# x - the x position of the mouse at the time of the event
+# y - the y position of the mouse at the time of the event
+#
+# BUGS
+# If the vlist is showing a list that is smaller than the actual
+# area of the list itself, the last line can still be selected
+# by clicking anywhere in the empty space beneath the last line.
+
+procedure event_Vlist(self, e, x, y)
+ local cb, dl, tl, sl, selectmode, selected, cb_data, cb_items, i, mode
+
+ if e ~=== &lpress then
+ fail # not our event
+
+ mode := storage_Vlist(self.id, "read", "mode")
+ if mode === V_READONLY then
+ fail # no events on read-only vidget
+
+ cb := self.callback
+ /y := &y
+ dl := storage_Vlist(self.id, "read", "datalist")
+ tl := storage_Vlist(self.id, "read", "top_line")
+ sl := storage_Vlist(self.id, "read", "selection")
+
+##### Dragging the mouse while holding #######
+##### the mouse button down highlights or un-highlights lines #######
+##### depending on whether the first line clicked on was highlighted #######
+##### or unhighlighted. #######
+
+ selected := vlist_selection(self, y)
+
+##### Handle mouse events for V_SELECT mode. #######
+
+ if mode === V_SELECT then {
+
+ /sl := 0
+ if sl = selected then {
+ dl[selected][1] := "N"
+ sl := &null
+ }
+ else {
+ dl[selected][1] := "S"
+ dl[sl][1] := "N"
+ sl := selected
+ }
+ drawlist_Vlist(self, dl, tl)
+
+ while (e := Event(self.win)) ~=== &lrelease do {
+
+ if e ~=== &ldrag then
+ next
+
+ selected := vlist_selection(self, &y)
+
+ /sl := 0
+ if sl = selected then
+ next
+ else {
+ dl[selected][1] := "S"
+ dl[sl][1] := "N"
+ sl := selected
+ drawlist_Vlist(self, dl, tl)
+ }
+
+ }
+
+ storage_Vlist(self.id, "write", "selection", sl)
+
+ if find("coupler", type(\cb)) then { # coupler
+ if \self.callback.locked then fail
+ return cb.V.set(cb, self) | &null
+ }
+
+ if type(\cb) == "procedure" then {
+ if dl[selected][1] == "S" then
+ return cb(self, dl[selected][2:0], selected) | &null
+ else
+ return cb(self, &null, 0) | &null
+ }
+
+ return
+ }
+
+##### Handle mouse events for V_MULTISELECT mode. #######
+
+ if dl[selected][1] == "S" then
+ selectmode := "N"
+ else
+ selectmode := "S"
+ dl[selected][1] := selectmode
+ drawlist_Vlist(self, dl, tl)
+
+ while (e := Event(self.win)) ~=== &lrelease do
+ if e === &ldrag then {
+ dl[vlist_selection(self, &y)][1] := selectmode
+ drawlist_Vlist(self, dl, tl)
+ }
+
+ if find("coupler", type(\cb)) then { # coupler
+ if \self.callback.locked then fail
+ return cb.V.set(cb, self) | &null
+ }
+
+ if type(\cb) == "procedure" then { # procedure
+ cb_data := []
+ cb_items := []
+ every i := 1 to *dl do
+ if dl[i][1] == "S" then {
+ put(cb_data, dl[i][2:0])
+ put(cb_items, i)
+ }
+ return cb(self, cb_data, cb_items) | &null
+ }
+
+ return
+end
+
+
+############################################################################
+#
+# PROCEDURE
+# vlist_selection(self, y)
+#
+# DESCRIPTION
+# Determines the item selected by the mouse
+#
+# INPUTS
+# self - the Vpane vidget record
+# sval - the y coordinate of an event
+#
+# OUTPUT
+# the index of the selected line
+
+procedure vlist_selection(self, y)
+ local fh, tl, dl, window_sz, selected
+
+ fh := WAttrib(self.win, "fheight")
+ tl := storage_Vlist(self.id, "read", "top_line")
+ dl := storage_Vlist(self.id, "read", "datalist")
+ window_sz := integer(self.uh / fh) - 1
+
+ selected := tl - 1 + integer((y - self.uy + fh - 2) / fh)
+ selected >:= tl + window_sz
+ selected >:= *dl
+ selected <:= 1
+ selected <:= tl
+ return selected
+end
+
+
+############################################################################
+#
+# PROCEDURE
+# outline_listpane(self)
+#
+# DESCRIPTION
+# Draws an outline around the Vpane being used to display the list.
+#
+# INPUTS
+# self - the Vpane vidget record
+
+procedure outline_listpane(self)
+
+ BevelRectangle(self.win, self.ax, self.ay, self.aw, self.ah, -2)
+
+ return
+
+end
diff --git a/ipl/gprocs/vmenu.icn b/ipl/gprocs/vmenu.icn
new file mode 100644
index 0000000..ea23240
--- /dev/null
+++ b/ipl/gprocs/vmenu.icn
@@ -0,0 +1,673 @@
+############################################################################
+#
+# File: vmenu.icn
+#
+# Subject: Procedures for vidget menus
+#
+# Authors: Jon Lipp and Gregg M. Townsend
+#
+# Date: May 2, 2001
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# Vidgets defined in this file:
+#
+# Vmenu_item
+# Vmenu_bar_item
+# Vmenu_frame
+# Vpull_down_button
+# Vmenu_set_items
+# Vmenu_get_items
+#
+# Utility procedures in this file:
+# Vsub_menu()
+# Vmenu_bar()
+# Vpull_down_pick_menu()
+# Vpull_down()
+#
+############################################################################
+#
+# Requires: Version 9 graphics
+#
+############################################################################
+#
+# Links: vstyle
+#
+############################################################################
+
+link vstyle
+
+############################################################################
+# Vmenu_item
+############################################################################
+record Vmenu_item_rec (win, s, callback, id, aw, ah, menu, ax, ay,
+ uid, P, D, V, style)
+
+procedure Vmenu_item(params[])
+ local self
+ static procs
+
+ initial procs := Vstd(event_Vmenu_item, draw_Vmenu_item, outline_menu_pane,
+ resize_Vidget, inrange_Vpane, init_Vmenu_item,
+ couplerset_Vmenu_item)
+ self := Vmenu_item_rec ! params
+ self.uid := Vget_uid()
+ if type(\self.callback) == "Vmenu_frame_rec" then {
+ self.menu := self.callback
+ self.callback := self.menu.callback
+ self.s ||:= " >"
+ }
+
+## Init
+ self.D := Vstd_draw(draw_off_entry, draw_on_entry)
+ self.P := Vstd_pos()
+ self.D.outline := 1
+ self.V := procs
+ self.V.init(self)
+
+ return self
+end
+
+#
+# A menu item needs to be sized a little smaller than a normal
+# button, so we steal the 2d init procedure.
+#
+procedure init_Vmenu_item(self)
+ local TW, FH, ascent, descent, basey
+
+ /self.s := ""
+ TW := TextWidth(self.win, self.s)
+ ascent := WAttrib(self.win, "ascent")
+ descent := WAttrib(self.win, "descent")
+ FH := ascent + descent
+ /self.aw := TW + 5
+ /self.ah := FH + 2
+
+ self.aw := 0 < self.aw | 1
+ self.ah := 0 < self.ah | 1
+
+ self.D.basex := (self.aw - TW + 1) / 2
+ basey := 1 + ascent
+ if FH <= 10 then basey := 8
+ self.D.basey := basey
+
+end
+
+procedure draw_Vmenu_item(s)
+ s.D.draw_off(s)
+end
+
+procedure draw_on_entry(s)
+ GotoXY(s.win, s.ax+s.D.basex, s.ay+s.D.basey)
+ writes(s.win, s.s)
+ BevelRectangle(s.win, s.ax, s.ay, s.aw, s.ah)
+end
+
+procedure draw_off_entry(s)
+ EraseArea(s.win, s.ax, s.ay, s.aw, s.ah)
+ GotoXY(s.win, s.ax+s.D.basex, s.ay+s.D.basey)
+ writes(s.win, s.s)
+end
+
+procedure couplerset_Vmenu_item(s)
+ s.V.draw(s)
+end
+
+#
+# This is complicated.... if we drag off to the right while within the
+# y-range of the menu item, call its submenu *if* one exists. Else
+# if there is a release not on the menu item, fall out of loop. Else
+# if released on menu item and there is *no* submenu, make a return
+# value consisting of the id. Else, continue through loop.
+#
+# This will take return value of submenu (if successful choice) and pass
+# it back up to menu bar item.
+#
+procedure event_Vmenu_item(self, e, sub)
+local rv
+
+ self.D.draw_on(self)
+ (\self.menu).V.resize(self.menu, self.ax+self.aw-4, self.ay)
+ show_Vmenu_frame(\self.menu)
+ rv := V_FAIL
+ repeat {
+ if (\self.menu,
+ (&x >= self.ax+self.aw) & (self.ay <= &y <= self.ay+self.ah)) then {
+ rv := self.menu.F.pick(self.menu, e, 1) | &null
+ if \rv ~=== V_DRAGGING & \rv ~=== V_FAIL then
+ rv := (push(\rv, self.uid))
+ }
+
+ else if (\self.menu, e === (&lrelease|&mrelease|&rrelease)) then rv := &null
+ else if e === (&lrelease|&mrelease|&rrelease) then rv := [self.uid]
+ else if self.V.inrange(self, &x, &y) then rv := V_DRAGGING
+ if \rv === V_DRAGGING then {
+ e := Event(self.win)
+ if e === "\^s" then
+ until Event(self.win) === (&lpress|&mpress|&rpress) ;
+ rv := V_FAIL
+ }
+ else break
+ }
+ hide_Vmenu_frame(\self.menu)
+ self.D.draw_off(self)
+ if rv === V_FAIL then fail
+ return rv
+end
+
+############################################################################
+# Vmenu_bar_item
+############################################################################
+
+procedure Vmenu_bar_item(params[])
+ local self
+ static procs
+
+ initial procs := Vstd(event_Vmenu_bar_item, draw_Vmenu_item,
+ outline_menu_pane, resize_Vmenu_bar_item, inrange_Vpane,
+ null_proc, couplerset_Vmenu_item)
+ self := Vmenu_item_rec ! params
+ self.uid := Vget_uid()
+ if type(\self.menu) ~== "Vmenu_frame_rec" then
+ _Vbomb("Vmenu_bar_item must be created with a Vmenu_frame")
+
+## Init
+ Vset_style(self, V_RECT)
+ self.P := Vstd_pos()
+ self.V := procs
+ self.callback := (\self.menu).callback
+ self.D.init(self)
+
+ return self
+end
+
+#
+# Resize ourselves, then tell our submenu to resize itself at the
+# right location.
+#
+procedure resize_Vmenu_bar_item(self, x, y, w, h)
+
+ resize_Vidget(self, x, y, w, h)
+ (\self.menu).V.resize(self.menu, self.ax, self.ay+self.ah)
+end
+
+#
+# Process events through a loop, grabbing focus:
+# If release, fall out. Else, if dragged off bottom, open up submenu.
+# If dragged any other direction, fall out.
+#
+# Take return value ( a list) from submenu, and reference callback tables
+# to call correct callback for submenu choice made.
+#
+procedure event_Vmenu_bar_item(self, e)
+local rv, callback, i, t, labels
+
+ if e ~=== &lpress & e ~=== &mpress & e ~=== &rpress then
+ fail # not our event
+ self.D.draw_on(self)
+ show_Vmenu_frame(\self.menu)
+ repeat {
+ if e === (&lrelease|&mrelease|&rrelease) then rv := &null
+ else if self.V.inrange(self, &x, &y) then rv := V_DRAGGING
+ else if (self.ax <= &x <= self.ax+self.aw) & (&y >= self.ay+self.ah) then
+ rv := (\self.menu).F.pick(self.menu, e)
+ if \rv === V_DRAGGING then {
+ e := Event(self.win)
+ rv := &null
+ }
+ else break
+ }
+ hide_Vmenu_frame(\self.menu)
+ self.D.draw_off(self)
+ if \rv === V_FAIL then
+ return &null
+ if \rv then {
+ callback := self.callback
+ labels := []
+ every i := !rv do {
+ t := callback[i]
+ callback := t[1]
+ put(labels, t[2])
+ }
+ return (\callback)(self, labels) | labels
+ }
+ return &null
+end
+
+
+############################################################################
+# Vmenu_frame
+############################################################################
+
+record Vmenu_frame_rec(win, callback, aw, ah, id, temp, drawn,
+ lookup, draw, ax, ay, uid, P, F, V)
+
+procedure Vmenu_frame(params[])
+local self
+static procs
+
+ initial {
+ procs := Vstd(event_Vframe, draw_Vframe, outline_menu_pane,
+ resize_Vframe, inrange_Vpane, null_proc,
+ couplerset_Vpane, insert_Vmenu_frame, null_proc,
+ lookup_Vframe, set_abs_Vframe)
+ }
+
+ self := Vmenu_frame_rec ! params
+
+## Init
+ self.uid := Vget_uid()
+ self.V := procs
+ self.F := Vstd_draw()
+ self.F.pick := pick_Vmenu_frame
+ self.F.format := format_Vmenu_frame
+
+ self.P := Vstd_pos()
+ init_Vframe(self)
+ self.callback := table()
+ self.temp := open("vmenu", "g", "canvas=hidden")
+
+ return self
+end
+
+#
+# Draw beveled, raised outline
+#
+procedure outline_menu_pane(self)
+ BevelRectangle(self.win, self.ax, self.ay, self.aw, self.ah)
+end
+
+#
+# Find minimum bounding encompassing frame. At the same time, set
+# children to be flush against left edge.
+#
+procedure format_Vmenu_frame(self, width)
+local maxwidth, child
+
+ maxwidth := \width | Vmin_frame_width(self) + 4
+ every child := !self.lookup do {
+ child.P.w := maxwidth - 4
+ }
+ self.V.resize(self, 0, 0, maxwidth, Vmin_frame_height(self) + 2)
+end
+
+#
+# Open up menu frame. Copy window on temporary binding.
+# Usually invoked by parent menu item.
+#
+procedure show_Vmenu_frame(self)
+
+ WAttrib(self.temp, "width="||(self.aw+10), "height="||(self.ah+10))
+ CopyArea(self.win, self.temp, self.ax, self.ay, self.aw+5, self.ah+5, 0, 0)
+ draw_Vframe(self)
+ self.drawn := 1
+end
+
+#
+# Hide menu frame. Copy contents of temporary binding back onto window.
+# Also invoked by parent menu item.
+#
+procedure hide_Vmenu_frame(self)
+
+ CopyArea(self.temp, self.win, 0, 0, self.aw+5, self.ah+5, self.ax, self.ay)
+ self.drawn := &null
+end
+
+#
+# Basically the event loop for the menu frame. Routes events to the
+# appropriate menu item.
+#
+procedure pick_Vmenu_frame(self, e, sub)
+local focus, rv
+
+ /e := -1
+ if /self.drawn then
+ show_Vmenu_frame(self)
+ rv := V_DRAGGING
+ repeat {
+ focus := self.V.lookup(self, &x, &y) | &null
+ if (e === (&lrelease|&mrelease|&rrelease) & /focus) then fail
+ else if (/sub, &y < self.ay) | (\sub, &x < self.ax) then return V_DRAGGING
+ else if rv := (\focus).V.event(focus, e, sub) then return rv
+ else if (e === "\^s" & /focus) then
+ until Event(self.win) === (&lpress|&mpress|&rpress) ;
+ e := Event(self.win)
+ }
+end
+
+#
+# Put the entries into the callback table of the frame as such: if the
+# entry has a submenu, put its callback table and string label in, else
+# put the callback procedure and string label in.
+#
+procedure insert_Vmenu_frame(self, vid, x, y)
+ local s
+
+ insert_Vframe(self, vid, x, y)
+ s := (type(vid.callback) == "table", vid.s[1:-2]) | vid.s
+ self.callback[\vid.uid] := [vid.callback, s]
+end
+
+############################################################################
+# wrappers for Vsub_menu and Vmwenu_bar
+############################################################################
+
+procedure Vsub_menu(w, p[])
+ local frame, id, name, callback, ypos, item
+
+ Vwin_check(w, "Vsub_menu()")
+
+ frame := Vmenu_frame(w)
+ id := 1
+ ypos := 0
+ while \(name := pop(p)) do {
+ callback := pop(p) | &null
+ if type(\name) ~== "string" & not numeric(name) then
+ _Vbomb("invalid label passed to Vsub_menu()")
+ image(callback) ? { if ="function" then
+ _Vbomb("Icon function" || tab(0) ||
+ "() not allowed as callback from sub_menu item")
+ }
+ item := Vmenu_item(w, name, callback, id)
+ VInsert(frame, item, 2, ypos)
+ id +:= 1
+ ypos +:= item.ah
+ }
+ VFormat(frame)
+ return frame
+end
+
+procedure Vmenu_bar(p[])
+ local parent, x, y, ins, frame, id, name, submenu, xpos, item, win
+
+ if ins := Vinsert_check(p) then {
+ parent := pop(p); x := pop(p); y:= pop(p)
+ }
+ win := pop(p)
+ Vwin_check(win, "Vmenu_bar()")
+
+ frame := Vframe(win)
+ xpos := id := 0
+ while name := pop(p) do {
+ submenu := pop(p) | &null
+ if type(\name) ~== "string" & not numeric(name) then
+ _Vbomb("invalid label passed to Vmenu_bar()")
+ if type(\submenu) ~== "Vmenu_frame_rec" then
+ _Vbomb("invalid menu parameter to Vmenu_bar()")
+ item := Vmenu_bar_item(win, name, , id, , , submenu )
+ VInsert(frame, item, xpos, 0)
+ id +:= 1
+ xpos +:= item.aw
+ }
+ VFormat(frame)
+ frame.V.outline := null_proc
+
+ if \ins then VInsert(parent, frame, x, y)
+
+ return frame
+end
+
+############################################################################
+# Vpull_down_button
+############################################################################
+
+record Vpull_down_button_rec (win, callback, id, sz, pd, data, s, style,
+ aw, ah, ax, ay, abx, uid, P, D, V)
+
+procedure Vpull_down_button(params[])
+local self
+local frame, x, y, ins
+static procs
+
+ initial procs := Vstd(event_Vpull_down_button, draw_Vpull_down_button,
+ outline_menu_pane, resize_Vpull_down_button, inrange_Vpane,
+ init_Vpull_down_button, couplerset_Vpull_down_button,,,,,
+ set_value_Vpull_down_button)
+ if ins := Vinsert_check(params) then {
+ frame := pop(params); x := pop(params); y:= pop(params)
+ }
+ self := Vpull_down_button_rec ! params
+ self.uid := Vget_uid()
+ if type(self.pd) ~== "Vmenu_frame_rec" then
+ _Vbomb("Vpull_down_button must be created with a Vpull_down")
+ Vset_style(self, V_RECT)
+ self.V := procs
+ self.P := Vstd_pos()
+ self.V.init(self)
+ if \ins then VInsert(frame, self, x, y)
+ return self
+end
+
+procedure draw_Vpull_down_button(self)
+
+ self.s := self.data[1:self.sz|0]
+ self.D.draw_off(self)
+ draw_Vpull_down_button_off(self)
+end
+
+procedure draw_Vpull_down_button_arrow(self)
+local x, y, sz
+
+ x := self.ax+self.abx; y := self.ay; sz := self.ah
+
+ FillPolygon(self.win, x+0.1*sz, y+0.2*sz, x+0.9*sz, y+0.2*sz,
+ x+0.5*sz, y+0.9*sz, x+0.1*sz, y+0.2*sz)
+end
+
+procedure draw_Vpull_down_button_off(self)
+local x, y
+
+ x := self.ax; y := self.ay
+ EraseArea(self.win, x+self.abx+1, y+1, self.aw-self.abx-1, self.ah-1)
+ DrawRectangle(self.win, x+self.abx, y, self.aw-self.abx, self.ah)
+ draw_Vpull_down_button_arrow(self)
+end
+
+procedure draw_Vpull_down_button_on(self)
+
+ FillRectangle(self.win, self.ax+self.abx+1, self.ay+1, self.aw-self.abx, self.ah)
+ WAttrib(self.win, "reverse=on")
+ draw_Vpull_down_button_arrow(self)
+ WAttrib(self.win, "reverse=off")
+end
+
+procedure resize_Vpull_down_button(self, x, y, w, h)
+
+ resize_Vidget(self, x, y, w, h)
+ self.pd.F.format(self.pd, self.aw)
+ self.pd.V.resize(self.pd, self.ax, self.ay+self.ah)
+end
+
+procedure couplerset_Vpull_down_button(self, name, value)
+
+ self.D.draw_off(self)
+end
+
+
+procedure event_Vpull_down_button(self, e)
+local rv
+
+ if \self.callback.locked then fail
+ draw_Vpull_down_button_on(self)
+ show_Vmenu_frame(\self.pd)
+ rv := V_DRAGGING
+ repeat {
+ if \e === (&lrelease|&mrelease|&rrelease) then rv := &null
+ else if self.V.inrange(self, &x, &y) then rv := V_DRAGGING
+ else if (self.ax <= &x <= self.ax+self.aw) & (&y >= self.ay+self.ah) then
+ rv := (\self.pd).F.pick(self.pd, e)
+ if \rv === V_DRAGGING then {
+ e := Event(self.win)
+ rv := &null
+ }
+ else break
+ }
+ if rv === V_FAIL then rv := &null
+ draw_Vpull_down_button_off(self)
+ hide_Vmenu_frame(\self.pd)
+ if \rv then {
+ self.data := self.pd.callback[rv[1]][2]
+ self.V.draw(self)
+ self.callback.V.set(self.callback, self, self.data)
+ return self.data
+ }
+end
+
+procedure set_value_Vpull_down_button(self, value)
+
+ self.data := \value | ""
+end
+
+procedure init_Vpull_down_button(self)
+local p
+
+ /self.data := ""
+ self.s := self.data
+ /self.sz := 24
+ self.aw := WAttrib(self.win, "fwidth")*self.sz + 8
+ self.ah := WAttrib(self.win, "fheight")
+
+ self.abx := self.aw
+# make little arrow box on end.
+ self.aw +:= WAttrib(self.win, "fheight")
+
+ p := \self.callback
+ self.callback := Vcoupler()
+ add_clients_Vinit(self.callback, p, self)
+
+ self.D.init(self)
+ self.D.basex := 4
+end
+
+
+############################################################################
+# Vmenu_set_items(self,data)
+#
+# data is a list of one or more strings, and possibly lists:
+# any string can be followed in the list by a list of data for a submenu
+############################################################################
+
+procedure Vmenu_set_items(self, data)
+ local cb, item
+
+ cb := !!self.lookup[1].callback
+ item := self.lookup[1]
+ item.menu := Vmenu_set_submenu(self.win, data, cb)
+ item.callback := item.menu.callback
+ VResize(self)
+ return
+end
+
+procedure Vmenu_set_submenu(win, data, cbk)
+ local a, c, e, i, lbl
+
+ if type(data) ~== "list" | *data = 0 then
+ _Vbomb("empty or invalid menu list for VSetItems()")
+ data := copy(data) # make copy to consume and destroy
+
+ a := [win]
+ while *data > 0 do {
+ put(a, string(get(data))) |
+ _Vbomb("invalid menu list entry for VSetItems()")
+ if type(data[1]) == "list" then
+ put(a, Vmenu_set_submenu(win, get(data), cbk))
+ else
+ put(a, cbk)
+ }
+ return Vsub_menu ! a
+end
+
+############################################################################
+# Vmenu_get_items
+############################################################################
+
+procedure Vmenu_get_items(self)
+ return Vmenu_get_submenu(self)[2]
+end
+
+procedure Vmenu_get_submenu(frame)
+ local l, r
+
+ l := list()
+ every r := !frame.lookup do {
+ if /r.menu then
+ put(l, r.s)
+ else {
+ put(l, r.s[1:-2])
+ put(l, Vmenu_get_submenu(\r.menu))
+ }
+ }
+ return l
+end
+
+
+
+############################################################################
+# Utilities.
+############################################################################
+
+#
+# Well this is a wrapper for combining a Vpull_down and a
+# Vpull_down_button.
+#
+# Vpull_down_pick_menu([frame, x, y, ] w, s, callback, id, size, centered)
+#
+# s - a list of string labels for the entries.
+# size - is the number of characters in the data field to be displayed.
+# centered - non-&null if entries are centered in pull_down.
+#
+procedure Vpull_down_pick_menu(params[])
+local frame, x, y, ins, pd, self
+
+ if ins := Vinsert_check(params) then {
+ frame := pop(params); x := pop(params); y:= pop(params)
+ }
+ put(params); put(params); put(params); put(params);
+ Vwin_check(params[1], "Vpull_down_pick_menu()")
+ pd := Vpull_down ! (params[1:3] ||| [\params[6] | &null])
+ self := Vpull_down_button ! ([params[1]] ||| params[3:6] ||| [pd])
+
+ if \ins then VInsert(frame, self, x, y)
+ return self
+end
+
+#
+# Vpulldown(..) produces a pull-down list, invoked by
+#
+# obj.F.pick(obj)
+#
+# returns the string value of the object picked.
+#
+# p[] is a list of strings to enter into the list;
+# centered is &null for right justified entries, 1 for centered.
+#
+# (This procedure does not support the optional VInsert parameters.)
+#
+procedure Vpull_down(win, s, centered)
+local cv, frame, id, name, style, ypos
+local max, i, TW, FH, item, string_list
+
+ Vwin_check(win, "Vpull_down()")
+ if type(s) ~== "list" then
+ _Vbomb("data parameter to Vpull_down must be a list of strings")
+ frame := Vmenu_frame(win)
+ ypos := id := 1
+ if \centered then {
+ max := 0
+ every i := !s do max <:= (TextWidth(win, i) + 6)
+ }
+ string_list := copy(s)
+ while name := pop(string_list) do {
+ name := \name | ""
+ item := Vmenu_item(win, name, , name, max)
+ VInsert(frame, item, 1, ypos)
+ id +:= 1
+ ypos +:= item.ah
+ }
+ VFormat(frame)
+ return frame
+end
diff --git a/ipl/gprocs/vpane.icn b/ipl/gprocs/vpane.icn
new file mode 100644
index 0000000..6257a83
--- /dev/null
+++ b/ipl/gprocs/vpane.icn
@@ -0,0 +1,167 @@
+############################################################################
+#
+# File: vpane.icn
+#
+# Subject: Procedures for vidget panes
+#
+# Author: Jon Lipp
+#
+# Date: March 23, 1995
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# Vidgets defined in this file:
+# Vpane
+#
+############################################################################
+#
+# Links: vidgets
+#
+############################################################################
+
+link vidgets
+
+############################################################################
+# pane - a simple region on the window
+############################################################################
+
+record Vpane_rec(win, callback, id, style, aw, ah, ax, ay,
+ uw, uh, ux, uy, uid, P, V)
+
+procedure Vpane(params[])
+ local self, frame, x, y, ins
+ static procs
+
+ initial procs := Vstd(event_Vpane, draw_Vpane, outline_Vpane,
+ resize_Vpane, inrange_Vpane, init_Vpane,
+ couplerset_Vpane)
+
+ if ins := Vinsert_check(params) then {
+ frame := pop(params); x := pop(params); y:= pop(params)
+ }
+ self := Vpane_rec ! params[1:7|0]
+ Vwin_check(self.win, "Vpane()")
+ if (\self.aw, not numeric(self.aw) ) then
+ _Vbomb("invalid aw parameter to Vpane()")
+ if (\self.ah, not numeric(self.ah) ) then
+ _Vbomb("invalid ah parameter to Vpane()")
+
+ /self.style := "invisible"
+ if integer(self.style) then
+ if self.style > 0 then
+ self.style := "grooved"
+ else
+ self.style := "invisible"
+
+ self.uid := Vget_uid()
+ self.V := procs
+ self.P := Vstd_pos()
+
+ if \ins then VInsert(frame, self, x, y)
+ return self
+end
+
+#
+# check if (x, y) lie within the bounds of a vidget.
+#
+procedure inrange_Vpane(self, x, y)
+ if (/self.ax | /self.ay | /self.aw | /self.ah) then
+ _Vbomb("VResize() not invoked on this vidget")
+ return self.ax <= \x < self.ax + self.aw & self.ay <= \y < self.ay + self.ah
+end
+
+#
+# Set the absolute position and size fields of a vidget.
+#
+procedure resize_Vidget(self, x, y, w, h)
+ self.ax := \x
+ self.ay := \y
+ self.aw := \w
+ self.ah := \h
+end
+
+#
+# Set the absolute position and size fields of a Pane vidget.
+#
+procedure resize_Vpane(self, x, y, w, h)
+ local border
+
+ resize_Vidget(self, x, y, w, h)
+ if self.style == "invisible" then
+ border := 0
+ else
+ border := 2
+ self.ux := self.ax + border
+ self.uy := self.ay + border
+ self.uw := self.aw - 2 * border
+ self.uh := self.ah - 2 * border
+end
+
+#
+# Draw the outline of an arbitrary vidget
+#
+procedure outline_Vidget(self)
+ GrooveRectangle(self.win, self.ax, self.ay, self.aw, self.ah)
+end
+
+#
+# Draw the outline of a Vpane vidget
+#
+procedure outline_Vpane(self)
+ case self.style of {
+ "sunken": BevelRectangle(self.win, self.ax, self.ay, self.aw, self.ah,-2)
+ "grooved": GrooveRectangle(self.win, self.ax, self.ay, self.aw, self.ah)
+ "raised": BevelRectangle(self.win, self.ax, self.ay, self.aw, self.ah)
+ }
+end
+
+# At the very least, tell a Vpane to outline itself.
+#
+procedure draw_Vpane(self)
+ self.V.outline(self)
+end
+
+#
+# If the Vpane has a callback, call (or set) it; otherwise, reject the event.
+#
+procedure event_Vpane(self, e, x, y)
+ local cb
+ static type
+
+ initial type := proc("type", 0) # protect attractive name
+
+ cb := self.callback
+ /x := &x
+ /y := &y
+ if type(\cb) == "procedure" then # procedure
+ return cb(self, e, x, y) | &null
+ if find("coupler",type(\cb)) then { # coupler
+ if \self.callback.locked then fail
+ return cb.V.set(cb, self) | &null
+ }
+ fail # reject
+end
+
+#
+# If the vidget with this procedure as its couplerset is notified by
+# a coupler, nothing will happen.
+#
+procedure couplerset_Vpane(self)
+end
+
+#
+# Release the resources associated with the binding on a window.
+#
+procedure destruct_Vpane(self)
+ Uncouple(self.win)
+end
+
+#
+# No init for Vpane.
+#
+procedure init_Vpane(self)
+end
diff --git a/ipl/gprocs/vquery.icn b/ipl/gprocs/vquery.icn
new file mode 100644
index 0000000..8696153
--- /dev/null
+++ b/ipl/gprocs/vquery.icn
@@ -0,0 +1,194 @@
+############################################################################
+#
+# File: vquery.icn
+#
+# Subject: Procedures for window queries
+#
+# Author: Jon Lipp
+#
+# Date: May 2, 2001
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# Utility procedures in this file: Vchoice(), Vinput()
+#
+############################################################################
+#
+# Requires: Version 9 graphics
+#
+############################################################################
+#
+# Links: vidgets, vbuttons, vtext
+#
+############################################################################
+
+link vidgets
+link vbuttons
+link vtext
+
+procedure Vchoice(str, buttons[])
+local win, root, t, u, w, b, i, x, y, rv
+local button_pos, def_button, old, event
+static wpad, hwpad
+static temp, PAD, WINX, WINY
+initial {
+ temp := open("vchoice", "g", "canvas=hidden")
+ PAD := integer(WAttrib(temp, "fheight") + 10)
+ WINX := integer(WAttrib(temp, "displaywidth") / 2)
+ WINY := integer(WAttrib(temp, "displayheight") / 2)
+
+ wpad := 30
+ hwpad := wpad/2
+}
+
+ if *buttons = 0 then buttons := [" Yes ", " No "]
+ t := TextWidth(temp, str)
+ u := 0
+ every b := !buttons do
+ u +:= TextWidth(temp, \b) + 13
+ w := ((u > t, u) | t) + wpad
+
+ win := vquery_open_window("choose", WINX-w/2, WINY-PAD, w, 2*PAD+wpad)
+ root := Vroot_frame(win)
+ VResize(root)
+
+ Vmessage(root, hwpad + (w-wpad-t)/2, hwpad, win, str)
+ x := hwpad + (w-wpad-u)/2; y := -hwpad
+ button_pos := table()
+ every i := 1 to *buttons do {
+ t := Vbutton(root, x, y, win, buttons[i], , i)
+ x +:= t.aw+5
+ button_pos[i] := xywh_rec(t.ax-2, t.ay-2, t.aw+4, t.ah+4)
+ }
+ VDraw(root)
+
+ def_button := 1
+ old := button_pos[def_button]
+ DrawRectangle(win, old.x, old.y, old.w, old.h)
+
+ repeat {
+ rv := &null
+ case event := Event(win) of {
+ -10: next
+ "\r": {
+ rv := def_button
+ break
+ }
+ "\t" : {
+ WAttrib(win, "drawop=reverse")
+ DrawRectangle(win, old.x, old.y, old.w, old.h)
+ def_button +:= 1
+ def_button := (def_button > *buttons, 1)
+ old := button_pos[def_button]
+ WAttrib(win, "drawop=copy")
+ DrawRectangle(win, old.x, old.y, old.w, old.h)
+ }
+ default : {
+ rv := VEvent(root, event, &x, &y)
+ (\rv, break)
+ }
+ } # end case
+ }
+ close(win)
+ return rv
+
+end
+record xywh_rec(x, y, w, h)
+
+procedure Vinput(str, def_value)
+local win, root, t, u, w, b, i, x, y, rv
+local buttons, v, input_vidget, ok, cancel
+local button_pos, def_button, old, lrv, event
+static temp, PAD, WINX, WINY, FW, VTEXT_W
+static wpad, hwpad, ID_OK, ID_CANCEL
+initial {
+ temp := WOpen("canvas=hidden")
+ PAD := integer(WAttrib(temp, "fheight") + 10)
+ WINX := integer(WAttrib(temp, "displaywidth") / 2)
+ WINY := integer(WAttrib(temp, "displayheight") / 2)
+ FW := integer(WAttrib(temp, "fwidth"))
+
+ wpad := 30
+ hwpad := wpad/2
+ ID_OK := -11
+ ID_CANCEL := -12
+ VTEXT_W := 20
+}
+
+ /str := ""
+ /def_value := ""
+ buttons := [" Ok ", "Cancel"]
+ v := FW * VTEXT_W + 8
+ t := TextWidth(temp, str)
+ u := 0
+ every b := !buttons do
+ u +:= TextWidth(temp, b) + 13
+ w := vquery_maximum(t, u, v) + wpad
+
+ win := vquery_open_window("choose", WINX-w/2, WINY-PAD, w, 3*PAD+wpad)
+ root := Vroot_frame(win)
+ VResize(root)
+
+ t := Vmessage(root, hwpad + (w-wpad-t)/2, hwpad, win, str)
+ input_vidget := Vtext(root, hwpad+(w-wpad-v)/2, hwpad+t.ah+5, win, "\\="||def_value , , , VTEXT_W)
+ x := hwpad + (w-wpad-u)/2; y := -hwpad
+ ok := Vbutton(root, x, y, win, buttons[1], , ID_OK)
+ x +:= ok.aw+5
+ cancel := Vbutton(root, x, y, win, buttons[2], , ID_CANCEL)
+
+ button_pos := table()
+ button_pos[ID_OK] := xywh_rec(ok.ax-2, ok.ay-2, ok.aw+4, ok.ah+4)
+ button_pos[ID_CANCEL] := xywh_rec(cancel.ax-2, cancel.ay-2, cancel.aw+4, cancel.ah+4)
+
+ VDraw(root)
+ def_button := ID_OK
+ old := button_pos[def_button]
+ DrawRectangle(win, old.x, old.y, old.w, old.h)
+
+ repeat {
+ lrv := rv := &null
+ case event := Event(win) of {
+ -10 : next
+ "\r" : {
+ rv := def_button
+ break
+ }
+ "\t": {
+ WAttrib(win, "drawop=reverse")
+ DrawRectangle(win, old.x, old.y, old.w, old.h)
+ def_button := (def_button = ID_OK, ID_CANCEL) | ID_OK
+ old := button_pos[def_button]
+ WAttrib(win, "drawop=copy")
+ DrawRectangle(win, old.x, old.y, old.w, old.h)
+ }
+
+ default: {
+ lrv := root.V.lookup(root, &x, &y)
+ /lrv := input_vidget
+ rv := (lrv).V.event(lrv, event, &x, &y)
+ if rv === (ID_OK | ID_CANCEL) then break
+ }
+ } # end case
+ }
+ close(win)
+ return (rv = ID_OK, input_vidget.data) | &null
+
+end
+
+procedure vquery_maximum(l[])
+ return sort(l)[-1]
+end
+procedure vquery_open_window(title, x, y, w, h)
+local win
+
+ /x := 50; /y := 50; /w := 400; /h := 400
+ win := open(title, "g", "pos="||x||","||y, "width="||w, "height="||h) |
+ _Vbomb("couldn't open window")
+
+ return win
+end
+
diff --git a/ipl/gprocs/vradio.icn b/ipl/gprocs/vradio.icn
new file mode 100644
index 0000000..b49e436
--- /dev/null
+++ b/ipl/gprocs/vradio.icn
@@ -0,0 +1,322 @@
+############################################################################
+#
+# File: vradio.icn
+#
+# Subject: Procedures for radio buttons
+#
+# Authors: Jon Lipp and Gregg M. Townsend
+#
+# Date: August 14, 1996
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# Vidgets defined in this file:
+# Vradio_entry
+# Vradio_frame
+#
+# Utility procedures in this file:
+# Vradio_buttons()
+# Vvert_radio_buttons()
+# Vhoriz_radio_buttons()
+# init_format_Vrb()
+# format_Vradio_frame()
+#
+############################################################################
+
+link vstyle
+
+############################################################################
+# Vradio - the radio button.
+############################################################################
+
+record Vradio_entry_rec (win, s, callback, id, style, aw, ah, don, ax, ay, uid, P, D, V)
+
+#
+# Creation procedure.
+#
+procedure Vradio_entry(params[])
+ local self
+ static procs
+
+ initial procs := Vstd(event_Vradio_entry, draw_Vradio_entry,
+ outline_radio_pane, resize_Vidget, inrange_Vpane, init_Vradio_entry,
+ couplerset_Vradio_entry)
+ self := Vradio_entry_rec ! params
+ self.uid := Vget_uid()
+ Vset_style(self, self.style)
+ self.V := procs
+ self.P := Vstd_pos()
+ self.V.init(self)
+ return self
+end
+
+procedure init_Vradio_entry (self)
+ local p
+
+ if /self.callback then
+ _Vbomb("must pass a coupler variable to a Vradio_entry button")
+ self.D.init(self)
+end
+
+
+#
+# Draw the frame around the radio buttons.
+#
+procedure outline_radio_pane(self)
+ GrooveRectangle(self.win, self.ax, self.ay, self.aw, self.ah)
+end
+
+
+#
+# Draw the radio button. If coupler's value is this id, draw "on".
+#
+procedure draw_Vradio_entry(self)
+ if self.callback.value === self.id then {
+ self.D.draw_on(self)
+ self.don := 1
+ }
+ else {
+ self.D.draw_off(self)
+ self.don := &null
+ }
+end
+
+#
+# The coupler notified us, turn "off".
+#
+procedure couplerset_Vradio_entry(self)
+ self.D.draw_off(self)
+ self.don := &null
+end
+
+#
+# If first time in this button, set coupler, draw "on".
+# If mouse releases on me, return my own record structure.
+#
+procedure event_Vradio_entry(self, e)
+
+ if self.callback.value ~=== self.id | /self.don then {
+ self.callback.V.set(self.callback, self, self.id)
+ self.D.draw_on(self)
+ self.don := 1
+ }
+ if \e === (&lrelease|&mrelease|&rrelease) then
+ return self
+end
+
+
+############################################################################
+# Vradio_frame
+############################################################################
+
+record Vradio_frame_rec(win, cv, callback, id, aw, ah, data,
+ lookup, draw, ax, ay, uid, P, V)
+
+#
+# Creation procedure.
+#
+procedure Vradio_frame(params[])
+ local self, p
+ static procs
+
+ initial {
+ procs := Vstd(event_Vradio_frame, draw_Vframe, outline_radio_pane,
+ resize_Vframe, inrange_Vpane, init_Vframe,
+ couplerset_Vpane, insert_Vframe, null_proc,
+ lookup_Vframe, set_abs_Vframe, set_value_Vradio_frame)
+ }
+
+ self := Vradio_frame_rec ! params
+ self.uid := Vget_uid()
+ self.V := procs
+ self.P := Vstd_pos()
+ self.V.init(self)
+ p := \self.callback
+ self.callback := Vcoupler()
+ add_clients_Vinit(self.callback, p, self)
+
+ return self
+end
+
+#
+# Distribute mouse event to proper radio button. If returns
+# a value, (mouse released) notify callbacks, return text label
+# of radio button selected.
+#
+procedure event_Vradio_frame(self, e, x, y)
+ local focus, rv
+
+ if \self.callback.locked then fail
+ if e ~=== &lpress & e ~=== &mpress & e ~=== &rpress then fail
+ focus := self.V.lookup(self, x, y)
+ (\focus).V.event(focus, e)
+ repeat {
+ e := Event(self.win)
+ if e === "\^s" then
+ until Event(self.win) === (&lpress|&mpress|&rpress) ;
+ if self.V.inrange(self, &x, &y) then
+ focus := self.V.lookup(self, &x, &y)
+ if rv := (\focus).V.event(focus, e) then {
+ self.data := rv.s
+ self.callback.V.set(self.callback, rv, rv.s)
+ return rv.s
+ }
+ }
+end
+
+#
+# Set the radio frame according to string label passed in. Match with
+# string label of a button. Null sets to no button.
+#
+procedure set_value_Vradio_frame(self, value)
+ local old, kid, id, s, k
+
+ if (/value | *value = 0 | value === V_DUMMY_ID) then {
+ kid := &null
+ id := V_DUMMY_ID
+ s := ""
+ }
+ else {
+ kid := self.cv.curr_id
+ id := self.cv.value
+ s := self.data
+ every (k := !self.lookup | fail) do
+ if value === k.s then {
+ id := k.id
+ kid := k
+ s := value
+ break
+ }
+ }
+
+ old := self.cv.curr_id
+ self.cv.curr_id := kid
+ self.cv.value := id
+ self.data := s
+
+ self.callback.V.set(self.callback, self, self.data)
+
+ (\old).D.draw_off(old) # clear current button
+ (\kid).D.draw_on(kid) # set new button
+
+ return
+end
+
+############################################################################
+# Vradio_buttons -
+# Construct radio buttons. Parameters:
+# w - window, proc - the callback procedure,
+# s[] - a list of button labels.
+############################################################################
+procedure Vradio_buttons(params[])
+ return Vvert_radio_buttons ! params
+end
+
+
+#
+# params: (w, s, callback, id, style)
+#
+procedure Vvert_radio_buttons(params[])
+ local frame, x, y, ins, win, s, callback, id, style
+ local rb_frame, max, cv, i, rb, first, uncentered
+ static type
+
+ initial type := proc("type", 0) # protect attractive name
+
+ if ins := Vinsert_check(params) then {
+ frame := pop(params); x := pop(params); y:= pop(params)
+ }
+ win := params[1]
+ s := params[2]
+ callback := params[3]
+ id := params[4]
+ style := params[5]
+ uncentered := params[6]
+
+ Vwin_check(win, "Vradio_buttons()")
+ if type(s) ~== "list" then
+ _Vbomb("data parameter to Vradio_buttons must be a list of strings")
+ cv := Vmenu_coupler()
+ rb_frame := Vradio_frame(win, cv, callback, id)
+ if /uncentered then {
+ max := 0
+ every i := !s do max <:= TextWidth(win, i)
+ max +:= 8
+ }
+ if \style == (V_CIRCLE | V_CHECK | V_DIAMOND |
+ V_CHECK_NO | V_CIRCLE_NO | V_DIAMOND_NO) then
+ max +:= 4 + WAttrib(win, "fheight")
+ every i := 1 to *s do {
+ rb := Vradio_entry(win, s[i], cv, i, style, max)
+ VInsert(rb_frame, rb, 0, (i-1)*rb.ah)
+ }
+
+ init_format_Vrb(rb_frame)
+ format_Vradio_frame(rb_frame)
+
+ if \ins then VInsert(frame, rb_frame, x, y)
+ return rb_frame
+end
+
+procedure Vhoriz_radio_buttons(params[])
+ local frame, x, y, ins, win, s, callback, id, style, hpos
+ local rb_frame, max, cv, i, rb, first
+ static type
+
+ initial type := proc("type", 0) # protect attractive name
+
+ if ins := Vinsert_check(params) then {
+ frame := pop(params); x := pop(params); y:= pop(params)
+ }
+ win := params[1]
+ s := params[2]
+ callback := params[3]
+ id := params[4]
+ style := params[5]
+
+ Vwin_check(win, "Vradio_buttons()")
+ if type(s) ~== "list" then
+ _Vbomb("data parameter to Vradio_buttons must be a list of strings")
+ cv := Vmenu_coupler()
+ rb_frame := Vradio_frame(win, cv, callback, id)
+ hpos := 0
+ every i := 1 to *s do {
+ rb := Vradio_entry(win, s[i], cv, i, style)
+ VInsert(rb_frame, rb, hpos, 0)
+ hpos +:= rb.aw
+ }
+
+ init_format_Vrb(rb_frame)
+ rb_frame.V.resize(rb_frame, 0, 0, Vmin_frame_width(rb_frame),
+ Vmin_frame_height(rb_frame))
+
+ if \ins then VInsert(frame, rb_frame, x, y)
+ return rb_frame
+end
+
+#
+# Set to no radio button selected, format size of frame.
+#
+procedure init_format_Vrb(rb_frame)
+
+ rb_frame.cv.value := V_DUMMY_ID
+ rb_frame.cv.curr_id := &null
+ rb_frame.data := ""
+end
+
+#
+# Get size of frame based on entries.
+#
+procedure format_Vradio_frame(self, width)
+local maxwidth, child
+
+ maxwidth := \width | Vmin_frame_width(self) + 4
+ every child := !self.lookup do {
+ child.P.w := maxwidth
+ }
+ self.V.resize(self, 0, 0, maxwidth, Vmin_frame_height(self))
+end
diff --git a/ipl/gprocs/vscroll.icn b/ipl/gprocs/vscroll.icn
new file mode 100644
index 0000000..5909bc3
--- /dev/null
+++ b/ipl/gprocs/vscroll.icn
@@ -0,0 +1,671 @@
+############################################################################
+#
+# File: vscroll.icn
+#
+# Subject: Procedures for scrollbars
+#
+# Authors: Jon Lipp and Gregg M. Townsend
+#
+# Date: April 1, 1997
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# Vidgets defined in this file:
+# Varrow
+# Vvthumb
+# Vhthumb
+# Vscrollbar_frame
+#
+# Utility procedures in this file:
+# Vvert_scrollbar()
+# Vhoriz_scrollbar()
+# reformat_Vhthumb()
+# reformat_Vvthumb()
+# Vreformat_vscrollbar()
+# Vreformat_hscrollbar()
+# VReformat()
+#
+############################################################################
+#
+# Includes: vdefns.icn
+#
+############################################################################
+#
+# Links: vidgets
+#
+############################################################################
+
+link vidgets
+
+$include "vdefns.icn"
+
+############################################################################
+# Varrow
+############################################################################
+
+record Varrow_rec(win, callback, aw, ah, rev, dir, incop, id, ax, ay, r,
+ uid, P, V)
+
+procedure Varrow(params[])
+local frame, x, y, ins, self, init_proc
+
+ init_proc := init_Varrow
+ if ins := Vinsert_check(params) then {
+ frame := pop(params); x := pop(params); y:= pop(params)
+ }
+ self := Varrow_rec ! params[1:7|0]
+ self.r := self.aw / 2
+ self.uid := Vget_uid()
+ self.V := Vstd(event_Varrow, draw_Varrow, 1,
+ resize_Vidget, inrange_Vpane, init_proc,
+ couplerset_Vpane)
+ self.P := Vstd_pos()
+ self.V.init(self)
+ if \ins then VInsert(frame, self, x, y)
+ return self
+end
+
+procedure event_Varrow(s,e)
+local c, prev, new
+static delay
+
+ initial delay := proc("delay", 0) # protect attractive name
+
+ if \s.callback.locked then fail
+ if e === (&lpress|&mpress|&rpress) then {
+ FillTriangle(s.win, s.ax + s.r, s.ay + s.r, s.r - 2, s.dir)
+ BevelTriangle(s.win, s.ax + s.r, s.ay + s.r, s.r, s.dir, -2)
+ s.callback.V.set(s.callback, s, prev := press_Varrow(s))
+ delay(200)
+ while (*Pending(s.win) = 0) |
+ (Event(s.win) === (&ldrag|&mdrag|&rdrag)) do {
+ new := press_Varrow(s)
+ if new ~= prev then
+ s.callback.V.set(s.callback, s, prev := new)
+ delay(40)
+ }
+ draw_Varrow(s)
+ return \(s.callback.value)
+ }
+end
+
+procedure draw_Varrow(s)
+ EraseArea(s.win, s.ax, s.ay, s.aw, s.ah)
+ BevelTriangle(s.win, s.ax + s.r, s.ay + s.r, s.r, s.dir)
+end
+
+procedure press_Varrow(s)
+ local v
+ v := s.incop(s.callback.value, s.callback.inc)
+ if abs(v) < abs(s.callback.inc) / 1000000.0 then # if close to zero
+ v -:= v # set to zero, preserving type
+ return v
+end
+
+procedure init_Varrow(s)
+ if /s.aw then _Vbomb("must specify a size for a Varrow")
+ if (/s.rev & s.dir == !"se") | (\s.rev & s.dir == !"nw") then
+ s.incop := proc("+", 2)
+ else
+ s.incop := proc("-", 2)
+ s.ah := s.aw
+ s.id := V_ARROW
+end
+
+############################################################################
+# Vvthumb
+############################################################################
+record Vthumb_rec (win, callback, id, aw, ah, win_sz, tot_sz, discont,
+ sp, sw, tw, th, ws, cv_range, pos, rev, frame, drawn, type,
+ ax, ay, uid, P, V)
+
+procedure procs_Vvthumb()
+ static procs
+ initial procs := Vstd(event_Vvthumb, draw_Vvthumb, 1,
+ resize_Vidget, inrange_Vpane, init_Vvthumb,
+ couplerset_Vvthumb,,,,,set_value_Vvthumb)
+ return procs
+end
+
+procedure Vvthumb(params[])
+local frame, x, y, ins, self
+
+ if ins := Vinsert_check(params) then {
+ frame := pop(params); x := pop(params); y:= pop(params)
+ }
+ self := Vthumb_rec ! params
+ self.uid := Vget_uid()
+ self.V := procs_Vvthumb()
+ self.P := Vstd_pos()
+ self.type := 1
+ self.V.init(self)
+ if \ins then VInsert(frame, self, x, y)
+ return self
+end
+
+#
+# debugging statement--
+#
+# write("draw: val ", val, " cv value ", s.callback.value, " cv min ",
+# s.callback.min, " ws ", s.ws, " cv range ", s.cv_range)
+#
+procedure draw_Vvthumb(s)
+ local val
+
+ s.drawn := 1
+ val := integer((s.callback.value - s.callback.min) * s.ws / s.cv_range + 0.5)
+ if \s.rev then
+ val := s.ws - val
+ s.pos := val
+ BevelRectangle(s.win, s.ax, s.ay + val, s.tw, s.th)
+end
+
+procedure event_Vvthumb(s, e)
+local value, offset
+
+ if \s.callback.locked then fail
+ if e === (&lpress|&mpress|&rpress) then {
+ offset := (s.th + 1) / 2
+ until e === (&lrelease|&mrelease|&rrelease) do {
+ value := ((&y - offset - s.ay) / (0 ~= s.ws)) * s.cv_range | 0
+ if \s.rev then
+ s.callback.V.set(s.callback, s, s.callback.max - value, s.discont)
+ else
+ s.callback.V.set(s.callback, s, s.callback.min + value, s.discont)
+ s.frame.data := s.callback.value
+ update_Vvthumb(s, 1)
+ e := Event(s.win)
+ }
+ update_Vvthumb(s)
+ if \s.discont then
+ s.callback.V.set(s.callback, s, s.callback.value)
+ return \(s.callback.value)
+ }
+end
+
+procedure update_Vvthumb(s, active)
+local val, op, tw, th, sw, sp
+
+ val := integer((s.callback.value - s.callback.min) * s.ws / s.cv_range + 0.5)
+ if \s.rev then
+ val := s.ws - val
+
+ op := s.pos; tw := s.tw; th := s.th
+ sp := s.sp; sw := s.sw
+ EraseArea(s.win, s.ax, s.ay + op, tw, th)
+ if \active then {
+ BevelRectangle(s.win, s.ax, s.ay + val, tw, th, -2)
+ FillRectangle(s.win, s.ax + 2, s.ay + val + 2, tw - 4, th - 4)
+ }
+ else
+ BevelRectangle(s.win, s.ax, s.ay + val, tw, th)
+ s.pos := val
+end
+
+procedure set_value_Vvthumb(s, value)
+ couplerset_Vvthumb(s, , value)
+end
+
+procedure couplerset_Vvthumb(s, caller, value)
+ value := numeric(value) | s.callback.min
+ if (\caller).id === V_ARROW then caller := s
+ else if value === s.callback.value then fail
+ s.frame.data := s.callback.value := value
+ if \s.drawn then
+ update_Vvthumb(s)
+end
+
+procedure init_Vvthumb(s)
+ static type
+
+ initial type := proc("type", 0) # protect attractive name
+
+ if /s.aw | /s.ah then
+ _Vbomb("must specify width and height for Vvthumb")
+ if /s.callback | type(s.callback) == "procedure" then
+ _Vbomb("Vvthumb requires a coupler variable callback")
+ s.sw := 3
+ s.sp:= (s.aw - s.sw) / 2
+ s.tw := s.aw
+ \s.win_sz <:= 0
+ if /s.win_sz then s.th := s.tw
+ else s.th := ( s.tw <
+ integer( ((1.0 >= real(s.win_sz)/s.tot_sz) | 1.0)\1 * s.ah) ) |
+ s.tw
+ s.ws := 0 < real(s.ah - s.th) | 0
+ s.cv_range := (0 < s.callback.max - s.callback.min | 1.0)
+
+end
+
+############################################################################
+# Vhthumb
+############################################################################
+
+procedure procs_Vhthumb()
+ static procs
+ initial procs := Vstd(event_Vhthumb, draw_Vhthumb, 1,
+ resize_Vidget, inrange_Vpane, init_Vhthumb,
+ couplerset_Vhthumb,,,,,set_value_Vhthumb)
+ return procs
+end
+
+procedure Vhthumb(params[])
+local frame, x, y, ins, self
+
+ if ins := Vinsert_check(params) then {
+ frame := pop(params); x := pop(params); y:= pop(params)
+ }
+ self := Vthumb_rec ! params
+ self.uid := Vget_uid()
+ self.V := procs_Vhthumb()
+ self.P := Vstd_pos()
+ self.V.init(self)
+ if \ins then VInsert(frame, self, x, y)
+ return self
+end
+
+procedure draw_Vhthumb(s)
+ local val
+
+ s.drawn := 1
+ val := integer((s.callback.value - s.callback.min) * s.ws / s.cv_range + 0.5)
+ if \s.rev then
+ val := s.ws - val
+ s.pos := val
+ BevelRectangle(s.win, s.ax + val, s.ay, s.tw, s.th)
+end
+
+procedure event_Vhthumb(s, e)
+local value, offset
+
+ if \s.callback.locked then fail
+ if e === (&lpress|&mpress|&rpress) then {
+ offset := (s.tw + 1) / 2
+ until e === (&lrelease|&mrelease|&rrelease) do {
+ value := ((&x - offset - s.ax)/(0 ~= s.ws)) * s.cv_range | 0
+ if \s.rev then
+ s.callback.V.set(s.callback, s, s.callback.max - value, s.discont)
+ else
+ s.callback.V.set(s.callback, s, s.callback.min + value, s.discont)
+ s.frame.data := s.callback.value
+ update_Vhthumb(s, 1)
+ e := Event(s.win)
+ }
+ update_Vhthumb(s)
+ if \s.discont then
+ s.callback.V.set(s.callback, s, s.callback.value)
+ return \(s.callback.value)
+ }
+end
+
+procedure update_Vhthumb(s, active)
+ local val, op, tw, th, sw, sp
+
+ val := integer((s.callback.value - s.callback.min) * s.ws / s.cv_range + 0.5)
+ if \s.rev then
+ val := s.ws - val
+
+ op := s.pos; tw := s.tw; th := s.th
+ sp := s.sp; sw := s.sw
+ EraseArea(s.win, s.ax + op, s.ay, tw, th)
+ if \active then {
+ BevelRectangle(s.win, s.ax + val, s.ay, tw, th, -2)
+ FillRectangle(s.win, s.ax + val + 2, s.ay + 2, tw - 4, th - 4)
+ }
+ else
+ BevelRectangle(s.win, s.ax + val, s.ay, tw, th)
+ s.pos := val
+end
+
+procedure set_value_Vhthumb(s, value)
+ couplerset_Vhthumb(s, s, value)
+end
+
+procedure couplerset_Vhthumb(s, caller, value)
+
+ value := numeric(value) | s.callback.min
+ if (\caller).id === V_ARROW then caller := s
+ else if value === s.callback.value then fail
+ s.frame.data := s.callback.value := value
+ if \s.drawn then
+ update_Vhthumb(s)
+end
+
+procedure init_Vhthumb(s)
+ static type
+
+ initial type := proc("type", 0) # protect attractive name
+
+ if /s.aw | /s.ah then
+ _Vbomb("must specify width and height for Vhthumb")
+ if /s.callback | type(s.callback) == "procedure" then
+ _Vbomb("Vhthumb requires a coupler variable callback")
+ s.sw := 3
+ s.sp := (s.ah - s.sw) / 2
+ s.th := s.ah
+ \s.win_sz <:= 0
+ if /s.win_sz then s.tw := s.th
+ else s.tw := ( s.th <
+ integer( ((1.0 >= real(s.win_sz)/s.tot_sz) | 1.0)\1 * s.aw) ) |
+ s.th
+ s.ws := 0 < real(s.aw - s.tw) | 0
+ s.cv_range := (0 < s.callback.max - s.callback.min | 1.0)
+end
+
+############################################################################
+# Vscrollbar_frame
+############################################################################
+
+record Vscrollbar_frame_rec(win, callback, id, aw, ah, lookup, draw, uid,
+ data, thumb, ax, ay, P, V)
+
+procedure Vscrollbar_frame(params[])
+local self, procs
+
+ procs := Vstd(event_Vframe, draw_Vframe, outline_Vscrollbar,
+ resize_Vscrollbar, inrange_Vpane, init_Vframe,
+ couplerset_Vpane, insert_Vframe, remove_Vframe,
+ lookup_Vframe, set_abs_Vframe)
+ self := Vscrollbar_frame_rec ! params
+ self.uid := Vget_uid()
+ self.V := procs
+ self.P := Vstd_pos()
+ self.V.init(self)
+ return self
+end
+
+procedure outline_Vscrollbar(self)
+ BevelRectangle(self.win, self.ax, self.ay, self.aw, self.ah, -2)
+end
+
+procedure resize_Vscrollbar(self, x, y, w, h)
+
+ resize_Vframe(self, x, y, w, h)
+
+ if self.aw > self.ah then {
+ if \self.thumb.type then { # was formerly vertical
+ self.thumb.V := procs_Vhthumb()
+ self.thumb.type := &null
+ }
+ VReformat(self, self.aw, self.ah)
+ }
+
+ else {
+ if /self.thumb.type then { # was formerly horizontal
+ self.thumb.V := procs_Vvthumb()
+ self.thumb.type := 1
+ }
+ VReformat(self, self.ah, self.aw)
+ }
+end
+
+# These are the middle-man procedures between the scrollbar frame
+# and the thumb.
+
+procedure couplerset_Vhscrollbar(s, caller, value)
+ couplerset_Vhthumb(s.thumb, caller, value)
+end
+
+procedure set_value_Vhscrollbar(s, value)
+ set_value_Vhthumb(s.thumb, value)
+ return
+end
+
+procedure couplerset_Vvscrollbar(s, caller, value)
+ couplerset_Vvthumb(s.thumb, caller, value)
+end
+
+procedure set_value_Vvscrollbar(s, value)
+ set_value_Vvthumb(s.thumb, value)
+ return
+end
+
+############################################################################
+# Vertical scrollbar
+############################################################################
+procedure Vvert_scrollbar(params[])
+local frame, x, y, ins, t, self
+
+ if ins := Vinsert_check(params) then {
+ frame := pop(params); x := pop(params); y:= pop(params)
+ }
+
+ self := Vmake_vscrollbar ! params
+ self.uid := Vget_uid()
+ if \ins then VInsert(frame, self, x, y)
+ return self
+end
+
+procedure Vmake_vscrollbar(win, callback, id, length, width,
+ min, max, inc, win_sz, discont)
+ local cv, cb, frame, up, down, thumb, tot_sz
+ local r, rev, in_max, odd
+ static type
+
+ initial type := proc("type", 0) # protect attractive name
+
+ Vwin_check(win, "Vvert_scrollbar()")
+ if (\win_sz, not numeric(win_sz) | win_sz < 0 ) then
+ _Vbomb("negative or non-numeric window_size parameter to Vvert_scrollbar()")
+ if (\inc, not numeric(inc) | inc < 0 ) then
+ _Vbomb("negative or non-numeric increment parameter to Vvert_scrollbar()")
+ if (\length, not numeric(length) ) then
+ _Vbomb("invalid length parameter to Vvert_scrollbar()")
+ if (\width, not numeric(width) ) then
+ _Vbomb("invalid width parameter to Vvert_scrollbar()")
+
+ /width := VSlider_DefWidth
+ /length := VSlider_DefLength
+ width <:= VSlider_MinWidth
+ length <:= VSlider_MinAspect * width
+ /min := 0
+ /max := 1.0
+ rev := 1
+ if max < min then { max :=: min; rev := &null }
+ in_max := max
+ max -:= (\win_sz | 0)
+ max <:= min
+ tot_sz := 0 < abs(in_max-min) | 1
+ r := (type(min|max) == "real", 1)
+ if (not numeric(\inc) ) | /inc then
+ inc := 0.1*abs(max-min)
+ (/r, inc := integer(inc), inc <:= 1)
+
+ cv := Vrange_coupler(min, max, , inc)
+ frame := Vscrollbar_frame(win, cv, id, width, length)
+ Varrow(frame, 2, 2, win, cv, width - 4, width - 4, rev, "n")
+ odd := width % 2
+ thumb := Vvthumb(frame, 2, width - odd,
+ win, cv, id, width - 4, length - 2 * width + 1 + odd,
+ win_sz, tot_sz, discont)
+ Varrow(frame, 2, length - width + 2, win, cv, width - 4, width - 4, rev, "s")
+
+ thumb.rev := rev
+ cv.V.add_client(cv, thumb)
+ add_clients_Vinit(cv, callback, thumb)
+
+ thumb.frame := frame
+ frame.thumb := thumb
+ frame.V.couplerset := couplerset_Vvscrollbar
+ frame.V.set_value := set_value_Vvscrollbar
+
+ return frame
+end
+
+############################################################################
+# Horizontal scrollbar
+############################################################################
+procedure Vhoriz_scrollbar(params[])
+local frame, x, y, ins, t, self
+
+ if ins := Vinsert_check(params) then {
+ frame := pop(params); x := pop(params); y:= pop(params)
+ }
+
+ self := Vmake_hscrollbar ! params
+ self.uid := Vget_uid()
+ if \ins then VInsert(frame, self, x, y)
+ return self
+end
+
+procedure Vmake_hscrollbar(win, callback, id, length, width,
+ min, max, inc, win_sz, discont)
+ local cv, cb, frame, up, down, thumb, tot_sz
+ local r, rev, in_max, odd
+ static type
+
+ initial type := proc("type", 0) # protect attractive name
+
+ Vwin_check(win, "Vhoriz_scrollbar().")
+ if (\win_sz, not numeric(win_sz) | win_sz < 0 ) then
+ _Vbomb("negative or non-numeric window_size parameter to Vhoriz_scrollbar()")
+ if (\inc, not numeric(inc) | inc < 0 ) then
+ _Vbomb("negative or non-numeric increment parameter to Vhoriz_scrollbar()")
+ if (\length, not numeric(length) ) then
+ _Vbomb("invalid length parameter to Vhoriz_scrollbar()")
+ if (\width, not numeric(width) ) then
+ _Vbomb("invalid width parameter to Vhoriz_scrollbar()")
+
+ /width := VSlider_DefWidth
+ /length := VSlider_DefLength
+ width <:= VSlider_MinWidth
+ length <:= VSlider_MinAspect * width
+ /min := 0
+ /max := 1.0
+ if max < min then {max :=: min; rev := 1 }
+ in_max := max
+ max -:= (\win_sz | 0)
+ max <:= min
+ tot_sz := 0 < abs(in_max-min) | 1
+ r := (type(min|max) == "real", 1)
+ if (not numeric(\inc) ) | /inc then
+ inc := 0.1*abs(max-min)
+ (/r, inc := integer(inc), inc <:= 1)
+
+ cv := Vrange_coupler(min, max, , inc)
+ frame := Vscrollbar_frame(win, cv, id, length, width)
+ Varrow(frame, 2, 2, win, cv, width - 4, width - 4, rev, "w")
+ odd := width % 2
+ thumb := Vhthumb(frame, width - odd, 2,
+ win, cv, id, length - 2 * width + 1 + odd, width - 4,
+ win_sz, tot_sz, discont)
+ Varrow(frame, length - width + 2, 2, win, cv, width-4, width-4, rev, "e")
+
+ thumb.rev := rev
+ cv.V.add_client(cv, thumb)
+ add_clients_Vinit(cv, callback, thumb)
+
+ thumb.frame := frame
+ frame.thumb := thumb
+ frame.V.couplerset := couplerset_Vhscrollbar
+ frame.V.set_value := set_value_Vhscrollbar
+
+ return frame
+end
+
+############################################################################
+# reformatting procedures. Will just reformat width and length.
+############################################################################
+procedure reformat_Vvthumb(s, length, width)
+
+ s.P.w := s.aw := \width
+ s.P.h := s.ah := \length
+ s.sp := (s.aw - s.sw) / 2
+ s.tw := s.aw
+ if /s.win_sz then s.th := s.tw
+ else s.th := ( s.tw <
+ integer( ((1.0 >= real(s.win_sz)/s.tot_sz) | 1.0)\1 * s.ah) ) |
+ s.tw-1
+ s.ws := 0 < real(s.ah - s.th - 2) | 0
+end
+
+procedure reformat_Vhthumb(s, length, width)
+
+ s.P.w := s.aw := length
+ s.P.h := s.ah := width
+ s.sp := (s.ah - s.sw) / 2
+ s.th := s.ah
+ if /s.win_sz then s.tw := s.th
+ else s.tw := ( s.th <
+ integer( ((1.0 >= real(s.win_sz)/s.tot_sz) | 1.0)\1 * s.aw) ) |
+ s.th-1
+ s.ws := 0 < real(s.aw - s.tw - 2) | 0
+end
+
+procedure Vreformat_vscrollbar(self, length, width)
+ local up, down, thumb
+
+ /width := self.aw
+ /length := self.ah
+ self.aw := self.P.w := width
+ self.ah := self.P.h := length
+
+ up := self.lookup[1]
+ thumb := self.lookup[2]
+ down := self.lookup[3]
+
+ VRemove(self, up, 1)
+ VRemove(self, thumb, 1)
+ VRemove(self, down, 1)
+
+ up.dir := "n"
+ down.aw := down.ah := up.aw := up.ah :=
+ down.P.w := down.P.h := up.P.w := up.P.h := width
+ down.r := up.r := (width - 4) / 2
+ down.dir := "s"
+
+ reformat_Vvthumb(thumb, length - 2 * width + 2, width - 4)
+ VInsert(self, up, 2, 2)
+ VInsert(self, thumb, 2, width)
+ VInsert(self, down, 2, width + thumb.ah)
+
+end
+
+procedure Vreformat_hscrollbar(self, length, width)
+ local left, right, thumb
+
+ /width := self.ah
+ /length := self.aw
+ self.aw := self.P.w := length
+ self.ah := self.P.h := width
+
+ left := self.lookup[1]
+ thumb := self.lookup[2]
+ right := self.lookup[3]
+
+ VRemove(self, left, 1)
+ VRemove(self, thumb, 1)
+ VRemove(self, right, 1)
+
+ left.dir := "w"
+ left.aw := left.ah := right.aw := right.ah :=
+ left.P.w := left.P.h := right.P.w := right.P.h := width
+ left.r := right.r := (width - 4) / 2
+ right.dir := "e"
+
+ reformat_Vhthumb(thumb, length - 2 * width + 2, width - 4)
+ VInsert(self, left, 2, 2)
+ VInsert(self, thumb, width, 2)
+ VInsert(self, right, width + thumb.aw, 2)
+end
+
+############################################################################
+# interface procedure for Vreformat
+############################################################################
+procedure VReformat(scrollbar, length, width)
+ static type
+
+ initial type := proc("type", 0) # protect attractive name
+
+ if /scrollbar | type(scrollbar) ~== "Vscrollbar_frame_rec" then
+ _Vbomb("invalid scrollbar parameter to VReformat()")
+
+ if \(scrollbar.thumb.type) then
+ Vreformat_vscrollbar(scrollbar, length, width)
+ else
+ Vreformat_hscrollbar(scrollbar, length, width)
+end
diff --git a/ipl/gprocs/vsetup.icn b/ipl/gprocs/vsetup.icn
new file mode 100644
index 0000000..73d4b3a
--- /dev/null
+++ b/ipl/gprocs/vsetup.icn
@@ -0,0 +1,250 @@
+############################################################################
+#
+# File: vsetup.icn
+#
+# Subject: Procedures for vidget application setup
+#
+# Author: Gregg M. Townsend
+#
+# Date: October 9, 1997
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# vsetup(win, cbk, wlist[]) initializes a set of widgets according to
+# a list of specifications created by the interface editor VIB.
+#
+# win can be an existing window, a list of command arguments to be
+# passed to Window(), null, or omitted. In the latter three cases
+# a new window is opened if &window is null.
+#
+# cbk is a default callback routine to be used when no callback is
+# specified for a particular vidget.
+#
+# wlist is a list of specifications; the first must be the Sizer and
+# the last may be null. Each specification is itself a list consisting
+# of a specification string, a callback routine, and an optional list
+# of additional specifications. Specification strings vary by vidget
+# type, but the general form is "ID:type:style:n:x,y,w,h:label".
+#
+# vsetup returns a table of vidgets indexed by vidget ID.
+# The root vidget is included with the ID of "root".
+#
+############################################################################
+#
+# Links: graphics,
+# vidgets, vslider, vmenu, vscroll, vtext, vbuttons, vradio, vlist
+#
+############################################################################
+
+link graphics
+link vidgets
+link vslider
+link vmenu
+link vscroll
+link vtext
+link vbuttons
+link vradio
+link vlist
+
+record VS_rec(var, typ, sty, num, x, y, w, h, lbl, cbk, etc)
+
+
+## vsetup(win, cbk, wlist[]) -- set up vidgets and return table of handles
+#
+# win is an existing window, or a list of command args for Window(), or &null.
+# cbk is a callback routine to use when a vidget's callback is null.
+# wlist is a list of vidget specs as constructed by vib (or uix).
+
+procedure vsetup(args[])
+ local r, wlbl, root, vtable, wspec, alist, win, winargs, cbk
+ static type
+
+ initial type := proc("type", 0) # protect attractive name
+
+ case type(args[1]) of { # check for window or arglist argument
+ "window": win := get(args)
+ "list": winargs := get(args)
+ "null": get(args)
+ }
+ /win := &window
+
+ if type(args[1]) ~== "list" then # check for callback argument
+ cbk := get(args)
+
+ wspec := get(args) # first spec gives window size
+
+ if /win then { # if we don't have a window
+ r := VS_crack(wspec) | _Vbomb("bad specification in vsetup")
+ wlbl := ("" ~== r.lbl) |
+ (&progname ? {while tab(upto('/')+1); tab(upto('.')|0)})
+ alist := []
+ put(alist, "width=" || (r.x + r.w))
+ put(alist, "height=" || (r.y + r.h))
+ put(alist, "label=" || wlbl)
+ put(alist, \winargs)
+ win := Window ! alist
+ }
+
+ VSetFont(win) # set correct text font
+
+ vtable := table() # make table of handles
+ vtable["root"] := root := Vroot_frame(win) # insert root frame
+ every r := VS_crack(\!args, cbk) do
+ vtable[r.var] := VS_obj(win, root, r) # insert other vidgets
+ VResize(root) # configure and realize vidgets
+ root.id := "root"
+ return vtable # return table
+end
+
+
+
+## VS_crack(wspec, cbk) -- extract elements of spec and put into record
+#
+# cbk is a default callback to use if the spec doesn't supply one.
+
+procedure VS_crack(wspec, cbk)
+ local r, f
+
+ r := VS_rec()
+ (get(wspec) | fail) ? {
+ r.var := tab(upto(':')) | fail; move(1)
+ r.typ := tab(upto(':')) | fail; move(1)
+ r.sty := tab(upto(':')) | fail; move(1)
+ r.num := tab(upto(':')) | fail; move(1)
+ r.x := tab(upto(',')) | fail; move(1)
+ r.y := tab(upto(',')) | fail; move(1)
+ r.w := tab(upto(',')) | fail; move(1)
+ r.h := tab(upto(':')) | fail; move(1)
+ r.lbl := tab(0)
+ }
+ r.cbk := \get(wspec) | cbk
+ r.etc := get(wspec)
+ return r
+end
+
+
+
+## VS_obj(win, root, r) -- create vidget depending on type
+
+procedure VS_obj(win, root, r)
+ local obj, gc, p, lo, hi, iv, args
+ static image
+
+ initial image := proc("image", 0)
+
+ case r.typ of {
+ "Label" | "Message": {
+ obj := Vmessage(win, r.lbl)
+ VInsert(root, obj, r.x, r.y, r.w, r.h)
+ obj.id := r.var
+ }
+ "Line": {
+ obj := Vline(win, r.x, r.y, r.w, r.h)
+ obj.id := r.var
+ VInsert(root, obj)
+ }
+ "Rect": {
+ if r.sty == "" then
+ if integer(r.num) > 0 then
+ r.sty := "grooved"
+ else
+ r.sty := "invisible"
+ obj := Vpane(win, r.cbk, r.var, r.sty)
+ VInsert(root, obj, r.x, r.y, r.w, r.h)
+ }
+ "Check": {
+ obj := Vcheckbox(win, r.cbk, r.var, r.w)
+ VInsert(root, obj, r.x, r.y, r.w, r.h)
+ }
+ "Button": {
+ if r.num == "1" then
+ p := Vtoggle
+ else
+ p := Vbutton
+ obj := p(win, r.lbl, r.cbk, r.var, r.sty, r.w, r.h)
+ VInsert(root, obj, r.x, r.y)
+ }
+ "Choice": {
+ obj := Vradio_buttons(win, r.etc, r.cbk, r.var, V_DIAMOND_NO)
+ VInsert(root, obj, r.x, r.y)
+ }
+ "Slider" | "Scrollbar" : {
+ r.lbl ? {
+ lo := numeric(tab(upto(',')))
+ move(1)
+ hi := numeric(tab(upto(',')))
+ move(1)
+ iv := numeric(tab(0))
+ }
+ if r.num == "" then
+ r.num := &null
+ obj := case (r.sty || r.typ) of {
+ "hSlider":
+ Vhoriz_slider(win, r.cbk, r.var, r.w, r.h, lo, hi, iv, r.num)
+ "vSlider":
+ Vvert_slider(win, r.cbk, r.var, r.h, r.w, hi, lo, iv, r.num)
+ "hScrollbar":
+ Vhoriz_scrollbar(win, r.cbk, r.var, r.w, r.h, lo, hi, , , r.num)
+ "vScrollbar":
+ Vvert_scrollbar(win, r.cbk, r.var, r.h, r.w, hi, lo, , , r.num)
+ }
+ VSetState(obj, iv) # needed for scrollbars
+ VInsert(root, obj, r.x, r.y)
+ }
+ "Text": {
+ obj := Vtext(win, r.lbl, r.cbk, r.var, r.num)
+ VInsert(root, obj, r.x, r.y)
+ }
+ "Menu": {
+ obj := Vmenu_bar(win, r.lbl, VS_submenu(win, r.etc, r.cbk))
+ obj.id := obj.lookup[1].id := r.var
+ VInsert(root, obj, r.x, r.y)
+ }
+ "List": {
+ if integer(r.num) > 0 then
+ r.num := 1
+ else
+ r.num := &null
+ obj := Vlist(win, r.cbk, r.var, [], r.num, r.w, r.h, r.sty)
+ VInsert(root, obj, r.x, r.y)
+ }
+ "List": {
+ if integer(r.num) > 0 then
+ r.num := 1
+ else
+ r.num := &null
+ obj := Vlist(win, r.cbk, r.var, [], r.num, r.w, r.h, r.sty)
+ VInsert(root, obj, r.x, r.y)
+ }
+ default: {
+ _Vbomb("unrecognized object in vsetup: " || image(r.typ))
+ fail
+ }
+ }
+ return obj
+end
+
+
+
+## VS_submenu(win, lst, cbk) -- create submenu vidget
+
+procedure VS_submenu(win, lst, cbk)
+ local a, c, lbl
+ static type
+
+ initial type := proc("type", 0) # protect attractive name
+
+ a := [win]
+ while *lst > 0 do {
+ put(a, get(lst))
+ if type(lst[1]) == "list" then
+ put(a, VS_submenu(win, get(lst), cbk))
+ else
+ put(a, cbk)
+ }
+ return Vsub_menu ! a
+end
diff --git a/ipl/gprocs/vslider.icn b/ipl/gprocs/vslider.icn
new file mode 100644
index 0000000..5ca6e59
--- /dev/null
+++ b/ipl/gprocs/vslider.icn
@@ -0,0 +1,387 @@
+############################################################################
+#
+# File: vslider.icn
+#
+# Subject: Procedures for sliders
+#
+# Authors: Jon Lipp and Gregg M. Townsend
+#
+# Date: April 1, 1997
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# Vidgets defined in this file:
+# Vvslider
+# Vhslider
+#
+# Utility procedures in this file:
+# Vvert_slider()
+# Vhoriz_slider()
+#
+############################################################################
+#
+# Includes: vdefns.icn
+#
+############################################################################
+#
+# Links: vidgets
+#
+############################################################################
+
+link vidgets
+
+$include "vdefns.icn"
+
+record Vslider_rec (win, callback, id, aw, ah, discont,
+ ax, ay, data, pad, ws, cv_range, rev, pos, uid, drawn, P, V)
+
+############################################################################
+# Vvslider
+############################################################################
+
+procedure procs_Vvslider()
+ static procs
+ initial procs := Vstd(event_Vvslider, draw_Vvslider, outline_Vslider,
+ resize_Vvslider, inrange_Vpane, init_Vvslider,
+ couplerset_Vvslider,,,,,set_value_Vvslider)
+ return procs
+end
+
+procedure Vvslider(params[])
+ local self
+
+ self := Vslider_rec ! params[1:7|0]
+ Vwin_check(self.win, "Vvert_slider()")
+ if (\self.aw, not numeric(self.aw) ) then
+ _Vbomb("invalid width parameter to Vvert_slider()")
+ if (\self.ah, not numeric(self.ah) ) then
+ _Vbomb("invalid length parameter to Vvert_slider()")
+
+ self.uid := Vget_uid()
+ self.V := procs_Vvslider()
+ self.P := Vstd_pos()
+
+ self.V.init(self)
+ return self
+end
+
+procedure draw_Vvslider(s)
+local val
+
+ s.drawn := 1
+ s.V.outline(s)
+ val := (s.callback.value - s.callback.min) * s.ws / s.cv_range
+ if \s.rev then
+ val := s.ws - val + s.pad
+ else
+ val +:= s.pad
+ s.pos := val
+ draw_Vvslider_bar(s)
+end
+
+procedure event_Vvslider(s, e)
+local value
+
+ if \s.callback.locked then fail
+ if e === (&lpress|&mpress|&rpress) then
+ until e === (&lrelease|&mrelease|&rrelease) do {
+ value := ((&y - s.ay - s.pad) / s.ws) * s.cv_range
+ if \s.rev then
+ s.callback.V.set(s.callback, s, s.callback.max - value, s.discont)
+ else
+ s.callback.V.set(s.callback, s, s.callback.min + value, s.discont)
+ s.data := s.callback.value
+ update_Vvslider(s, 1)
+ e := Event(s.win)
+ }
+ else
+ fail # not our event
+ if \s.discont then
+ s.callback.V.set(s.callback, s, s.callback.value)
+ update_Vvslider(s)
+ return s.callback.value
+end
+
+procedure update_Vvslider(s, active)
+local val
+
+ val := (s.callback.value - s.callback.min) * s.ws / s.cv_range
+ if \s.rev then
+ val := s.ws - val + s.pad
+ else
+ val +:= s.pad
+ s.pos := val
+ draw_Vvslider_bar(s, active)
+ return s.callback.value
+end
+
+procedure draw_Vvslider_bar(s, active)
+local ww, d
+
+ ww := s.aw - 4
+ EraseArea(s.win, s.ax + 2, s.ay + 2, ww, s.ah - 4)
+ if \active then {
+ d := -1
+ FillRectangle(s.win, s.ax + 4, s.ay + s.pos - ww + 2, ww - 4, 2 * ww - 4)
+ }
+ else
+ d := 1
+ BevelRectangle(s.win, s.ax + 2, s.ay + s.pos - ww, ww, 2 * ww, d)
+ BevelRectangle(s.win, s.ax + 3, s.ay + s.pos, ww - 2, 1 - ww, d)
+ BevelRectangle(s.win, s.ax + 3, s.ay + s.pos, ww - 2, ww - 1, d)
+end
+
+procedure set_value_Vvslider(s, value)
+ couplerset_Vvslider(s, , value)
+ return
+end
+
+procedure couplerset_Vvslider(s, caller, value)
+
+ value := numeric(value) | s.callback.min
+ if s.callback.value === value then fail
+ s.callback.V.set(s.callback, caller, value)
+ s.data := s.callback.value
+ if \s.drawn then
+ update_Vvslider(s)
+end
+
+procedure init_Vvslider(s)
+ static type
+
+ initial type := proc("type", 0) # protect attractive name
+
+ /s.aw := VSlider_DefWidth
+ /s.ah := VSlider_DefLength
+ s.aw <:= VSlider_MinWidth
+ s.ah <:= VSlider_MinAspect * s.aw
+ if /s.callback | type(s.callback) == "procedure" then
+ _Vbomb("Vvslider requires a coupler variable callback")
+ s.pad := s.aw - 2
+ s.ws := real(s.ah - 2 * s.pad)
+ s.cv_range := s.callback.max - s.callback.min
+ init_Vpane(s)
+end
+
+procedure resize_Vvslider(s, x, y, w, h)
+
+ resize_Vidget(s, x, y, w, h)
+ if s.aw > s.ah then {
+ s.V := procs_Vhslider()
+ return s.V.resize(s, x, y, w, h)
+ }
+ s.pad := s.aw - 2
+ s.ws := real(s.ah - 2 * s.pad)
+ s.cv_range := s.callback.max - s.callback.min
+end
+
+
+############################################################################
+# Vhslider
+############################################################################
+
+procedure procs_Vhslider()
+ static procs
+
+ initial procs := Vstd(event_Vhslider, draw_Vhslider, outline_Vslider,
+ resize_Vhslider, inrange_Vpane, init_Vhslider,
+ couplerset_Vhslider,,,,,set_value_Vhslider)
+ return procs
+end
+
+procedure Vhslider(params[])
+ local self
+
+ self := Vslider_rec ! params[1:7|0]
+ self.aw :=: self.ah
+ Vwin_check(self.win, "Vhoriz_slider()")
+ if (\self.ah, not numeric(self.ah) ) then
+ _Vbomb("invalid width parameter to Vhoriz_slider()")
+ if (\self.aw, not numeric(self.aw) ) then
+ _Vbomb("invalid length parameter to Vhoriz_slider()")
+
+ self.uid := Vget_uid()
+ self.V := procs_Vhslider()
+ self.P := Vstd_pos()
+
+ self.V.init(self)
+ return self
+end
+
+procedure draw_Vhslider(s)
+local val
+
+ s.drawn := 1
+ s.V.outline(s)
+ val := (s.callback.value - s.callback.min) * s.ws / s.cv_range
+ if \s.rev then
+ val := s.ws - val + s.pad
+ else
+ val +:= s.pad
+ s.pos := val
+ draw_Vhslider_bar(s)
+end
+
+procedure event_Vhslider(s, e)
+local value
+
+ if \s.callback.locked then fail
+ if e === (&lpress|&mpress|&rpress) then
+ until e === (&lrelease|&mrelease|&rrelease) do {
+ value := ((&x - s.ax - s.pad) / s.ws) * s.cv_range
+ if \s.rev then
+ s.callback.V.set(s.callback, s, s.callback.max - value, s.discont)
+ else
+ s.callback.V.set(s.callback, s, s.callback.min + value, s.discont)
+ s.data := s.callback.value
+ update_Vhslider(s, 1)
+ e := Event(s.win)
+ }
+ else
+ fail # not our event
+ if \s.discont then
+ s.callback.V.set(s.callback, s, s.callback.value)
+ update_Vhslider(s)
+ return s.callback.value
+end
+
+procedure update_Vhslider(s, active)
+local val
+
+ val := (s.callback.value - s.callback.min) * s.ws / s.cv_range
+ if \s.rev then
+ val := s.ws - val + s.pad
+ else
+ val +:= s.pad
+ s.pos := val
+ draw_Vhslider_bar(s, active)
+ return s.callback.value
+end
+
+procedure draw_Vhslider_bar(s, active)
+local hh, d
+
+ hh := s.ah - 4
+ EraseArea(s.win, s.ax + 2, s.ay + 2, s.aw - 4, hh)
+ if \active then {
+ d := -1
+ FillRectangle(s.win, s.ax + s.pos - hh + 2, s.ay + 4, 2 * hh - 4, hh - 4)
+ }
+ else
+ d := 1
+ BevelRectangle(s.win, s.ax + s.pos - hh, s.ay + 2, 2 * hh, hh, d)
+ BevelRectangle(s.win, s.ax + s.pos, s.ay + 3, 1 - hh, hh - 2, d)
+ BevelRectangle(s.win, s.ax + s.pos, s.ay + 3, hh - 1, hh - 2, d)
+end
+
+procedure set_value_Vhslider(s, value)
+ couplerset_Vhslider(s, , value)
+ return
+end
+
+procedure couplerset_Vhslider(s, caller, value)
+
+## break a cycle in callbacks by checking value.
+ value := numeric(value) | s.callback.min
+ if s.callback.value === value then fail
+ s.callback.V.set(s.callback, caller, value)
+ s.data := s.callback.value
+ if \s.drawn then
+ update_Vhslider(s)
+end
+
+procedure init_Vhslider(s)
+ static type
+
+ initial type := proc("type", 0) # protect attractive name
+
+ /s.ah := VSlider_DefWidth
+ /s.aw := VSlider_DefLength
+ s.ah <:= VSlider_MinWidth
+ s.aw <:= VSlider_MinAspect * s.ah
+ if /s.callback | type(s.callback) == "procedure" then
+ _Vbomb("Vhslider requires a coupler variable callback")
+ s.pad := s.ah - 2
+ s.ws := real(s.aw - 2 * s.pad)
+ s.cv_range := s.callback.max - s.callback.min
+ init_Vpane(s)
+end
+
+procedure resize_Vhslider(s, x, y, w, h)
+
+ resize_Vidget(s, x, y, w, h)
+ if s.aw < s.ah then {
+ s.V := procs_Vvslider()
+ return s.V.resize(s, x, y, w, h)
+ }
+ s.pad := s.ah - 2
+ s.ws := real(s.aw - 2 * s.pad)
+ s.cv_range := s.callback.max - s.callback.min
+end
+
+############################################################################
+# Utilities - slider wrapper procedures.
+############################################################################
+
+procedure outline_Vslider(s)
+ BevelRectangle(s.win, s.ax, s.ay, s.aw, s.ah, -2) # draw trough
+end
+
+procedure Vmake_slider(slider_type, w, callback, id, length, width,
+ min, max, init, discontinuous)
+local cv, sl, cb, t
+ static type
+
+ initial type := proc("type", 0) # protect attractive name
+
+ /min := 0
+ /max := 1.0
+ if not numeric(min) | not numeric(max) | (\init, not numeric(init)) then
+ _Vbomb("non-numeric min, max, or init parameter passed to Vxxxxx_slider()")
+ if max < min then { min :=: max; t := 1 }
+ cv := Vrange_coupler(min, max, init)
+ sl := slider_type(w, cv, id, width, length, discontinuous)
+ sl.rev := t
+
+ add_clients_Vinit(cv, callback, sl)
+ return sl
+end
+
+############################################################################
+# Vvert_slider(w, callback, id, width, length, lower_bound, upper_bound,
+# initial_value)
+############################################################################
+procedure Vvert_slider(params[])
+local frame, x, y, ins, t, self
+
+ if ins := Vinsert_check(params) then {
+ frame := pop(params); x := pop(params); y:= pop(params)
+ }
+
+ params[6] :=: params[7]
+ push(params, Vvslider)
+ self := Vmake_slider ! params
+ if \ins then VInsert(frame, self, x, y)
+ return self
+end
+
+############################################################################
+# Vhoriz_slider(w, callback, id, width, length, left_bound, right_bound,
+# initial_value)
+############################################################################
+procedure Vhoriz_slider(params[])
+local frame, x, y, ins, self
+
+ if ins := Vinsert_check(params) then {
+ frame := pop(params); x := pop(params); y:= pop(params)
+ }
+
+ push(params, Vhslider)
+ self := Vmake_slider ! params
+ if \ins then VInsert(frame, self, x, y)
+ return self
+end
diff --git a/ipl/gprocs/vstd.icn b/ipl/gprocs/vstd.icn
new file mode 100644
index 0000000..4365abb
--- /dev/null
+++ b/ipl/gprocs/vstd.icn
@@ -0,0 +1,146 @@
+############################################################################
+#
+# File: vstd.icn
+#
+# Subject: Procedures for standard lookups
+#
+# Author: Jon Lipp
+#
+# Date: June 23, 2000
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# Utility procedures in this file:
+# VInit()
+# null_proc()
+# Vget_uid()
+# _Vbomb()
+# Vinsert_check()
+# Vwin_check()
+#
+############################################################################
+
+record Vstd(event, draw, outline, resize, inrange, init, couplerset,
+ insert, remove, lookup, set_abs, set_value )
+
+record Vstd_coupler(set, add_client, init, unset, toggle, eval)
+
+record Vstd_dialog(open_dialog, register, format, unregister, entries, focus,
+ text_entries, text_lu)
+
+#
+# Used by menus, buttons
+#
+record Vstd_draw(draw_off, draw_on, init, space, CS, CP, outline,
+ basex, basey, pick, format)
+
+#
+# type is non-null for vertical; &null for horizontal.
+#
+record Vstd_scrollbar(sp, sw, tw, th, ws, cv_range, oldpos, rev,
+ frame, drawn, type)
+record Vstd_pos(x, y, w, h)
+
+global Vrecset, Vcoupler_recset
+global V_TEXT_PAD, V_NO_RB_FOCUS, V_DRAGGING, V_FAIL
+global V_IMAGE, V_IMAGE_NO, V_RECT, V_2D, V_CHECK, V_CIRCLE, V_DIAMOND, V_XBOX
+global V_RECT_NO, V_2D_NO, V_CHECK_NO, V_CIRCLE_NO, V_DIAMOND_NO, V_XBOX_NO
+global V_CANCEL, V_OK, V_NEXT, V_PREVIOUS
+global V_ARROW, V_COUPLER, V_DUMMY_ID
+
+procedure null_proc()
+end
+
+procedure VInit()
+initial {
+
+# Define the cset of all allowable vidget record types.
+ Vrecset := set(["Vbutton_rec", "Vcheckbox_rec",
+ "Vline_rec", "Vdialog_frame_rec",
+ "Vframe_rec", "Vmenu_item_rec",
+ "Vmenu_frame_rec", "Vradio_entry_rec", "Vradio_frame_rec",
+ "Vpull_down_button_rec", "Vpane_rec", "Varrow_rec",
+ "Vthumb_rec", "Vscrollbar_frame_rec",
+ "Vslider_rec", "Vtext_rec", "Vgrid_rec"])
+
+ Vcoupler_recset := set(["Vcoupler_rec", "Vrange_coupler_rec"])
+
+# The padding in a Vtext_in between the data outline and the data text.
+ V_TEXT_PAD := 4
+
+# Used for button styles.
+ V_RECT := V_2D := -690402
+ V_CHECK := -690403
+ V_CIRCLE := -690404
+ V_RECT_NO := V_2D_NO := -690406
+ V_CHECK_NO := -690407
+ V_CIRCLE_NO := -690408
+ V_XBOX := -690409
+ V_XBOX_NO := -690410
+ V_DIAMOND := -690411
+ V_DIAMOND_NO := -690412
+ V_IMAGE := -690413
+ V_IMAGE_NO := -690414
+
+# Used for communication between a dialog box and its contents.
+ V_CANCEL := -690417
+ V_OK := -690418
+ V_NEXT := -690419
+ V_PREVIOUS := -690420
+
+# Used for telling a radio button frame *not* to turn on a default
+# selection.
+ V_NO_RB_FOCUS := -690421
+
+# Used in menus.
+ V_DRAGGING := -690422
+ V_FAIL := -690423
+
+# Lets a thumb know an arrow called its couplerset.
+ V_ARROW := -690424
+ V_COUPLER := -690425
+ V_DUMMY_ID := -690426
+}
+
+end
+
+procedure Vget_uid()
+ static uid
+ initial uid := 0
+
+ uid +:= 1
+ return uid
+end
+
+procedure _Vbomb(str)
+
+ write(&errout, "Vidget error: ", str)
+ runerr(600)
+
+end
+
+procedure Vinsert_check(p)
+ static type
+
+ initial type := proc("type", 0) # protect attractive name
+
+ if type(p[1]) ? find("frame") then {
+ if not (numeric(p[2]), numeric(p[3])) then
+ _Vbomb("invalid x or y coordinate to VInsert()")
+ return 1
+ }
+ else fail
+end
+
+procedure Vwin_check(win, caller)
+ static type
+
+ initial type := proc("type", 0) # protect attractive name
+
+ if not (type(win) ? ="window") then
+ _Vbomb("invalid window parameter to "|| caller)
+end
diff --git a/ipl/gprocs/vstyle.icn b/ipl/gprocs/vstyle.icn
new file mode 100644
index 0000000..cf9ad90
--- /dev/null
+++ b/ipl/gprocs/vstyle.icn
@@ -0,0 +1,363 @@
+############################################################################
+#
+# File: vstyle.icn
+#
+# Subject: Procedures for drawing buttons
+#
+# Authors: Jon Lipp and Gregg M. Townsend
+#
+# Date: August 14, 1996
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# Utility procedures in this file:
+# Vset_style()
+#
+############################################################################
+
+link imscolor
+
+procedure Vset_style (vid, style)
+
+ style := integer(style) | case style of {
+ &null: V_RECT
+ "regular": V_RECT
+ "regularno": V_RECT_NO
+ "check": V_CHECK
+ "checkno": V_CHECK_NO
+ "circle": V_CIRCLE
+ "circleno": V_CIRCLE_NO
+ "diamond": V_DIAMOND
+ "diamondno": V_DIAMOND_NO
+ "xbox": V_XBOX
+ "xboxno": V_XBOX_NO
+ "image": V_IMAGE
+ "imageno": V_IMAGE_NO
+ default: _Vbomb("invalid style parameter")
+ }
+
+ vid.style := style
+ case style of {
+ V_RECT :
+ vid.D := Vstd_draw(draw_off_rect, draw_on_rect, init_rect)
+ V_CHECK :
+ vid.D := Vstd_draw(draw_off_check, draw_on_check, init_check)
+ V_CIRCLE :
+ vid.D := Vstd_draw(draw_off_circle, draw_on_circle, init_circle)
+ V_DIAMOND:
+ vid.D := Vstd_draw(draw_off_diamond, draw_on_diamond, init_diamond)
+ V_XBOX :
+ vid.D := Vstd_draw(draw_off_xbox, draw_on_xbox, init_xbox)
+ V_IMAGE :
+ vid.D := Vstd_draw(draw_off_image, draw_on_image, init_image)
+ V_RECT_NO : {
+ vid.D := Vstd_draw(draw_off_rect, draw_on_rect, init_rect)
+ vid.D.outline := 1
+ }
+ V_CHECK_NO : {
+ vid.D := Vstd_draw(draw_off_check, draw_on_check, init_check)
+ vid.D.outline := 1
+ }
+ V_CIRCLE_NO : {
+ vid.D := Vstd_draw(draw_off_circle, draw_on_circle, init_circle)
+ vid.D.outline := 1
+ }
+ V_DIAMOND_NO: {
+ vid.D := Vstd_draw(draw_off_diamond, draw_on_diamond, init_diamond)
+ vid.D.outline := 1
+ }
+ V_XBOX_NO : {
+ vid.D := Vstd_draw(draw_off_xbox, draw_on_xbox, init_xbox)
+ vid.D.outline := 1
+ }
+ V_IMAGE_NO : {
+ vid.D := Vstd_draw(draw_off_image, draw_on_image, init_image)
+ vid.D.outline := 1
+ }
+ default: _Vbomb("invalid style parameter")
+ }
+end
+
+
+procedure init_xbox(s)
+ # nothing to do
+end
+
+procedure draw_off_xbox(s)
+ if /s.D.outline then {
+ EraseArea(s.win, s.ax + 2, s.ay + 2, s.aw - 4, s.ah - 4)
+ BevelRectangle(s.win, s.ax, s.ay, s.aw, s.ah, 2)
+ }
+ else
+ EraseArea(s.win, s.ax, s.ay, s.aw, s.ah)
+end
+
+procedure draw_on_xbox(s)
+ WAttrib(s.win, "linewidth=2")
+ DrawSegment(s.win, s.ax + 4, s.ay + 4, s.ax + s.aw - 4, s.ay + s.ah - 4,
+ s.ax + s.aw - 4, s.ay + 4, s.ax + 4, s.ay + s.ah - 4)
+ WAttrib(s.win, "linewidth=1")
+ if /s.D.outline then
+ BevelRectangle(s.win, s.ax, s.ay, s.aw, s.ah, -2)
+end
+
+
+procedure init_rect(s)
+ local TW, FH, ascent, descent
+
+ /s.s := ""
+ TW := TextWidth(s.win, s.s)
+ ascent := WAttrib(s.win, "ascent")
+ descent := WAttrib(s.win, "descent")
+ FH := ascent + descent
+ /s.aw := TW + 8
+ /s.ah := FH + 8
+
+ s.aw := 0 < s.aw | 1
+ s.ah := 0 < s.ah | 1
+
+ s.D.basex := (s.aw - TW - 1) / 2
+ s.D.basey := (s.ah - FH) / 2 + ascent
+end
+
+procedure draw_off_rect(s)
+ EraseArea(s.win, s.ax, s.ay, s.aw, s.ah)
+ GotoXY(s.win, s.ax+s.D.basex, s.ay+s.D.basey)
+ writes(s.win, s.s)
+ if /s.D.outline then
+ BevelRectangle(s.win, s.ax, s.ay, s.aw, s.ah, 2)
+end
+
+procedure draw_on_rect(s)
+ FillRectangle(s.win, s.ax, s.ay, s.aw, s.ah)
+ WAttrib(s.win, "reverse=on")
+ GotoXY(s.win, s.ax+s.D.basex, s.ay+s.D.basey)
+ writes(s.win, s.s)
+ WAttrib(s.win, "reverse=off")
+ if /s.D.outline then
+ BevelRectangle(s.win, s.ax, s.ay, s.aw, s.ah, -2)
+end
+
+
+procedure init_check(s)
+ local FH, ascent, descent
+
+ /s.s := ""
+ s.D.space := 4
+ ascent := WAttrib(s.win, "ascent")
+ descent := WAttrib(s.win, "descent")
+ FH := ascent + descent
+ /s.ah := FH + 8
+ /s.aw := TextWidth(s.win, s.s) + FH + 3*s.D.space
+
+ s.aw := 0 < s.aw | 1
+ s.ah := 0 < s.ah | 1
+
+ s.D.basex := FH + 2*s.D.space
+ s.D.basey := (s.ah - FH)/2 + ascent
+
+ s.D.CS := FH
+ s.D.CP := (s.ah-s.D.CS)/2
+end
+
+procedure draw_off_check(s)
+ local sp, cp, cs, ax, ay
+
+ sp := s.D.space; cp := s.D.CP; cs := s.D.CS
+ ax := s.ax; ay := s.ay
+
+ BevelRectangle(s.win, ax+sp, ay+cp, cs, cs, 2)
+ EraseArea(s.win, ax+sp+2, ay+cp+2, cs-4, cs-4)
+ GotoXY(s.win, ax+s.D.basex, ay+s.D.basey)
+ writes(s.win, s.s)
+ if /s.D.outline then
+ GrooveRectangle(s.win, s.ax, s.ay, s.aw, s.ah)
+end
+
+procedure draw_on_check(s)
+ local sp, cs, cp, ax, ay
+
+ sp := s.D.space; cp := s.D.CP; cs := s.D.CS
+ ax := s.ax; ay := s.ay
+
+ BevelRectangle(s.win, ax+sp, ay+cp, cs, cs, -2)
+ FillRectangle(s.win, ax+sp+2, ay+cp+2, cs-4, cs-4)
+ GotoXY(s.win, ax+s.D.basex, ay+s.D.basey)
+ writes(s.win, s.s)
+ if /s.D.outline then
+ GrooveRectangle(s.win, s.ax, s.ay, s.aw, s.ah)
+end
+
+
+procedure init_circle(s)
+ local FH, ascent, descent
+
+ /s.s := ""
+ s.D.space := 4
+ ascent := WAttrib(s.win, "ascent")
+ descent := WAttrib(s.win, "descent")
+ FH := ascent + descent
+ /s.ah := FH + 8
+ /s.aw := TextWidth(s.win, s.s) + FH + 3*s.D.space
+
+ s.aw := 0 < s.aw | 1
+ s.ah := 0 < s.ah | 1
+
+ s.D.basex := FH + 2*s.D.space
+ s.D.basey := (s.ah -FH)/2 + ascent
+
+ s.D.CS := FH + 1
+ s.D.CP := (s.ah-s.D.CS)/2
+end
+
+procedure draw_off_circle(s)
+ local da, ax, ay, r
+
+ da := s.D
+ r := da.CS / 2 - 1
+ ax := s.ax
+ ay := s.ay
+
+ EraseArea(s.win, ax+da.space, ay+da.CP, da.CS, da.CS)
+ BevelCircle(s.win, ax+da.space+r, ay+da.CP+r, r, 2)
+
+ GotoXY(s.win, ax+da.basex, ay+da.basey)
+ writes(s.win, s.s)
+ if /da.outline then
+ GrooveRectangle(s.win, s.ax, s.ay, s.aw, s.ah)
+end
+
+procedure draw_on_circle(s)
+ local da, ax, ay, r
+
+ da := s.D
+ da := s.D
+ r := da.CS / 2 - 1
+ ax := s.ax
+ ay := s.ay
+
+ FillCircle(s.win, ax+da.space+r, ay+da.CP+r, r - 1)
+ BevelCircle(s.win, ax+da.space+r, ay+da.CP+r, r, -2)
+
+ GotoXY(s.win, ax+da.basex, ay+da.basey)
+ writes(s.win, s.s)
+ if /da.outline then
+ GrooveRectangle(s.win, s.ax, s.ay, s.aw, s.ah)
+end
+
+
+procedure init_diamond(s)
+ local FH, ascent, descent
+
+ /s.s := ""
+ s.D.space := 4
+ ascent := WAttrib(s.win, "ascent")
+ descent := WAttrib(s.win, "descent")
+ FH := ascent + descent
+ /s.ah := FH + 8
+ /s.aw := TextWidth(s.win, s.s) + FH + 3*s.D.space
+
+ s.aw := 0 < s.aw | 1
+ s.ah := 0 < s.ah | 1
+
+ s.D.basex := FH + 2*s.D.space
+ s.D.basey := (s.ah - FH)/2 + ascent
+
+ s.D.CS := FH + 1
+ s.D.CP := (s.ah-s.D.CS)/2
+end
+
+procedure draw_off_diamond(s)
+ local sp, cp, cs, ax, ay, r
+
+ sp := s.D.space; cp := s.D.CP; cs := s.D.CS
+ ax := s.ax; ay := s.ay
+ r := cs / 2
+
+ EraseArea(s.win, ax+sp, ay+cp, cs, cs)
+ BevelDiamond(s.win, ax+sp+r, ay+cp+r, r, 2)
+ GotoXY(s.win, ax+s.D.basex, ay+s.D.basey)
+ writes(s.win, s.s)
+ if /s.D.outline then
+ GrooveRectangle(s.win, s.ax, s.ay, s.aw, s.ah)
+end
+
+procedure draw_on_diamond(s)
+ local sp, cs, cp, ax, ay, r
+
+ sp := s.D.space; cp := s.D.CP; cs := s.D.CS
+ ax := s.ax; ay := s.ay
+ r := cs / 2
+
+ BevelDiamond(s.win, ax+sp+r, ay+cp+r, r, -2)
+ FillDiamond(s.win, ax+sp+r, ay+cp+r, r - 2)
+ GotoXY(s.win, ax+s.D.basex, ay+s.D.basey)
+ writes(s.win, s.s)
+ if /s.D.outline then
+ GrooveRectangle(s.win, s.ax, s.ay, s.aw, s.ah)
+end
+
+
+# undocumented image button code from Lorne Foss & Clint Jeffery, UTSA
+#
+# If type = V_IMAGE | V_IMAGE_NO, button string is used as image source.
+# If it contains a comma, it's a DrawImage string.
+# If not, it's the name of a GIF file in the current directory.
+# Size is determined by the GIF or DrawImage image.
+
+procedure init_image(s)
+ local imagefile
+
+ imagefile := s.s
+ if string(s.s) then {
+ if not find(",", s.s) then {
+ s.s := WOpen("canvas=hidden","image="||imagefile) |
+ _Vbomb("can't initialize button image from file " || s.s)
+ s.aw := WAttrib(s.s,"width")
+ s.ah := WAttrib(s.s,"height")
+ }
+ else {
+ s.aw := imswidth(s.s)
+ s.ah := imsheight(s.s)
+ if /s.aw | /s.ah then
+ _Vbomb("illegal DrawImage string for button")
+ }
+ if /s.D.outline then {
+ s.aw +:= 4
+ s.ah +:= 4
+ }
+ }
+end
+
+procedure draw_on_image(s)
+ draw_image_helper(s, -2, FillRectangle)
+end
+
+procedure draw_off_image(s)
+ draw_image_helper(s, 2, EraseArea)
+end
+
+procedure draw_image_helper(s, bevel, bgproc)
+ local b
+ static type
+
+ initial type := proc("type", 0) # protect attractive name
+
+ if /s.D.outline then {
+ BevelRectangle(s.win, s.ax, s.ay, s.aw, s.ah, bevel)
+ b := abs(bevel)
+ }
+ else
+ b := 0
+
+ if type(s.s) == "window" then
+ CopyArea(s.s, s.win, 0, 0, s.aw, s.ah, s.ax + b, s.ay + b)
+ else {
+ bgproc(s.win, s.ax + b, s.ay + b, s.aw - 2 * b, s.ah - 2 * b)
+ DrawImage(s.win, s.ax + b, s.ay + b, s.s)
+ }
+end
diff --git a/ipl/gprocs/vtext.icn b/ipl/gprocs/vtext.icn
new file mode 100644
index 0000000..abcd173
--- /dev/null
+++ b/ipl/gprocs/vtext.icn
@@ -0,0 +1,479 @@
+############################################################################
+#
+# File: vtext.icn
+#
+# Subject: Procedures for textual vidgets
+#
+# Authors: Jon Lipp and Gregg M. Townsend
+#
+# Date: November 4, 2002
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# Vidgets defined in this file:
+# Vtext
+#
+############################################################################
+#
+# Requires: Version 9 graphics
+#
+############################################################################
+#
+# Includes: keysyms
+#
+############################################################################
+#
+# Links: vidgets
+#
+############################################################################
+
+link vidgets
+
+$include "keysyms.icn"
+
+$ifndef _X_WINDOW_SYSTEM
+ $define Key_KP_Up Key_Up
+ $define Key_KP_Down Key_Down
+ $define Key_KP_Left Key_Left
+ $define Key_KP_Right Key_Right
+$endif
+
+
+############################################################################
+# Vtext
+############################################################################
+
+record Vstd_text(draw_cursor, erase_cursor, draw_data, unblock, block,
+ DataPixelSize, MaxPixelSize, NumericData, CursorPos, DataLength,
+ OldCursorPos, CursorOn, ta, tb, dx, dy)
+
+record Vtext_rec (win, s, callback, id, MaxChars, mask, data, uid,
+ ax, ay, aw, ah, T, P, V)
+
+procedure Vtext(params[])
+ local frame, x, y, ins, self
+ static procs, type
+
+ initial {
+ procs := Vstd(event_Vtext, draw_Vtext,
+ outline_Vtext, resize_Vtext, inrange_Vpane, init_Vtext,
+ couplerset_Vtext,,,,, set_value_Vtext)
+ type := proc("type", 0) # protect attractive name
+ }
+
+ if ins := Vinsert_check(params) then {
+ frame := pop(params); x := pop(params); y:= pop(params)
+ }
+ self := Vtext_rec ! params[1:7|0]
+ Vwin_check(self.win, "Vtext()")
+ if (\self.MaxChars, not numeric(self.MaxChars) ) then
+ _Vbomb("invalid size parameter to Vtext()")
+ if type(\self.mask) ~== "cset" then
+ _Vbomb("invalid mask parameter to Vtext()")
+ if type(\self.s) ~== "string" & not numeric(self.s) then
+ _Vbomb("invalid prompt passed to Vtext()")
+
+ self.uid := Vget_uid()
+ self.V := procs
+ self.P := Vstd_pos()
+ self.T := Vstd_text(draw_cursor_Vtext, erase_cursor_Vtext,
+ draw_data_Vtext, unblock_Vtext, block_Vtext)
+ init_Vtext(self)
+
+ if \ins then VInsert(frame, self, x, y)
+ return self
+end
+
+#
+# Initialization
+#
+procedure init_Vtext(self)
+ local p
+
+ /self.s := ""
+ /self.MaxChars := 18
+ self.s ? if self.s := tab(find("\\=")) then ="\\=" & self.data := tab(0)
+ /self.data := ""
+ if *self.data > self.MaxChars then self.data := self.data[1:self.MaxChars]
+ self.T.DataLength := *self.data
+ self.T.MaxPixelSize := WAttrib(self.win, "fwidth")*self.MaxChars
+# /self.T.MaxPixelSize := 250
+
+## check max length by pixel size.
+# if TextWidth(self.win, self.data) > self.T.MaxPixelSize then {
+# t := get_pos_Vtext(self, self.T.MaxPixelSize)
+# self.data := self.data[1:t]
+# }
+# self.T.DataLength := *self.data
+ self.T.DataPixelSize := TextWidth(self.win, self.data)
+
+## size by characters - taken out.
+ /self.mask := &cset
+
+## initialize with cursor at end
+ self.T.ta := self.T.tb := self.T.CursorPos := self.T.DataLength + 1
+
+## initialize with all data blocked out (selected)
+# self.T.ta := 1
+# self.T.tb := self.T.CursorPos := self.T.DataLength + 1
+
+ self.T.dx := TextWidth (self.win, self.s) + 6
+ self.aw := self.T.dx + self.T.MaxPixelSize + 4
+ self.ah := WAttrib(self.win, "fheight") + 6 # 4 for bevel, 2 for I-bar
+ self.T.dy := self.ah - 3 - WAttrib(self.win, "descent")
+
+ p := \self.callback
+ self.callback := Vcoupler()
+ add_clients_Vinit(self.callback, p, self)
+end
+
+#
+# Reconfigure the text vidget.
+#
+procedure resize_Vtext(s, x, y, w, h)
+ s.T.dx := TextWidth (s.win, s.s) + 6
+ s.T.DataLength := *s.data
+ s.T.MaxPixelSize := WAttrib(s.win, "fwidth") * s.MaxChars
+ w := s.aw := s.T.dx + s.T.MaxPixelSize + 4
+ h := s.ah := WAttrib(s.win, "fheight") + 6
+ resize_Vidget(s, x, y, w, h)
+end
+
+#
+# Draw the prompt, the data, outline the data area, then draw
+# the cursor if it was already on previous to calling this
+# procedure (happens with dialog boxes and resize events).
+#
+procedure draw_Vtext(self)
+ local t
+
+ t := self.T.CursorOn
+ self.T.CursorOn := &null
+ draw_prompt_Vtext(self)
+ draw_data_Vtext(self)
+ outline_Vtext(self)
+ if \t then draw_cursor_Vtext(self)
+end
+
+#
+# Outline the data field.
+#
+procedure outline_Vtext(self)
+
+ BevelRectangle(self.win, self.ax+self.T.dx-4, self.ay,
+ self.aw-(self.T.dx-4), self.ah, -2)
+end
+
+#
+# Draw the prompt.
+#
+procedure draw_prompt_Vtext(self)
+ GotoXY(self.win, self.ax, self.ay+self.T.dy)
+ writes(self.win, self.s)
+ return
+end
+
+#
+# Since the cursor is drawn in "reverse" mode, erase it only if it
+# is "on" upon entering this procedure.
+#
+procedure erase_cursor_Vtext(self)
+ local ocx, cy
+
+ if /self.T.CursorOn then fail
+ ocx := self.T.OldCursorPos
+
+## bracket cursor
+ WAttrib(self.win, "drawop=reverse", "linewidth=1")
+ DrawSegment(self.win, \ocx-2, self.ay+2, ocx+2, self.ay+2,
+ ocx, self.ay+3, ocx, self.ay+self.ah-4,
+ ocx-2, self.ay+self.ah-3, ocx+2, self.ay+self.ah-3)
+ WAttrib(self.win, "drawop=copy")
+ self.T.CursorOn := &null
+end
+
+#
+# Draw the cursor only if it was previously "off" at this location.
+#
+procedure draw_cursor_Vtext(self)
+ local ocx, cx, cy
+
+ if \self.T.CursorOn then fail
+ cx := self.ax+self.T.dx + get_pixel_pos_Vtext(self, self.T.CursorPos) - 1
+## bracket cursor
+ WAttrib(self.win, "drawop=reverse", "linewidth=1")
+ DrawSegment(self.win, cx-2, self.ay+2, cx+2, self.ay+2,
+ cx, self.ay+3, cx, self.ay+self.ah-4,
+ cx-2, self.ay+self.ah-3, cx+2, self.ay+self.ah-3)
+ WAttrib(self.win, "drawop=copy")
+ self.T.OldCursorPos := cx
+ self.T.CursorOn := 1
+end
+
+#
+# De-block the data (reset ta and tb to CursorPos).
+#
+procedure unblock_Vtext(self)
+ self.T.ta := self.T.CursorPos := self.T.tb
+ draw_data_Vtext(self)
+end
+
+#
+# Block (select) all the data
+#
+procedure block_Vtext(self)
+ self.T.ta := 1
+ self.T.tb := self.T.CursorPos := self.T.DataLength + 1
+ draw_data_Vtext(self)
+ if self.T.DataLength = 0 then
+ draw_cursor_Vtext(self)
+end
+
+#
+# Draw the data, reversing that text that lies between ta and tb
+# fields.
+#
+procedure draw_data_Vtext(self)
+
+# if self.T.ta = self.T.tb then return
+ erase_cursor_Vtext(self)
+ GotoXY(self.win, self.ax+self.T.dx, self.ay+self.T.dy)
+ if self.T.ta <= self.T.tb then {
+ writes(self.win, self.data[1:self.T.ta])
+ WAttrib(self.win, "reverse=on")
+ writes(self.win, self.data[self.T.ta:self.T.tb])
+ WAttrib(self.win, "reverse=off")
+ writes(self.win, self.data[self.T.tb:0])
+ }
+ else {
+ writes(self.win, self.data[1:self.T.tb])
+ WAttrib(self.win, "reverse=on")
+ writes(self.win, self.data[self.T.tb:self.T.ta])
+ WAttrib(self.win, "reverse=off")
+ writes(self.win, self.data[self.T.ta:0])
+ }
+ EraseArea(self.win, self.ax+self.T.dx+self.T.DataPixelSize, self.ay+2,
+ self.aw-(self.T.dx +self.T.DataPixelSize+1), self.ah-4)
+ return
+end
+
+#
+# Wow. Mouse events, block out text, key presses, enter, delete
+# etcetera stuff. Call callback if linefeed key or return key
+# is pressed.
+#
+procedure event_Vtext(self, e, x, y)
+ static ota
+ local otb, rv
+
+ if \self.callback.locked then fail
+ /x := &x; /y := &y
+ self.T.DataLength := *self.data
+ if e === (&lpress|&mpress|&rpress) then {
+ WAttrib(self.win, "pointer=xterm")
+ otb := self.T.ta := self.T.tb := self.T.CursorPos :=
+ get_pos_Vtext(self, &x-(self.ax+self.T.dx))
+ if otb = self.T.DataLength+1 & otb = \ota then
+ self.T.ta := 1
+ draw_data_Vtext(self)
+ draw_cursor_Vtext(self)
+ until e === (&lrelease|&mrelease|&rrelease) do {
+ self.T.tb := get_pos_Vtext(self, &x-(self.ax+self.T.dx))
+ if otb ~= self.T.tb then {
+ draw_data_Vtext(self)
+ self.T.CursorPos := self.T.tb
+ draw_cursor_Vtext(self)
+ otb := self.T.tb
+ }
+ e := Event(self.win)
+ }
+ rv := &null
+ WAttrib(self.win, "pointer=top left arrow")
+ } ## end mouse event loop
+ else if (not &meta) & (not (integer(e) < 0)) then {
+ ## it's a keypress
+ if rv := case e of {
+ "\^b" | Key_Left | Key_KP_Left: move_cursor_Vtext(self, -1)
+ "\^f" | Key_Right | Key_KP_Right: move_cursor_Vtext(self, 1)
+ "\b" | "\d": delete_left_Vtext(self)
+ "\^k" | "\^u" | "\^x": delete_line_Vtext(self)
+ (&shift & "\t") | Key_Up | Key_KP_Up: return V_PREVIOUS
+ "\t" | Key_Down | Key_KP_Down: return V_NEXT
+ "\r" | "\l": {
+ self.callback.V.set(self.callback, self, self.data)
+ V_NEXT
+ }
+ default: insert_char_Vtext(self, e)
+ }
+ then {
+ draw_data_Vtext(self)
+ draw_cursor_Vtext(self)
+ self.T.ta := self.T.tb := self.T.CursorPos
+ }
+ }
+ else
+ fail # not our event
+
+ ota := self.T.ta
+ return rv
+end
+
+# Move the cursor one way or another, determine if at bounds.
+#
+procedure move_cursor_Vtext(self, increment)
+ local t
+
+ t := self.T.CursorPos + increment
+ if t < 1 | t > self.T.DataLength+1 then fail
+ self.T.ta := self.T.tb := self.T.CursorPos := t
+ return
+end
+
+#
+# Blank out the whole data field.
+#
+procedure delete_line_Vtext(self)
+
+ self.data := ""
+ self.T.DataLength := *self.data
+ self.T.DataPixelSize := 0
+ self.T.ta := self.T.tb := self.T.CursorPos := 1
+ return
+end
+
+#
+# Get the character position based on mouse x coordinate.
+#
+procedure get_pos_Vtext(self, x)
+ local tp, c, i, j
+
+ c := 1
+ i := j := 0
+ while i < x do {
+ j := i
+ i +:= TextWidth(self.win, self.data[c])
+ if (c +:= 1) > self.T.DataLength then break
+ }
+ if x <= ((i + j) / 2) then
+ c -:= 1 # less than halfway into the char
+ if i < x then tp := self.T.DataLength+1
+ else tp := (1 <= c) | 1
+ return tp
+end
+
+#
+# Get pixel position in data field based on character position.
+#
+procedure get_pixel_pos_Vtext(self, CursorPos)
+ local sum, i
+
+ sum := 1
+ every i := 1 to CursorPos-1 do sum +:= TextWidth(self.win, self.data[i])
+ return sum
+end
+
+#
+# Insert a character; could replace blocked out text. Check if
+# insertion will go over bounds.
+#
+procedure insert_char_Vtext(self, c)
+
+ if *c > 1 then
+ fail # this isn't a character
+
+ if TextWidth(self.win, c) == 0 then
+ fail # not displayable
+
+ if (self.T.DataLength - abs(self.T.ta-self.T.tb) + 1) > self.MaxChars |
+ not (c ? any(self.mask)) then fail
+
+ if self.T.ta ~= self.T.tb then
+ change_data_Vtext(self, c)
+ else
+ self.data := self.data[1:self.T.CursorPos] || c ||
+ self.data[self.T.CursorPos:0]
+ self.T.DataLength := *self.data
+ self.T.DataPixelSize := TextWidth(self.win, self.data)
+ self.T.CursorPos +:= 1
+ return
+end
+
+#
+# Replace a character at current position.
+#
+procedure change_data_Vtext(self, c)
+ if self.T.tb < self.T.ta then {
+ self.data := self.data[1:self.T.tb] || (\c | "") ||
+ self.data[self.T.ta:0]
+ self.T.ta := self.T.CursorPos := self.T.tb
+ }
+ else {
+ self.data := self.data[1:self.T.ta] || (\c | "") ||
+ self.data[self.T.tb:0]
+ self.T.tb := self.T.CursorPos := self.T.ta
+ }
+end
+
+#
+# Delete the character to the left of the cursor.
+#
+procedure delete_left_Vtext(self)
+ if self.T.ta ~= self.T.tb then {
+ change_data_Vtext(self)
+ self.T.DataPixelSize := TextWidth(self.win, self.data)
+ return
+ }
+ else
+ if self.T.CursorPos > 1 then {
+ self.data := self.data[1:self.T.CursorPos-1] ||
+ self.data[self.T.CursorPos:0]
+ self.T.DataPixelSize := TextWidth(self.win, self.data)
+ self.T.CursorPos -:= 1
+ return
+ }
+end
+
+#
+# Set the data field to value passed in.
+# NOTE: doesn't pass it through mask right now.
+# Call callback if value if different from internal coupler's
+# value.
+#
+procedure couplerset_Vtext(self, caller, value)
+ local data
+
+ data := string(\value) | ""
+ self.data := data
+ if *self.data > self.MaxChars then self.data := self.data[1:self.MaxChars]
+ self.T.DataLength := *self.data
+ self.T.DataPixelSize := TextWidth(self.win, self.data)
+
+## initialize with cursor at end
+ self.T.ta := self.T.tb := self.T.CursorPos := self.T.DataLength + 1
+
+## initialize with all data blocked out (selected)
+# self.T.ta := 1
+# self.T.tb := self.T.CursorPos := self.T.DataLength + 1
+
+ draw_data_Vtext(self)
+
+ if numeric(value) then {
+ if value = \self.T.NumericData then fail
+ self.T.NumericData := value
+ }
+ else if data === self.data then fail
+ self.callback.V.set(self.callback, caller, value)
+# draw_cursor_Vtext(self)
+end
+
+#
+# Call couplerset to set value.
+#
+procedure set_value_Vtext(self, value)
+ couplerset_Vtext(self, , value)
+ return
+end
diff --git a/ipl/gprocs/wattrib.icn b/ipl/gprocs/wattrib.icn
new file mode 100644
index 0000000..76324e7
--- /dev/null
+++ b/ipl/gprocs/wattrib.icn
@@ -0,0 +1,51 @@
+############################################################################
+#
+# File: wattrib.icn
+#
+# Subject: Procedures for attributes
+#
+# Author: Ralph E. Griswold
+#
+# Date: March 6, 1995
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# These are "helper" procedures to use in place of WAttrib().
+#
+# This is a work in progress; at present it only handles fetching
+# of a few attribute values.
+#
+############################################################################
+#
+# Requires: Version 9 graphics
+#
+############################################################################
+
+
+procedure Width(win)
+
+ /win := &window
+
+ return WAttrib(win, "width")
+
+end
+
+procedure Height(win)
+
+ /win := &window
+
+ return WAttrib(win, "height")
+
+end
+
+procedure LineWidth(win)
+
+ /win := &window
+
+ return WAttrib(win, "linewidth")
+
+end
diff --git a/ipl/gprocs/weavegif.icn b/ipl/gprocs/weavegif.icn
new file mode 100644
index 0000000..97c948f
--- /dev/null
+++ b/ipl/gprocs/weavegif.icn
@@ -0,0 +1,132 @@
+############################################################################
+#
+# File: weavegif.icn
+#
+# Subject: Procedure to produce a woven image from a draft
+#
+# Author: Ralph E. Griswold
+#
+# Date: June 10, 2001
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# This procedure produces a woven image from a pattern-form draft, which
+# is passed to it as it's first argument. Window attributes may be
+# passed as a list in the second argument
+#
+############################################################################
+#
+# Requires: Version 9 graphics
+#
+############################################################################
+#
+# Links: tables, wopen
+#
+############################################################################
+#
+# Links: wopen
+#
+############################################################################
+
+link wopen
+
+link tables, wopen
+
+procedure weavegif(draft, attribs) #: create GIF from ISD
+ local x, y, color, treadle, i, j, treadle_list, k
+ local win, treadle_colors, lst, s
+
+ /attribs := []
+
+ /draft.width := *draft.threading
+ /draft.height := *draft.treadling
+
+ put(attribs, "label=" || draft.name, "size=" || draft.width || "," ||
+ draft.height)
+
+ win := (WOpen ! attribs) | {
+ write(&errout, "Cannot open window for woven image.")
+ fail
+ }
+
+ # Draw warp threads as "background".
+
+ if \draft.color_list then {
+ if *set(draft.warp_colors) = 1 then { # solid warp ground
+ Fg(draft.color_list[draft.warp_colors[1]])
+ FillRectangle()
+ }
+ every i := 1 to draft.width do {
+ Fg(win, draft.color_list[draft.warp_colors[i]])
+ DrawLine(win, i - 1, 0, i - 1, *draft.treadling - 1)
+ }
+ }
+ else {
+ every i := 1 to draft.width do {
+ Fg(win, draft.warp_colors[i])
+ DrawLine(win, i - 1, 0, i - 1, *draft.treadling - 1)
+ }
+ }
+
+ # Precompute points at which weft threads are on top.
+
+ treadle_list := list(draft.treadles)
+
+ every !treadle_list := [win]
+
+ every i := 1 to draft.treadles do {
+ every j := 1 to draft.shafts do
+ if draft.tieup[j, i] == "0" then
+ every k := 1 to *draft.threading do
+ if draft.threading[k] = j then
+ put(treadle_list[i], k - 1, 0)
+ }
+
+ if \draft.color_list then {
+ treadle_colors := list(*draft.color_list)
+ every !treadle_colors := []
+ every i := 1 to draft.height do {
+ j := draft.weft_colors[i]
+ put(treadle_colors[j], i)
+ }
+ }
+ else {
+ treadle_colors := table()
+ every i := 1 to draft.width do {
+ j := draft.weft_colors[i]
+ /treadle_colors[j] := []
+ put(treadle_colors[j], i)
+ }
+ }
+
+ # "Overlay" weft threads.
+
+ if \draft.color_list then {
+ every i := 1 to *treadle_colors do {
+ Fg(win, draft.color_list[i]) | stop("bogon")
+ every y := !treadle_colors[i] do {
+ WAttrib(win, "dy=" || (y - 1))
+ if *treadle_list[draft.treadling[y]] = 1 then next # blank pick
+ DrawPoint ! treadle_list[draft.treadling[y]]
+ }
+ }
+ }
+ else {
+ every s := !keylist(treadle_colors) do {
+ Fg(win, s) | stop("bogon")
+ lst := treadle_colors[s]
+ every y := !lst do {
+ WAttrib(win, "dy=" || (y - 1))
+ if *treadle_list[draft.treadling[y]] = 1 then next # blank pick
+ DrawPoint ! treadle_list[draft.treadling[y]]
+ }
+ }
+ }
+
+ return win
+
+end
diff --git a/ipl/gprocs/wifisd.icn b/ipl/gprocs/wifisd.icn
new file mode 100644
index 0000000..46cb556
--- /dev/null
+++ b/ipl/gprocs/wifisd.icn
@@ -0,0 +1,324 @@
+############################################################################
+#
+# File: wifisd.icn
+#
+# Subject: Procedure to convert WIF to xencoded ISD
+#
+# Author: Ralph E. Griswold
+#
+# Date: April 6, 2002
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# This procedure analyzes a Weaving Information File and returns xencoded
+# ISD.
+#
+# Information in a WIF that is not necessary for an ISD is ignored.
+#
+# If there is a liftplan, the symbols in the treadling sequence
+# correspond to shaft patterns given in the liftplan. The symbols
+# for these pattern shafts are implicit and occur in orde to the number
+# of shaft patterns.
+#
+# There is a problem where there is treadling with multiple treadles
+# and no liftplan. *Presumably* that treadling can be used like a
+# liftplan, but without, necessarily, a direct tie-up. This problem
+# problem has not been addressed yet.
+#
+# If there is a liftplan, then a direct tie-up is implied by the
+# wording in the WIF documentation. However, that's in the interpretation
+# of the draft. The tie-up produced here is the one given in the WIF.
+#
+# If there is a liftplan and a treadling with multiple treadles,
+# the treadling is ignored.
+#
+# This procedure does not attempt to detect or correct errors in WIFs,
+# but it does try to work around some common problems.
+#
+############################################################################
+#
+# Links: numbers, tieutils, tables, weavutil, xcode
+#
+############################################################################
+
+link numbers
+link tieutils
+link tables
+link weavutil
+link xcode
+
+global data_default
+global data_entries
+global sections
+global wif
+
+procedure wif2isd(file, title)
+ local section, line, i, colors, information_sections, data_sections
+ local color_range, information, data, tieup
+ local lst, x, k, r, g, b, color, opts, j, tie, lift
+ local range, format
+ local color_set, color_tbl, symbols, maxi, colors_in, liftplan
+ local lift_set, lift_list, lifting, lift_table, draft, threads
+
+ /title := "untitled"
+
+ maxi := 0
+
+ information_sections := [
+ "wif",
+ "contents",
+ "translations",
+ "color palette",
+ "warp symbol palette",
+ "weft symbol palette",
+ "text",
+ "weaving",
+ "warp",
+ "weft",
+ "bitmap image",
+ "bitmap file"
+ ]
+
+ data_sections := [
+ "notes",
+ "liftplan",
+ "color table",
+ "warp symbol table",
+ "weft symbol table",
+ "threading",
+ "warp thickness",
+ "warp thickness zoom",
+ "warp spacing",
+ "warp spacing zoom",
+ "warp colors",
+ "warp symbols",
+ "treadling",
+ "weft thickness",
+ "weft thickness zoom",
+ "weft spacing",
+ "weft spacing zoom",
+ "weft colors",
+ "weft symbols",
+ "bitmap image data",
+ "tieup",
+ "private"
+ ]
+
+ data_default := table()
+ data_entries := table()
+
+ sections := table()
+ information := table()
+ data := table()
+
+ wif := []
+
+ # Read WIF into list.
+
+ while line := trim(read(file)) do
+ if *line > 0 then put(wif, line)
+
+ # Locate sections.
+
+ every i := 1 to *wif do {
+ wif[i] ? {
+ if ="[" then {
+ section := map(tab(upto(']')))
+ sections[section] := i
+ }
+ }
+ }
+
+ # Process information sections.
+
+ every name := !information_sections do
+ information[name] := info(name)
+
+ # Set up data information.
+
+ data_entries["tieup"] := (\information["weaving"])["treadles"] # may be bogus
+ data_entries["liftplan"] := (\information["weft"])["threads"]
+ data_entries["color table"] := (\information["color palette"])["entries"]
+ data_entries["warp symbol table"] :=
+ (\information["warp symbol palette"])["entries"]
+ data_entries["weft symbol table"] :=
+ (\information["weft symbol palette"])["entries"]
+ data_entries["threading"] := (\information["warp"])["threads"]
+ data_entries["warp colors"] := (\information["warp"])["threads"]
+ data_entries["treadling"] := (\information["weft"])["threads"]
+ data_entries["weft colors"] := (\information["weft"])["threads"]
+
+ data_default["tieup"] := ""
+ data_default["liftplan"] := ""
+ data_default["notes"] := ""
+ data_default["warp colors"] := (\information["warp"])["color"]
+ data_default["weft colors"] := (\information["weft"])["color"]
+ \data_default["warp colors"] ?:= { # We require index for now.
+ tab(upto(','))
+ }
+ \data_default["weft colors"] ?:= { # We require index for now.
+ tab(upto(','))
+ }
+
+
+ # Process data sections.
+
+ draft := isd()
+
+ every name := !data_sections do
+ data[name] := decode_data(name)
+
+ # First get colors and encode them.
+
+ draft.color_list := \data["color table"] | ["white", "black"]
+
+ # Compose draft
+
+ draft.name := title
+
+ draft.shafts := (\information["weaving"])["shafts"] | abort(3)
+ draft.treadles := (\information["weaving"])["treadles"] | abort(3)
+
+ draft.warp_colors := \data["warp colors"]
+
+ draft.weft_colors := \data["weft colors"] | draft.warp_colors
+
+ # Need to get liftplan, if there is one, before processing treadling.
+ # Output is later.
+ #
+ # Note: If the treadling has multiple treadles, we need to handle it
+ # some other way than we now are. What we need to do is to create
+ # a treadling here.
+
+ if draft.liftplan := \data["liftplan"] then {
+ lifting := ""
+ lift_set := set()
+ lift_list := []
+ lift_table := table()
+ k := 0
+ threads := (\information["weft"])["threads"] | abort(3)
+ every i := 1 to threads do {
+ line := repl("0", draft.treadles)
+ if \draft.liftplan[i] then {
+ draft.liftplan[i] ? {
+ while j := tab(upto(',') | 0) do {
+ if *j > 0 then line[j] := "1"
+ move(1) | break
+ }
+ }
+ }
+ if not member(lift_set, line) then {
+ insert(lift_set, line)
+ k +:= 1
+ lift_table[line] := possym(k) | stop("*** masking error")
+ }
+ put(lift_list, line)
+ lifting ||:= lift_table[line]
+ }
+ }
+
+ draft.threading := \data["threading"]
+ draft.shafts := max ! draft.threading # don't trust information
+
+# if \lifting then draft.treadling := lifting else
+ draft.treadling := \data["treadling"] | draft.threading
+ draft.treadles := max ! draft.treadling # don't trust information
+
+ data_entries["tieup"] := draft.treadles # try to fix bogosity
+
+ data["tieup"] := decode_data("tieup") # re-do
+
+ if tieup := \data["tieup"] then {
+ tie := ""
+ every i := 1 to draft.treadles do {
+ line := repl("0", draft.shafts)
+ if \tieup[i] then {
+ tieup[i] ? {
+ while j := tab(upto(',') | 0) do {
+ if *j > 0 then line[j] := "1"
+ move(1) | break
+ }
+ }
+ }
+ tie ||:= line # MAY BE MIS-ORIENTED
+ }
+ }
+
+ draft.tieup := pat2tier(tie2pat(draft.shafts, draft.treadles, tie)).matrix
+
+ # Now, finally, the liftplan, if any.
+ #
+ # The lift lines are given in order of occurrence. The symbols
+ # used for them in the treadling can be reconstructed and are
+ # note included here.
+
+ draft.liftplan := \lift_list
+
+ xencode(draft, &output)
+
+end
+
+procedure abort(i)
+
+ stop("*** insufficient information to produce specifications: ", i)
+
+end
+
+procedure info(name)
+ local i, tbl, keyname, keyvalue, line
+
+ tbl := table()
+
+ i := \sections[name] | fail
+
+ repeat {
+ i +:= 1
+ line := wif[i] | return tbl
+ line ? {
+ {
+ keyname := map(tab(upto('='))) &
+ move(1) &
+ keyvalue := trim(tab(upto(';') | 0))
+ } | return tbl
+ tbl[keyname] := keyvalue
+ } | return tbl
+ }
+
+end
+
+procedure decode_data(name)
+ local i, lst, keyname, keyvalue, line, size, value
+
+ i := \sections[name] | fail
+
+ value := \data_default[name]
+
+ if size := \data_entries[name] then lst := list(size, value)
+ else lst := []
+
+ repeat {
+ i +:= 1
+ line := wif[i] | return lst
+ line ? {
+ {
+ keyname := integer(tab(upto('='))) | return lst
+ move(1)
+ keyvalue := trim(tab(upto(';') | 0))
+ keyvalue := integer(keyvalue) # in case
+ if *keyvalue = 0 then {
+ keyvalue := value
+ if /keyvalue then {
+ write(&errout, "name=", name)
+ stop("*** no default where needed")
+ }
+ }
+ }
+ if /size then put(lst, keyvalue) else lst[keyname] := keyvalue
+ }
+ }
+
+end
diff --git a/ipl/gprocs/win.icn b/ipl/gprocs/win.icn
new file mode 100644
index 0000000..66b24f5
--- /dev/null
+++ b/ipl/gprocs/win.icn
@@ -0,0 +1,54 @@
+############################################################################
+#
+# File: win.icn
+#
+# Subject: Procedures to open bare-bones window
+#
+# Author: Ralph E. Griswold
+#
+# Date: May 2, 2001
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# These procedures are provided as quick-and-dirty ways to get a
+# nominal window as, for example, when testing.
+#
+# win() causes error termination if a window can't be opened.
+# winf(), on the other hand, just fails.
+#
+############################################################################
+#
+# Requires: Version 9 graphics
+#
+############################################################################
+#
+# Links: wopen
+#
+############################################################################
+
+link wopen
+
+procedure win(width, height)
+
+ /width := 500
+ /height := 500
+
+ return WOpen("size=" || width || "," || height) |
+ stop("*** can't open window")
+
+ return
+
+end
+
+procedure winf(width, height)
+
+ /width := 500
+ /height := 500
+
+ return WOpen("size=" || width || "," || height) | fail
+
+end
diff --git a/ipl/gprocs/window.icn b/ipl/gprocs/window.icn
new file mode 100644
index 0000000..9526060
--- /dev/null
+++ b/ipl/gprocs/window.icn
@@ -0,0 +1,380 @@
+############################################################################
+#
+# File: window.icn
+#
+# Subject: Procedure for opening window
+#
+# Author: Gregg M. Townsend
+#
+# Date: October 10, 1997
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# Window() opens a window with provisions for option processing and
+# error handling. The returned window is assigned to &window if
+# &window is null. If the window cannot be opened, the program is
+# aborted.
+#
+# The characteristics of the window are set from several sources:
+# Window's arguments, optionally including the program argument list;
+# user defaults; and built-in defaults. These built-in defaults are
+# the same as for optwindow(): bg=pale gray, fg=black, size=500,300.
+#
+############################################################################
+#
+# With one exception, arguments to Window() are attribute specifications
+# such as those used with open() and WAttrib(). Order is significant,
+# with later attributes overriding earlier ones.
+#
+# Additionally, the program argument list -- the single argument passed
+# to the main procedure -- can be passed as an argument to Window().
+# Options specified with a capital letter are removed from the list and
+# interpreted as attribute specifications, again in a manner consistent
+# with optwindow().
+#
+# Because the Window() arguments are processed in order, attributes that
+# appear before the program arglist can be overridden by command-line
+# options when the program is executed. If attributes appear after the
+# program arglist, they cannot be overridden. For example, with
+#
+# procedure main(args)
+# Window("size=600,400", "fg=yellow", args, "bg=black")
+#
+# the program user can change the size and foreground color
+# but not the background color.
+#
+# User defaults are applied at the point where the program arglist appears
+# (and before processing the arglist). If no arglist is supplied, no
+# defaults are applied. Defaults are obtained by calling WDefault().
+# Icon attribute names are used as option names; &progname is used
+# as the program name after trimming directories and extensions.
+#
+# The following table lists the options recognized in the program arglist,
+# the corresponding attribute (and WDefault()) names, the default values
+# if any, and the meanings. All legal attributes are allowed in the
+# Window() call, but only these are set from the command line or
+# environment:
+#
+# arg attribute default meaning
+# --- --------- ------- --------------------------
+# -B bg pale gray background color
+# -F fg black foreground color
+# -T font - text font
+# -L label &progname window title
+# (trimmed)
+#
+# -D display - window device
+# -X posx - horizontal position
+# -Y posy - vertical position
+# -W width 500 window width
+# -H height 300 window height
+#
+# -S size 500,300 size
+# -P pos - position
+# -G geometry - window size and/or position
+#
+# -A <any> - use "-A name=value"
+# to set arbitrary attribute
+#
+# -! - - write open() params to &error
+# (for debugging)
+#
+############################################################################
+#
+# Includes: vdefns
+#
+############################################################################
+#
+# Requires: Version 9 graphics
+#
+############################################################################
+
+
+$include "vdefns.icn"
+
+global wdw_debug # non-null if to trace open call
+
+
+# Window(att, ..., arglist, ..., att) -- open window and set &window
+
+procedure Window(args[])
+ local cs, pname, att, omit1, omit2, name, val, a, win
+ static type
+
+ initial type := proc("type", 0) # protect attractive name
+
+ wdw_debug := &null
+ att := table()
+
+ # Trim &progname for use as option index and window label.
+ cs := &cset -- &letters -- &digits -- '.$_'
+ &progname ? {
+ while tab(upto(cs)) do
+ move(1)
+ pname := tab(upto('.') | 0)
+ }
+ if pname == "" then
+ pname := &progname
+
+ # Process arguments.
+ every a := !args do
+ case type(a) of {
+ "string": a ? {
+ name := tab(upto("=")) | runerr(205, a)
+ move(1)
+ val := tab(0)
+ wdw_register(att, name, val)
+ }
+ "list": {
+ wdw_defaults(att, a, pname)
+ wdw_options(att, a)
+ }
+ default:
+ runerr(110, a)
+ }
+
+ # Set defaults for certain attributes if not set earlier.
+ /att["fg"] := "black"
+ /att["bg"] := VBackground
+ /att["label"] := pname
+
+ if /att["image"] & not (att["canvas"] === "maximal") then { # don't override
+ /att["width"] := 500
+ /att["height"] := 300
+ }
+
+ # Open the window. Defer "font" and "fg" until later because they can
+ # cause failure. Don't defer "bg", because it affects the initial
+ # window appearance, but try again without it if the open fails.
+ omit1 := set(["fg", "font"])
+ omit2 := set(["fg", "font", "bg"])
+ win := wdw_open(att, omit1 | omit2) | stop(&progname, ": can't open window")
+
+ # Set foreground, background, and font, giving a nonfatal message if
+ # the value is unacceptable. Then return the window.
+ wdw_attrib(win, att, "fg")
+ wdw_attrib(win, att, "bg")
+ wdw_attrib(win, att, "font")
+ GotoRC(win, 1, 1) # now that font has been set
+ /&window := win
+ return win
+end
+
+
+# wdw_defaults(att, arglist, pname) -- find defaults and store in att table
+#
+# arglist is checked for "-D displayname", which is honored if present.
+# pname is the program name for calling xdefault.
+# A list of several attribute names (see code) is checked.
+
+procedure wdw_defaults(att, arglist, pname)
+ local w, oname, dpy
+
+ # We need to have a window in order to read defaults, and unless we honor
+ # the -D option from the command line here it becomes pretty useless.
+ dpy := ("display=" || wdw_peekopt(arglist, "D")) | "fg=black"
+
+ # Open an offscreen window.
+ w := open("Window()", "g", "canvas=hidden", "size=32,32", dpy) |
+ stop(&progname, ": can't open display")
+
+ # Set attributes from environment. Order is significant here:
+ # pos & size override geometry, and posx/posy/width/height override both.
+ every oname := "display" | "bg" | "fg" | "font" | "windowlabel" | "label" |
+ "geometry" | "size" | "pos" | "posx" | "posy" | "width" | "height" do
+ wdw_register(att, oname, WDefault(w, pname, oname))
+
+ # Delete the offscreen window, and return.
+ Uncouple(w)
+ return
+end
+
+
+# wdw_peekopt(arglist, ch) -- return value of option 'ch' from arglist
+#
+# Option cracking rules are identical with wdw_options().
+# Fails if the option does not appear.
+
+procedure wdw_peekopt(arglist, ch)
+ local a, opt, val
+
+ arglist := copy(arglist)
+ while a := get(arglist) do a ? {
+ if ="-" & (opt := tab(any(&ucase))) then {
+ if pos(0) then
+ val := get(arglist) | fail
+ else
+ val := tab(0)
+ if opt == ch then
+ return val
+ }
+ }
+ fail
+end
+
+
+# wdw_options(att, arglist) - move options from arglist into att table
+#
+# Upper-case options in the argument list are stored in the table "att"
+# under their attribute names (see code for list). An "option" is a list
+# entry beginning with "-" and an option letter; its value follows in the
+# same string (if more characters remain) or in the next entry.
+#
+# This procedure can be "fooled" if a non-upper-case option is followed
+# in the next entry by a value that looks like the start of an option.
+#
+# Options and values are removed from arglist, leaving only the unprocessed
+# entries.
+#
+# The special option "-!" takes no value and causes wdw_debug to be set.
+
+procedure wdw_options(att, arglist)
+ local a, opt, name, val, rejects
+
+ rejects := []
+ while a := get(arglist) do a ? {
+ if ="-" & (opt := tab(any(&ucase))) then {
+ if pos(0) then
+ val := get(arglist) | stop(&progname, ": missing value for ", a)
+ else
+ val := tab(0)
+ case opt of {
+ "B": wdw_register(att, "bg", val)
+ "F": wdw_register(att, "fg", val)
+ "T": wdw_register(att, "font", val)
+ "L": wdw_register(att, "label", val)
+ "D": wdw_register(att, "display", val)
+ "X": wdw_register(att, "posx", val)
+ "Y": wdw_register(att, "posy", val)
+ "W": wdw_register(att, "width", val)
+ "H": wdw_register(att, "height", val)
+ "P": wdw_register(att, "pos", val)
+ "S": wdw_register(att, "size", val)
+ "G": wdw_register(att, "geometry", val)
+ "A": val ? {
+ name := tab(upto("=")) |
+ stop(&progname, ": malformed -A option: ", val)
+ move(1)
+ wdw_register(att, name, tab(0))
+ }
+ default: stop(&progname, ": unrecognized option -", opt)
+ }
+ }
+ else if ="-!" & pos(0) then
+ wdw_debug := 1
+ else
+ put(rejects, a)
+ }
+
+ # Arglist is now empty; put back args that we didn't use.
+ while put(arglist, get(rejects))
+ return
+end
+
+
+
+# wdw_register(att, name, val) -- store attribute val in att[name]
+#
+# The compound attributes "pos", "size", and "geometry" are broken down
+# into their component parts and stored as multiple values. A runtime
+# error occurs if any of these is malformed. Interactions with
+# "canvas=maximal" are also handled.
+
+procedure wdw_register(att, name, val)
+ wdw_reg(att, name, val) | runerr(205, name || "=" || val)
+ return
+end
+
+procedure wdw_reg(att, name, val)
+ case name of {
+ "size": val ? { # size=www,hhh
+ att["width"] := tab(many(&digits)) | fail
+ ="," | fail
+ att["height"] := tab(many(&digits)) | fail
+ pos(0) | fail
+ if \att["canvas"] == "maximal" then
+ delete(att, "canvas")
+ }
+ "pos": val ? { # pos=xxx,yyy
+ att["posx"] := tab(many(&digits)) | fail
+ ="," | fail
+ att["posy"] := tab(many(&digits)) | fail
+ pos(0) | fail
+ }
+ "geometry": val ? { # geometry=[wwwxhhh][+xxx+yyy]
+ if att["width"] := tab(many(&digits))
+ then {
+ ="x" | fail
+ att["height"] := tab(many(&digits)) | fail
+ if \att["canvas"] == "maximal" then
+ delete(att, "canvas")
+ }
+ if ="+" then {
+ att["posx"] := tab(many(&digits)) | fail
+ ="+" | fail
+ att["posy"] := tab(many(&digits)) | fail
+ }
+ pos(0) | fail
+ }
+ "canvas": {
+ att[name] := val
+ if val == "maximal" then
+ every delete(att, "width" | "height")
+ }
+ default: {
+ att[name] := val
+ }
+ }
+ return
+end
+
+
+# wdw_open(att, omit) -- open window with attributes from att table
+#
+# Ignore null or empty attributes and those in the "omit" set.
+# Trace open call if wdw_debug is set. Set &window.
+
+procedure wdw_open(att, omit)
+ local args, name
+ static image
+
+ initial image := proc("image", 0) # protect attractive name
+
+ args := [&progname, "g"]
+ every name := key(att) do
+ if not member(omit, name) then
+ put(args, name || "=" || ("" ~== \att[name]))
+
+ if \wdw_debug then {
+ writes(&errout, "Window: open(", image(args[1]))
+ every writes(&errout, ",", image(args[2 to *args]))
+ write(&errout, ")")
+ }
+
+ return open ! args
+end
+
+
+# wdw_attrib(win, att, name) -- call WAttrib(win, name=att[name])
+#
+# Null and empty values are ignored.
+# Failure is diagnosed on stderr.
+# The call is traced if wdw_debug is set.
+
+procedure wdw_attrib(win, att, name)
+ local val, s
+ static image
+
+ initial image := proc("image", 0) # protect attractive name
+
+ val := ("" ~== \att[name]) | return
+ s := name || "=" || val
+ if \wdw_debug then
+ write(&errout, "Window: WAttrib(", image(s), ")")
+ WAttrib(win, s) | write(&errout, &progname, ": can't set ", s)
+ return
+end
diff --git a/ipl/gprocs/winsnap.icn b/ipl/gprocs/winsnap.icn
new file mode 100644
index 0000000..b7ef5fe
--- /dev/null
+++ b/ipl/gprocs/winsnap.icn
@@ -0,0 +1,62 @@
+############################################################################
+#
+# File: winsnap.icn
+#
+# Subject: Procedure to take snapshot of a portion of a window
+#
+# Author: Ralph E. Griswold
+#
+# Date: May 2, 2001
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# This procedure writes an image file for a specified portion of a
+# window. The name for the file is requested from the user via a
+# dialog box. If there already is a file by the specified name, the
+# user is given the option of overwriting it or selecting another
+# name. The procedure fails if the user cancels.
+#
+############################################################################
+#
+# Requires: Version 9 graphics
+#
+############################################################################
+#
+# Links: graphics
+#
+############################################################################
+
+link graphics
+
+procedure winsnap(win, x, y, w, h)
+ local name, f
+
+ if type(win) ~== "window" then {
+ win :=: x :=: y :=: w :=: h
+ win := \&window | runerr(140, &window)
+ }
+
+ repeat {
+ if OpenDialog("Image file name") == "Okay" then {
+ name := dialog_value
+ if f := open(name) then {
+ close(f)
+ if Dialog("Overwrite existing file?", , , ,
+ ["Okay", "Cancel"]) == "Cancel" then next
+ }
+ WriteImage(win, name, x, y, w, h) | {
+ Notice("Cannot write image")
+ fail
+ }
+ return
+ }
+ else fail
+ }
+
+ return
+
+end
diff --git a/ipl/gprocs/wipe.icn b/ipl/gprocs/wipe.icn
new file mode 100644
index 0000000..8f2d866
--- /dev/null
+++ b/ipl/gprocs/wipe.icn
@@ -0,0 +1,112 @@
+############################################################################
+#
+# File: wipe.icn
+#
+# Subject: Procedure to wipe window area
+#
+# Author: Ralph E. Griswold
+#
+# Date: May 2, 2001
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# wipe(window, color, direction, x, y, w, h) "wipes" a rectangular area of
+# window to the specified color. The direction of wiping can be any one of:
+#
+# "right" from left to right
+# "left" from right to left
+# "down" from top to bottom
+# "up from bottom to top
+# "left-right" from left and right toward center
+# "up-down" from top and bottom toward center
+# "in" from outside to inside
+#
+# The default direction is "right".
+#
+# The default color is the background color of the window.
+#
+# x, y is the top left corner of the area and w and h are the width and
+# height. An omitted value defaults to the one for the entire window.
+#
+############################################################################
+#
+# Requires: Version 9 graphics
+#
+############################################################################
+
+procedure wipe(window, color, direction, x1, y1, w, h)
+ local x, y, x2, y2, fg
+
+ /color := Bg(window) # establish defaults
+ /direction := "right"
+ /x1 := 0
+ /y1 := 0
+ /w := WAttrib(window, "width")
+ /h := WAttrib(window, "height")
+ x2 := x1 + w
+ y2 := y1 + h
+
+ fg := Fg(window) # save present foreground color
+ Fg(window, color) # set foreground for wiping
+
+ if not(integer(x1) & integer(x2) & integer(y1) & integer(y2)) |
+ (x1 > x2) | (y1 > y2) then stop("*** illegal coordinates in wipe()")
+
+ case direction of {
+ "right": {
+ every x := x1 to x2 do {
+ DrawLine(window, x, y1, x, y2)
+ }
+ }
+ "left": {
+ every x := x2 to x1 by -1 do {
+ DrawLine(window, x, y1, x, y2)
+ }
+ }
+ "left-right": {
+ until (x2 < x1) do {
+ DrawLine(window, x1, y1, x1, y2)
+ DrawLine(window, x2, y1, x2, y2)
+ x1 +:= 1
+ x2 -:= 1
+ }
+ }
+ "up-down": {
+ until y2 < y1 do {
+ DrawLine(window, x1, y1, x2, y1)
+ DrawLine(window, x1, y2, x2, y2)
+ y1 +:= 1
+ y2 -:= 1
+ }
+ }
+ "down": {
+ every y := y1 to y2 do {
+ DrawLine(window, x1, y, x2, y)
+ }
+ }
+ "up": {
+ every y := y2 to y1 by -1 do {
+ DrawLine(window, x1, y, x2, y)
+ }
+ }
+ "in": {
+ until (x2 < x1) | (y2 < y1) do {
+ DrawLine(window, x1, y1, x1, y2, x2, y2, x2, y1, x1, y1)
+ x1 +:= 1
+ x2 -:= 1
+ y1 +:= 1
+ y2 -:= 1
+ }
+ }
+ default: stop("*** illegal direction specificaion in wipe()")
+ }
+
+ Fg(window, fg) # restore foreground color
+
+ return
+
+end
diff --git a/ipl/gprocs/wopen.icn b/ipl/gprocs/wopen.icn
new file mode 100644
index 0000000..820f761
--- /dev/null
+++ b/ipl/gprocs/wopen.icn
@@ -0,0 +1,230 @@
+############################################################################
+#
+# File: wopen.icn
+#
+# Subject: Procedures for graphics input/output
+#
+# Authors: Gregg M. Townsend and Ralph E. Griswold
+#
+# Date: April 15, 2002
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# These procedures provide window input and output using "W" names as
+# substitutes for standard input and output functions. WOpen() opens
+# and returns a window; the result is also assigned to &window if
+# &window is null.
+#
+# WOpen(attrib, ...) opens and returns a window.
+#
+# WRead(W) reads a line from a window.
+#
+# WReads(W, i) reads i characters from a window.
+#
+# WWrite(W, s, ...) writes a line to window.
+#
+# WWrites(W, s, ...) writes a partial line to window.
+#
+# WDelay(W, n) flushes a window, then delays n milliseconds.
+# default: n = 1
+#
+# WClose(W) closes a window;
+# if W === &window, sets &window to &null.
+#
+# WDone(), WQuit(), QuitCheck(), and QuitEvents() incorporate knowledge
+# of the Icon standard set of "quit" events, currently the letters
+# "q" or "Q". The procedures themselves are trivial.
+#
+# WQuit() consumes unread window events and succeeds if a quit event
+# is seen. It does not wait. WDone() waits until a quit event is read,
+# then exits the program. QuitCheck(ev) calls exit() if its parameter
+# is a quit event; QuitCheck can be used with the vidget package as a
+# default event handler. QuitEvents() generates the standard set of
+# quit events.
+#
+# ZDone() is a zooming version of WDone(). If the window is resized
+# while waiting for a quit event, its contents are zoomed to fill the
+# new size. Zooming to a multiple of the original size can also be
+# accomplished by typing a nonzero digit into the window.
+#
+# SubWindow(W, x, y, w, h) produces a subwindow by creating and
+# reconfiguring a clone of the given window. The original window
+# is not modified. In the clone, which is returned, clipping
+# bounds are set by the given rectangle and the origin is
+# set at the rectangle's upper left corner.
+#
+############################################################################
+#
+# Requires: Version 9 graphics
+#
+############################################################################
+
+link gpxop
+
+procedure WOpen(args[])
+ push(args, "g")
+ push(args, "")
+ if /&window then
+ return &window := open ! args
+ else
+ return open ! args
+end
+
+
+procedure WRead(window)
+ if /window then
+ window := \&window | runerr(140, &window)
+ return read(window)
+end
+
+
+procedure WReads(window, i)
+ static type
+
+ initial type := proc("type", 0) # protect attractive name
+ if /window then
+ window := \&window | runerr(140, &window)
+ else if type(window) ~== "window" then {
+ i := window
+ window := \&window | runerr(140, &window)
+ }
+ return reads(window, i)
+end
+
+
+procedure WWrite(args[])
+ static type
+
+ initial type := proc("type", 0) # protect attractive name
+ if not (type(args[1]) == "window") then
+ push(args, \&window) | runerr(140, &window)
+ return write ! args
+end
+
+
+procedure WWrites(args[])
+ static type
+
+ initial type := proc("type", 0) # protect attractive name
+ if not (type(args[1]) == "window") then
+ push(args, \&window) | runerr(140, &window)
+ return writes ! args
+end
+
+
+procedure WDelay(window, n)
+ static delay, type
+
+ initial {
+ delay := proc("delay", 0) # protect attractive names
+ type := proc("type", 0)
+ }
+
+ if /window then
+ window := \&window | runerr(140, &window)
+ else if type(window) ~== "window" then {
+ n := window
+ window := \&window | runerr(140, &window)
+ }
+ /n := 1
+ integer(n) | runerr(101, n)
+ WFlush(window)
+ delay(n)
+
+ return window
+
+end
+
+
+procedure WClose(window)
+ if /window then
+ window := \&window | runerr(140, &window)
+ if window === &window then
+ &window := &null
+ return close(window)
+end
+
+
+procedure QuitEvents()
+ suspend !"qQ"
+end
+
+
+procedure QuitCheck(ev)
+ if ev === QuitEvents() then
+ exit()
+ return
+end
+
+
+procedure WQuit(win)
+ /win := &window
+ while *Pending(win) > 0 do
+ if Event(win) === QuitEvents() then
+ return win
+ fail
+end
+
+
+procedure WDone(win)
+ /win := &window
+ until Event(win) === QuitEvents()
+ exit()
+end
+
+
+# ZDone(win) -- like WDone(), but zoom window if resized while waiting
+
+procedure ZDone(win)
+ local org, e, w, h, ww, hh, x0, y0
+
+ /win := &window
+ x0 := -WAttrib(win, "dx")
+ y0 := -WAttrib(win, "dy")
+ w := WAttrib(win, "width")
+ h := WAttrib(win, "height")
+ org := WOpen("width=" || w, "height=" || h, "canvas=hidden") | WDone()
+ CopyArea(win, org, x0, y0)
+ WAttrib(win, "resize=on")
+ while e := Event(win) do case e of {
+ QuitEvents():
+ exit()
+ &resize:
+ Zoom(org, win, , , , , x0, y0)
+ !"123456789": {
+ ww := e * w
+ hh := e * h
+ WAttrib(win, "width=" || ww, "height=" || hh)
+ Zoom(org, win, , , , , x0, y0, ww, hh)
+ }
+ }
+end
+
+procedure SubWindow(win, x, y, w, h)
+ static type
+ initial type := proc("type", 0) # protect attractive name
+
+ if type(win) ~== "window" then
+ return SubWindow((\&window | runerr(140)), win, x, y, w)
+
+ /x := -WAttrib(win, "dx")
+ /y := -WAttrib(win, "dy")
+ /w := WAttrib(win, "width") - (x + WAttrib(win, "dx"))
+ /h := WAttrib(win, "height") - (y + WAttrib(win, "dy"))
+
+ if w < 0 then
+ x -:= (w := -w)
+ if h < 0 then
+ y -:= (h := -h)
+
+ win := Clone(win,
+ "dx=" || WAttrib(win, "dx") + x,
+ "dy=" || WAttrib(win, "dy") + y)
+ Clip(win, 0, 0, w, h)
+ GotoRC(win, 1, 1)
+ return win
+end
diff --git a/ipl/gprocs/xbfont.icn b/ipl/gprocs/xbfont.icn
new file mode 100644
index 0000000..6ba7a7d
--- /dev/null
+++ b/ipl/gprocs/xbfont.icn
@@ -0,0 +1,322 @@
+############################################################################
+#
+# File: xbfont.icn
+#
+# Subject: Procedures for X font selection
+#
+# Author: Gregg M. Townsend
+#
+# Date: May 2, 2001
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# BestFont(W, s, ...) generates X-windows font names matching a
+# given specification, beginning with the closest match. The
+# ranking algorithm is similar to that used in Font() but it is
+# not identical.
+#
+############################################################################
+#
+# BestFont(window, spec, ...) returns the name of whichever available
+# X-Windows font most closely matches the given specification. Note that
+# matching is done using a slightly different algorithm from that of the
+# Icon runtime system; this procedure preceded Icon's font selection
+# implementation and served as a prototype.
+#
+# The font specification is one or more strings containing whitespace-
+# or comma-separated tokens. Tokens are case-insensitive. There are
+# three kinds of tokens.
+# A token having the form of an integer specifies the desired "pixel
+# size" (height). If no size is included, a target size of 14 is used.
+# An unrecognized token is taken as a substring of the desired X font
+# name. Family names, weights, and other such factors are specified this
+# way.
+# Certain tokens are recognized and handled specially:
+# m mono monospaced
+# p prop proportional
+# r roman
+# i italic
+# o oblique
+# s sans sans-serif sansserif
+# These are turned into search strings of a particular form. For example,
+# "roman" and "r" specify the search string "-r-".
+#
+# The "best match" to a given specification is calculated by reviewing
+# all the available fonts, assigning a score to each, then choosing the
+# one with the highest value. There are several aspects of scoring.
+# Size is the most important factor. A tuned font of the correct size
+# gets the maximum score. Nearby sizes receive partial credit, with
+# an undersized font preferred over an oversized font. Scalable fonts
+# are also recognized, but a tuned font of the correct or nearly-correct
+# size gets a higher score.
+# Each successful substring match increases the score, whether the
+# test string comes from an unrecognized token or a special keyword.
+# Earlier tokens receive slightly more weight than later ones.
+# All tokens need not match. The string "lucida gill sans 18"
+# is perfectly reasonable; it specifies a preference for Lucida Sans
+# over Gill Sans by the position of the tokens, but will match either.
+# Ties are broken by giving slight preferences for normal weight,
+# no slant, normal width, and ASCII ("iso8859") encoding. A slight
+# penalty is assessed for "typewriter" fonts. Oblique fonts receive
+# partial credit for matching "italic" requests, and vice versa.
+# The scoring function can be altered by assigning values to certain
+# global variables. See XBF_defaults() for a commented list of these.
+#
+# For a scalable font, the returned value is a string specifying an
+# instance of the font scaled to the target size. For large sizes, the
+# scaling time may be noticeable when the font is used.
+#
+# BestFont() is actually a generator that produces the entire list
+# of available fonts in order of preference. RankFonts(w, spec, ...)
+# is similar to BestFont but produces a sequence of two-element records,
+# where result.str is the font name and result.val is its score. For
+# either of these, a list of X font names can be passed instead of a
+# window.
+#
+# There is some startup cost the first time BestFont is called; it
+# opens a pipe to the "xlsfonts" program and reads the output. Results
+# are cached, so this overhead is only incurred once.
+#
+# Examples:
+# Font(w, BestFont(w, "times bold italic 20"))
+# s := BestFont(w, size, family, "italic")
+#
+############################################################################
+#
+# Requires: Version 9 graphics under Unix
+#
+############################################################################
+
+
+record XBF_rec(str, val)
+
+global XBF_wantsize # requested font size
+global XBF_sizval # array of scores indexed by actual font size
+
+
+# globals used for tuning the scoring function; see XBF_defaults()
+
+global XFW_defsize, XFW_size, XFW_maxover, XFW_maxunder, XFW_scaled
+global XFW_spacing, XFW_slant, XFW_aslant, XFW_sans
+global XFW_default, XFW_exact, XFW_posn, XFW_tiebreakers
+
+
+# BestFont(window, spec...) - generate ranked sequence of font names
+
+procedure BestFont(args[]) #: generate best X fonts
+ suspend (RankFonts ! args) . str
+end
+
+
+# XRankFont(window, spec...) - generate sequence of (name,score) tuples
+
+procedure RankFonts(w, args[]) #: generate scores for X fonts
+ local tokens, cklist, sclist, fspec, ranks, r
+
+ if type(w) ~== "window" & type(w) ~== "list" then {
+ push(args, w)
+ w := &window
+ }
+ XBF_defaults() # set default values
+ XBF_wantsize := XFW_defsize # set target size to default
+ tokens := XBF_tokenlist(args) # break args into list of tokens
+ cklist := XBF_weights(tokens) # get list of (substring,weight)s
+ XBF_sizval := XBF_sizes(XBF_wantsize) # build array for scoring sizes
+
+ # make a list of (fontname,score) tuples, and sort it
+ sclist := []
+ every fspec := XBF_fontlist(w) do
+ put(sclist, XBF_rec(fspec, XBF_eval(fspec, cklist)))
+ ranks := sortf(sclist, 2)
+
+ # generate results from hightest to lowest rank
+ while r := pull(ranks) do
+ suspend XBF_rec(XBF_spec(r.str, XBF_wantsize), r.val)
+end
+
+
+# XBF_defaults() - assign default values to any unset tuning parameters
+
+procedure XBF_defaults()
+ /XFW_defsize := 14 # default size if unspecified
+ /XFW_size := 1000 # points for matching size exactly
+ /XFW_maxover := 30 # max allowable overage on size (per cent)
+ /XFW_maxunder := 60 # max allowable shortfall on size (per cent)
+ /XFW_scaled := 800 # points for matching size with scaled font
+
+ /XFW_spacing := 500 # points for matching prop/mono spacing
+ /XFW_slant := 500 # points for matching slant
+ /XFW_aslant := 300 # points for approx slant (oblique : italic)
+ /XFW_sans := 500 # points for matching "sans" spec
+
+ /XFW_exact := 1100 # points for matching entire font name
+ /XFW_default := 500 # points for matching unrecognized token
+ /XFW_posn := 10 # points for position in request list
+
+ /XFW_tiebreakers := [ # "tiebreaker" strings always scored
+ XBF_rec("-normal-", 1), # prefer normal width
+ XBF_rec("-medium-", 1), # prefer medium weight
+ XBF_rec("-r-", 2), # upright slant is even more important
+ XBF_rec("-iso8859-", 1), # prefer ASCII, not symbol/kana/etc
+ XBF_rec("typewriter", -4)] # penalize typewriter fonts
+
+ return
+end
+
+
+# XBF_tokenlist(args) -- turn list of args into list of tokens
+
+procedure XBF_tokenlist(args)
+ local tokens
+
+ tokens := []
+ every map(trim(!args)) ? repeat {
+ tab(many(' \t,'))
+ if pos(0) then
+ break
+ put(tokens, tab(upto(' \t,') | 0))
+ }
+ return tokens
+end
+
+
+# XBF_weights(tokens) -- turn tokens into list of substrings and weights
+#
+# Also saves the size value in the global XBF_wantsize.
+
+procedure XBF_weights(tokens)
+ local cklist, tk, pf
+
+ cklist := []
+ pf := *tokens * XFW_posn
+ every tk := !tokens do {
+ if not (XBF_wantsize := integer(tk)) then {
+ pf -:= XFW_posn
+ case tk of {
+ "m" | "mono" | "monospaced":
+ every put(cklist, XBF_rec("-m-" | "-c-", XFW_spacing + pf))
+ "p" | "prop" | "proportional":
+ put(cklist, XBF_rec("-p-", XFW_spacing + pf))
+ "r" | "roman":
+ put(cklist, XBF_rec("-r-", XFW_slant + pf))
+ "i" | "italic": {
+ put(cklist, XBF_rec("-i-", XFW_slant + pf))
+ put(cklist, XBF_rec("-o-", XFW_aslant + pf))
+ }
+ "o" | "oblique": {
+ put(cklist, XBF_rec("-o-", XFW_slant + pf))
+ put(cklist, XBF_rec("-i-", XFW_aslant + pf))
+ }
+ "s" | "sans" | "sans-serif" | "sansserif":
+ put(cklist, XBF_rec("sans", XFW_sans + pf))
+ default:
+ put(cklist, XBF_rec(tk, XFW_default + pf))
+ }
+ }
+ }
+ every put(cklist, !XFW_tiebreakers)
+ return cklist
+end
+
+
+# XBF_sizes(wantsize) -- build array of scores for evaluating font sizes
+
+procedure XBF_sizes(wantsize)
+ local l, sz, diff, score, maxunder, maxover
+
+ l := [XFW_scaled] # initial entry scores scaled fonts
+
+ # set scores for undersized fonts
+ maxunder := (XFW_maxunder / 100.0) * wantsize
+ every sz := 1 to wantsize-1 do {
+ diff := wantsize - sz
+ score := integer(XFW_size * (1 - diff / maxunder))
+ score <:= 0
+ put(l, score)
+ }
+
+ # set scores for correct and oversized fonts
+ maxover := (XFW_maxover / 100.0) * wantsize
+ repeat {
+ sz +:= 1
+ diff := sz - wantsize
+ score := integer(XFW_size * (1 - diff / maxover))
+ if score <= 0 then
+ break # quit when too big to be useful
+ put(l, score)
+ }
+
+ return l
+end
+
+
+# XBF_fontlist(w) - generate list of font names for window (or list) w
+
+procedure XBF_fontlist(w)
+ static fontlist
+ local pipe
+
+ if type(w) == "list" then
+ suspend !w
+ else {
+ if /fontlist then {
+ fontlist := []
+ pipe := open("xlsfonts", "rp") | stop("can't open xlsfonts pipe")
+ while put(fontlist, trim(read(pipe)))
+ close(pipe)
+ }
+ suspend !fontlist
+ }
+end
+
+
+# XBF_eval(fontname, cklist) -- evaluate the score of an X font name
+
+procedure XBF_eval(fontname, cklist)
+ local t, r
+
+ # find the size and look up its score in the XBF_sizval array
+ fontname ? {
+ every 1 to 7 do
+ tab(upto('-')) & move(1)
+ t := XBF_sizval [1 + integer(tab(upto('-')))] | 0
+ }
+
+ # add the corresponding value for every substring that matches
+ every r := !cklist do
+ if find(r.str, fontname) then
+ if r.str == fontname then
+ t +:= XFW_exact # high score for matching entire name
+ else
+ t +:= r.val # else give specified value
+ return t
+end
+
+
+# XBF_spec(fontname, size) -- return the correct form of an X font name
+#
+# This is just the name itself except in the case of scalable fonts.
+
+procedure XBF_spec(fontname, size)
+ local s
+
+ fontname ? {
+ s := tab(find("-0-0-")) | return fontname # return if not scalable
+ move(5) # skip pixel size, point size
+ tab(upto('-')) & move(1) # skip x-resolution
+ tab(upto('-')) & move(1) # skip y-resolution
+ s ||:= "-"
+ s ||:= size # spec pixel size
+ s ||:= "-*-*-*-" # wildcard ptsize & resolutions
+ s ||:= tab(upto('-')) # copy spacing field
+ s ||:= move(1)
+ tab(upto('-')) # skip average width
+ s ||:= "*"
+ s ||:= tab(0) # copy the rest
+ }
+ return s
+end
diff --git a/ipl/gprocs/xcolor.icn b/ipl/gprocs/xcolor.icn
new file mode 100644
index 0000000..cc243dc
--- /dev/null
+++ b/ipl/gprocs/xcolor.icn
@@ -0,0 +1,21 @@
+############################################################################
+#
+# File: xcolor.icn
+#
+# Subject: Declaration to link color
+#
+# Author: Gregg M. Townsend
+#
+# Date: June 9, 1994
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# compatibility file
+#
+############################################################################
+
+link color
diff --git a/ipl/gprocs/xcompat.icn b/ipl/gprocs/xcompat.icn
new file mode 100644
index 0000000..f9aef40
--- /dev/null
+++ b/ipl/gprocs/xcompat.icn
@@ -0,0 +1,110 @@
+############################################################################
+#
+# File: xcompat.icn
+#
+# Subject: Procedures for compatibility with 8.10 graphics
+#
+# Authors: Gregg M. Townsend and Ralph E. Griswold
+#
+# Date: May 26, 1994
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# This file provides compatible implementation of Icon 8.10 functions
+# that cannot be replaced with 9.0 functions via the simple renaming
+# done in xnames.icn. The following procedures are provided:
+#
+# XBind(w1, w2, ...)
+# XUnbind()
+# XWindowLabel(s)
+# XDrawArc(w,x,y,width,height,a1,a2,...),
+# XFillArc(w,x,y,width,height,a1,a2,...),
+#
+############################################################################
+#
+# Requires: Version 9 graphics
+#
+############################################################################
+
+
+procedure XBind(args[])
+ local window
+
+ if type(args[2]) == type(args[1]) == "window" then
+ return Couple ! args # two windows: couple them
+
+ if type(args[1]) == "window" then { # one window: clone it
+ window := pop(args)
+ if /args[1] then
+ pop(args)
+ push(args, window)
+ return Clone ! args
+ }
+
+ # no windows: create hidden canvas
+ while /args[1] do # remove leading null args
+ pop(args)
+ if type(args[1]) == "window" then # remove possible arg2 window
+ pop(args)
+ while /args[-1] do # remove trailing null args
+ pull(args)
+ put(args, "canvas=hidden") # turn into open() call
+ push(args, "x")
+ push(args, "window")
+ return open ! args
+end
+
+
+procedure XUnbind(args[])
+ XUnbind := proc("XUnbind" | "XUncouple" | "Uncouple", 0)
+ return XUnbind ! args
+end
+
+
+procedure XWindowLabel(win, s)
+ if type(win) == "window" then
+ WAttrib(win, "label=" || s)
+ else
+ WAttrib("label=" || win)
+ return
+end
+
+
+procedure XDrawArc(args[])
+ local a1, i
+ static m
+
+ initial m := -(2 * &pi) / (360 * 64)
+
+ if type(args[1]) == "window" then
+ a1 := 6
+ else
+ a1 := 5
+ every i := a1 to *args by 6 do {
+ args[i] *:= m
+ args[i + 1] *:= m
+ }
+ return DrawArc ! args
+end
+
+
+procedure XFillArc(args[])
+ local a1, i
+ static m
+
+ initial m := -(2 * &pi) / (360 * 64)
+
+ if type(args[1]) == "window" then
+ a1 := 6
+ else
+ a1 := 5
+ every i := a1 to *args by 6 do {
+ args[i] *:= m
+ args[i + 1] *:= m
+ }
+ return FillArc ! args
+end
diff --git a/ipl/gprocs/xform.icn b/ipl/gprocs/xform.icn
new file mode 100644
index 0000000..6377c3a
--- /dev/null
+++ b/ipl/gprocs/xform.icn
@@ -0,0 +1,60 @@
+############################################################################
+#
+# File: xform.icn
+#
+# Subject: Procedures to transform points
+#
+# Author: Ralph E. Griswold
+#
+# Date: October 1, 1997
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# This file contains procedures that manipulate points representing
+# vertices.
+#
+############################################################################
+#
+# Links: calls, gobject
+#
+############################################################################
+
+link calls, gobject
+
+procedure p_xlate(call, x, y)
+ local point
+
+ every point := invoke(call) do {
+ point.x +:= x
+ point.y +:= y
+ suspend point
+ }
+
+end
+
+procedure p_scale(call, factor)
+ local point
+
+ every point := invoke(call) do {
+ point.x *:= factor
+ point.y *:= factor
+ suspend point
+ }
+
+end
+
+procedure p_rotate(call, angle)
+ local point, radius
+
+ every point := invoke(call) do {
+ radius := sqrt(point.x ^ 2, point.y ^ 2)
+ point.x *:= radius * cos(angle)
+ point.y *:= radius * sin(angle)
+ suspend point
+ }
+
+end
diff --git a/ipl/gprocs/xformimg.icn b/ipl/gprocs/xformimg.icn
new file mode 100644
index 0000000..ce2b2f2
--- /dev/null
+++ b/ipl/gprocs/xformimg.icn
@@ -0,0 +1,168 @@
+############################################################################
+#
+# File: xformimg.icn
+#
+# Subject: Procedures to transform image
+#
+# Author: Ralph E. Griswold
+#
+# Date: February 4, 1995
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# These procedures perform reflections, rotations, and concatenations
+# of images.
+#
+# Warning: Some of these operations are slow.
+#
+############################################################################
+#
+# Requires: Version 9 graphics
+#
+############################################################################
+#
+# Links: numbers, wattrib, wopen
+#
+############################################################################
+
+link numbers
+link wattrib
+link wopen
+
+procedure wreflect(win1, dir)
+ local win2, x1, x2, y1, y2, width, height
+
+ /dir := "v" # vertical reflection is the default
+
+ height := Height(win1)
+ width := Width(win1)
+
+ win2 := WOpen("canvas=hidden", "width=" || width, "height=" || height) |
+ stop("*** cannot window for reflection")
+
+ case dir of {
+ "h": {
+ x2 := 0
+ y2 := height - 1
+ every Fg(win2, Pixel(win1)) do {
+ DrawPoint(win2, x2, y2)
+ if x2 = width - 1 then {
+ x2 := 0
+ y2 -:= 1
+ }
+ else x2 +:= 1
+ }
+ }
+ "v": {
+ x2 := width - 1
+ y2 := 0
+ every Fg(win2, Pixel(win1)) do {
+ DrawPoint(win2, x2, y2)
+ if x2 = 0 then {
+ x2 := width - 1
+ y2 +:= 1
+ }
+ else x2 -:= 1
+ }
+ }
+ default: stop("*** invalid specification for reflect()")
+ }
+
+ return win2
+
+end
+
+procedure wrotate(win1, dir)
+ local win2, x1, x2, y1, y2, width, height
+
+ /dir := "90" # 90-degree rotation is the default
+
+ height := Height(win1)
+ width := Width(win1)
+
+
+ case integer(dir) of {
+ 90: {
+ x2 := height - 1
+ y2 := 0
+ win2 := WOpen("canvas=hidden", "width=" || height,
+ "height=" || width) | stop("*** cannot open target window")
+ every Fg(win2, Pixel(win1)) do {
+ DrawPoint(win2, x2, y2)
+ if y2 = width - 1 then {
+ y2 := 0
+ x2 -:= 1
+ }
+ else y2 +:= 1
+ }
+ }
+ -90: {
+ win2 := WOpen("canvas=hidden", "width=" || height,
+ "height=" || width) | stop("*** cannot open target window")
+ x2 := 0
+ y2 := width - 1
+ every Fg(win2, Pixel(win1)) do {
+ DrawPoint(win2, x2, y2)
+ if y2 = 0 then {
+ y2 := width - 1
+ x2 +:= 1
+ }
+ else y2 -:= 1
+ }
+ }
+ 180: {
+ win2 := WOpen("canvas=hidden", "width=" || width,
+ "height=" || height) | stop("*** cannot open target window")
+ x2 := width - 1
+ y2 := height - 1
+ every Fg(win2, Pixel(win1)) do {
+ DrawPoint(win2, x2, y2)
+ if x2 = 0 then {
+ x2 := width - 1
+ y2 -:= 1
+ }
+ else x2 -:= 1
+ }
+ }
+ default: stop("*** invalid specification for rotate()")
+ } | stop("*** invalid specification for rotate()")
+
+ return win2
+
+end
+
+procedure wcatenate(win1, win2, dir)
+ local width1, width2, height1, height2, win3
+
+ /dir := "h" # horizontal concatenation is the default
+
+ width1 := Width(win1)
+ width2 := Width(win2)
+ height1 := Height(win1)
+ height2 := Height(win2)
+
+ case dir of {
+ "h": {
+ win3 := WOpen("canvas=hidden", "width=" || (width1 + width2),
+ "height=" || max(height1, height2)) |
+ stop("*** cannot open window for concatenation")
+ CopyArea(win1, win3)
+ CopyArea(win2, win3, 0, 0, width2, height2, width1, 0)
+ }
+ "v": {
+ win3 := WOpen("canvas=hidden", "width=" || max(width1, width2),
+ "height=" || (height1 + height2)) |
+ stop("*** cannot open window for concatenation")
+ CopyArea(win1, win3)
+ CopyArea(win2, win3, 0, 0, width2, height2, 0, height1)
+ }
+ default: stop("*** invalid specification for catenate()")
+ }
+
+ return win3
+
+end
diff --git a/ipl/gprocs/xgtrace.icn b/ipl/gprocs/xgtrace.icn
new file mode 100644
index 0000000..16afd10
--- /dev/null
+++ b/ipl/gprocs/xgtrace.icn
@@ -0,0 +1,81 @@
+############################################################################
+#
+# File: xgtrace.icn
+#
+# Subject: Procedures to draw traces of points
+#
+# Author: Ralph E. Griswold
+#
+# Date: November 19, 1997
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# As used here, the term "trace" refers to a sequence of points that
+# generally consists of locations on a curve or other geometrical object.
+#
+############################################################################
+#
+# Requires: Version 9 graphics
+#
+############################################################################
+#
+# Links: gtace, turtle
+#
+############################################################################
+
+link gtrace
+link turtle
+
+#
+# line_trace(call) draws lines along the figure described by the trace from
+# invoke(call).
+
+procedure line_trace(call)
+ local TPlot, point
+
+ TPlot := TGoto # go to first point
+ every point := invoke(call) do {
+ TPlot(point.x, point.y)
+ TPlot := TDrawto # draw subsequently
+ }
+
+ return
+
+end
+
+#
+# segment_trace(call) draws line segments between successive pairs of
+# points along the figure described by the trace from invoke(call).
+
+procedure segment_trace(call)
+ local TPlot, TPlotNext, point
+
+ TPlot := TGoto # go to first point
+ TPlotNext := TDrawto
+ every point := invoke(call) do {
+ TPlot(point.x, point.y)
+ TPlot :=: TPlotNext # draw subsequently
+ }
+
+ return
+
+end
+
+#
+# curve_trace(call) draws a curve along the figure described by the trace
+# from invoke(call).
+#
+procedure curve_trace(call, limit)
+ local points, n
+
+ /limit := 500 # maximum number of points allowed
+
+ DrawCurve ! coord_list(call, limit)
+
+ return
+
+end
diff --git a/ipl/gprocs/xio.icn b/ipl/gprocs/xio.icn
new file mode 100644
index 0000000..7cacd46
--- /dev/null
+++ b/ipl/gprocs/xio.icn
@@ -0,0 +1,22 @@
+############################################################################
+#
+# File: xio.icn
+#
+# Subject: Declarations to link window I/O
+#
+# Author: Gregg M. Townsend
+#
+# Date: June 9, 1994
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# compatibility file
+#
+############################################################################
+
+link wopen
+link window
diff --git a/ipl/gprocs/xplane.icn b/ipl/gprocs/xplane.icn
new file mode 100644
index 0000000..93ae325
--- /dev/null
+++ b/ipl/gprocs/xplane.icn
@@ -0,0 +1,21 @@
+############################################################################
+#
+# File: xplane.icn
+#
+# Subject: Declaration to link bitplane
+#
+# Author: Gregg M. Townsend
+#
+# Date: June 9, 1994
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# compatibility file
+#
+############################################################################
+
+link bitplane
diff --git a/ipl/gprocs/xputpixl.icn b/ipl/gprocs/xputpixl.icn
new file mode 100644
index 0000000..5240dcb
--- /dev/null
+++ b/ipl/gprocs/xputpixl.icn
@@ -0,0 +1,21 @@
+############################################################################
+#
+# File: xputpixl.icn
+#
+# Subject: Declaration to link putpixel
+#
+# Author: Gregg M. Townsend
+#
+# Date: June 9, 1994
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# compatibility file
+#
+############################################################################
+
+link putpixel
diff --git a/ipl/gprocs/xqueue.icn b/ipl/gprocs/xqueue.icn
new file mode 100644
index 0000000..832e5b1
--- /dev/null
+++ b/ipl/gprocs/xqueue.icn
@@ -0,0 +1,21 @@
+############################################################################
+#
+# File: xqueue.icn
+#
+# Subject: Declaration to link enqueue
+#
+# Author: Gregg M. Townsend
+#
+# Date: June 9, 1994
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# compatibility file
+#
+############################################################################
+
+link enqueue
diff --git a/ipl/gprocs/xutils.icn b/ipl/gprocs/xutils.icn
new file mode 100644
index 0000000..8c46067
--- /dev/null
+++ b/ipl/gprocs/xutils.icn
@@ -0,0 +1,37 @@
+############################################################################
+#
+# File: xutils.icn
+#
+# Subject: Procedures for graphics utilities
+#
+# Author: Gregg M. Townsend
+#
+# Date: June 9, 1994
+#
+############################################################################
+#
+# This file is in the public domain.
+#
+############################################################################
+#
+# compatibility file
+#
+############################################################################
+
+link wopen
+link gpxop
+link gpxlib
+
+procedure Quit(win)
+ /win := &window
+ while *Pending(win) > 0 do
+ if Event(win) === QuitEvents() then
+ return win
+ fail
+end
+
+procedure Done(win)
+ /win := &window
+ until Event(win) === QuitEvents()
+ exit()
+end