summaryrefslogtreecommitdiff
path: root/sys/src/cmd/abaco
diff options
context:
space:
mode:
authorTaru Karttunen <taruti@taruti.net>2011-03-30 15:46:40 +0300
committerTaru Karttunen <taruti@taruti.net>2011-03-30 15:46:40 +0300
commite5888a1ffdae813d7575f5fb02275c6bb07e5199 (patch)
treed8d51eac403f07814b9e936eed0c9a79195e2450 /sys/src/cmd/abaco
Import sources from 2011-03-30 iso image
Diffstat (limited to 'sys/src/cmd/abaco')
-rwxr-xr-xsys/src/cmd/abaco/NOTES14
-rwxr-xr-xsys/src/cmd/abaco/abaco.fonts20
-rwxr-xr-xsys/src/cmd/abaco/charsets.awk36
-rwxr-xr-xsys/src/cmd/abaco/charsets.txt1868
-rwxr-xr-xsys/src/cmd/abaco/cols.c549
-rwxr-xr-xsys/src/cmd/abaco/dat.h355
-rwxr-xr-xsys/src/cmd/abaco/exec.c531
-rwxr-xr-xsys/src/cmd/abaco/fns.h85
-rwxr-xr-xsys/src/cmd/abaco/fonts.h20
-rwxr-xr-xsys/src/cmd/abaco/html.c1072
-rwxr-xr-xsys/src/cmd/abaco/main.c433
-rwxr-xr-xsys/src/cmd/abaco/mkfile52
-rwxr-xr-xsys/src/cmd/abaco/page.c839
-rwxr-xr-xsys/src/cmd/abaco/plumbing10
-rwxr-xr-xsys/src/cmd/abaco/rows.c252
-rwxr-xr-xsys/src/cmd/abaco/scrl.c317
-rwxr-xr-xsys/src/cmd/abaco/tabs.c332
-rwxr-xr-xsys/src/cmd/abaco/tcs.h167
-rwxr-xr-xsys/src/cmd/abaco/tcs.txt58
-rwxr-xr-xsys/src/cmd/abaco/text.c880
-rwxr-xr-xsys/src/cmd/abaco/time.c119
-rwxr-xr-xsys/src/cmd/abaco/urls.c230
-rwxr-xr-xsys/src/cmd/abaco/util.c1293
-rwxr-xr-xsys/src/cmd/abaco/wind.c383
24 files changed, 9915 insertions, 0 deletions
diff --git a/sys/src/cmd/abaco/NOTES b/sys/src/cmd/abaco/NOTES
new file mode 100755
index 000000000..bc852c14d
--- /dev/null
+++ b/sys/src/cmd/abaco/NOTES
@@ -0,0 +1,14 @@
+Bugs:
+ * fix text selection and double clicking;
+
+Not Bugs:
+ * complaints like "gif: decode <stdin> failed: ReadGIF: can't recognize format ��"
+ are caused by sites that return a jpeg and send Content-Type: image/gif.
+
+Not implented:
+ * frame's borders and scroll are ignored
+ * Image: maps
+ * table atributes
+ * css
+ * Js
+
diff --git a/sys/src/cmd/abaco/abaco.fonts b/sys/src/cmd/abaco/abaco.fonts
new file mode 100755
index 000000000..5c467449c
--- /dev/null
+++ b/sys/src/cmd/abaco/abaco.fonts
@@ -0,0 +1,20 @@
+/lib/font/bit/freefont/sans/sans.12.font
+/lib/font/bit/freefont/sans/sans.13.font
+/lib/font/bit/freefont/sans/sans.15.font
+/lib/font/bit/freefont/sans/sans.18.font
+/lib/font/bit/freefont/sans/sans.20.font
+/lib/font/bit/freefont/sansi/sansi.12.font
+/lib/font/bit/freefont/sansi/sansi.13.font
+/lib/font/bit/freefont/sansi/sansi.15.font
+/lib/font/bit/freefont/sansi/sansi.18.font
+/lib/font/bit/freefont/sansi/sansi.20.font
+/lib/font/bit/freefont/sansbd/sansbd.12.font
+/lib/font/bit/freefont/sansbd/sansbd.13.font
+/lib/font/bit/freefont/sansbd/sansbd.15.font
+/lib/font/bit/freefont/sansbd/sansbd.18.font
+/lib/font/bit/freefont/sansbd/sansbd.20.font
+/lib/font/bit/freefont/mono/mono.12.font
+/lib/font/bit/freefont/mono/mono.13.font
+/lib/font/bit/freefont/mono/mono.15.font
+/lib/font/bit/freefont/mono/mono.18.font
+/lib/font/bit/freefont/mono/mono.20.font
diff --git a/sys/src/cmd/abaco/charsets.awk b/sys/src/cmd/abaco/charsets.awk
new file mode 100755
index 000000000..e822f80e8
--- /dev/null
+++ b/sys/src/cmd/abaco/charsets.awk
@@ -0,0 +1,36 @@
+#!/bin/awk -f
+# makes a table of character sets from http://www.iana.org/assignments/character-sets
+# and tcs.txt
+
+BEGIN{
+ if(ARGC != 3){
+ print "Usage: " ARGV[0] " charsets.txt tcs.txt"
+ exit 1
+ }
+ while(getline<ARGV[1]){
+ if(/^Name:/){
+ i = 0
+ name=tolower($2)
+ names[name] = name
+ alias[name i] = name
+ nalias[name] = ++i
+
+ }
+ if(/^Alias:/){
+ a = tolower($2)
+ if(a != "none"){
+ names[a] = name
+ alias[name i ] = a
+ nalias[name] = ++i
+ }
+ }
+ }
+}
+{
+ tcs = $1
+ if(tcs in names){
+ name = names[tcs]
+ for(i=0; i<nalias[name]; i++)
+ print "\"" alias[name i] "\", \"" $2 "\","
+ }
+}
diff --git a/sys/src/cmd/abaco/charsets.txt b/sys/src/cmd/abaco/charsets.txt
new file mode 100755
index 000000000..350858a82
--- /dev/null
+++ b/sys/src/cmd/abaco/charsets.txt
@@ -0,0 +1,1868 @@
+
+===================================================================
+CHARACTER SETS
+
+(last updated 28 January 2005)
+
+These are the official names for character sets that may be used in
+the Internet and may be referred to in Internet documentation. These
+names are expressed in ANSI_X3.4-1968 which is commonly called
+US-ASCII or simply ASCII. The character set most commonly use in the
+Internet and used especially in protocol standards is US-ASCII, this
+is strongly encouraged. The use of the name US-ASCII is also
+encouraged.
+
+The character set names may be up to 40 characters taken from the
+printable characters of US-ASCII. However, no distinction is made
+between use of upper and lower case letters.
+
+The MIBenum value is a unique value for use in MIBs to identify coded
+character sets.
+
+The value space for MIBenum values has been divided into three
+regions. The first region (3-999) consists of coded character sets
+that have been standardized by some standard setting organization.
+This region is intended for standards that do not have subset
+implementations. The second region (1000-1999) is for the Unicode and
+ISO/IEC 10646 coded character sets together with a specification of a
+(set of) sub-repertoires that may occur. The third region (>1999) is
+intended for vendor specific coded character sets.
+
+ Assigned MIB enum Numbers
+ -------------------------
+ 0-2 Reserved
+ 3-999 Set By Standards Organizations
+ 1000-1999 Unicode / 10646
+ 2000-2999 Vendor
+
+The aliases that start with "cs" have been added for use with the
+IANA-CHARSET-MIB as originally defined in RFC3808, and as currently
+maintained by IANA at http://www.iana.org/assignments/ianacharset-mib.
+Note that the ianacharset-mib needs to be kept in sync with this
+registry. These aliases that start with "cs" contain the standard
+numbers along with suggestive names in order to facilitate applications
+that want to display the names in user interfaces. The "cs" stands
+for character set and is provided for applications that need a lower
+case first letter but want to use mixed case thereafter that cannot
+contain any special characters, such as underbar ("_") and dash ("-").
+
+If the character set is from an ISO standard, its cs alias is the ISO
+standard number or name. If the character set is not from an ISO
+standard, but is registered with ISO (IPSJ/ITSCJ is the current ISO
+Registration Authority), the ISO Registry number is specified as
+ISOnnn followed by letters suggestive of the name or standards number
+of the code set. When a national or international standard is
+revised, the year of revision is added to the cs alias of the new
+character set entry in the IANA Registry in order to distinguish the
+revised character set from the original character set.
+
+
+Character Set Reference
+------------- ---------
+
+Name: ANSI_X3.4-1968 [RFC1345,KXS2]
+MIBenum: 3
+Source: ECMA registry
+Alias: iso-ir-6
+Alias: ANSI_X3.4-1986
+Alias: ISO_646.irv:1991
+Alias: ASCII
+Alias: ISO646-US
+Alias: US-ASCII (preferred MIME name)
+Alias: us
+Alias: IBM367
+Alias: cp367
+Alias: csASCII
+
+Name: ISO-10646-UTF-1
+MIBenum: 27
+Source: Universal Transfer Format (1), this is the multibyte
+ encoding, that subsets ASCII-7. It does not have byte
+ ordering issues.
+Alias: csISO10646UTF1
+
+Name: ISO_646.basic:1983 [RFC1345,KXS2]
+MIBenum: 28
+Source: ECMA registry
+Alias: ref
+Alias: csISO646basic1983
+
+Name: INVARIANT [RFC1345,KXS2]
+MIBenum: 29
+Alias: csINVARIANT
+
+Name: ISO_646.irv:1983 [RFC1345,KXS2]
+MIBenum: 30
+Source: ECMA registry
+Alias: iso-ir-2
+Alias: irv
+Alias: csISO2IntlRefVersion
+
+Name: BS_4730 [RFC1345,KXS2]
+MIBenum: 20
+Source: ECMA registry
+Alias: iso-ir-4
+Alias: ISO646-GB
+Alias: gb
+Alias: uk
+Alias: csISO4UnitedKingdom
+
+Name: NATS-SEFI [RFC1345,KXS2]
+MIBenum: 31
+Source: ECMA registry
+Alias: iso-ir-8-1
+Alias: csNATSSEFI
+
+Name: NATS-SEFI-ADD [RFC1345,KXS2]
+MIBenum: 32
+Source: ECMA registry
+Alias: iso-ir-8-2
+Alias: csNATSSEFIADD
+
+Name: NATS-DANO [RFC1345,KXS2]
+MIBenum: 33
+Source: ECMA registry
+Alias: iso-ir-9-1
+Alias: csNATSDANO
+
+Name: NATS-DANO-ADD [RFC1345,KXS2]
+MIBenum: 34
+Source: ECMA registry
+Alias: iso-ir-9-2
+Alias: csNATSDANOADD
+
+Name: SEN_850200_B [RFC1345,KXS2]
+MIBenum: 35
+Source: ECMA registry
+Alias: iso-ir-10
+Alias: FI
+Alias: ISO646-FI
+Alias: ISO646-SE
+Alias: se
+Alias: csISO10Swedish
+
+Name: SEN_850200_C [RFC1345,KXS2]
+MIBenum: 21
+Source: ECMA registry
+Alias: iso-ir-11
+Alias: ISO646-SE2
+Alias: se2
+Alias: csISO11SwedishForNames
+
+Name: KS_C_5601-1987 [RFC1345,KXS2]
+MIBenum: 36
+Source: ECMA registry
+Alias: iso-ir-149
+Alias: KS_C_5601-1989
+Alias: KSC_5601
+Alias: korean
+Alias: csKSC56011987
+
+Name: ISO-2022-KR (preferred MIME name) [RFC1557,Choi]
+MIBenum: 37
+Source: RFC-1557 (see also KS_C_5601-1987)
+Alias: csISO2022KR
+
+Name: EUC-KR (preferred MIME name) [RFC1557,Choi]
+MIBenum: 38
+Source: RFC-1557 (see also KS_C_5861-1992)
+Alias: csEUCKR
+
+Name: ISO-2022-JP (preferred MIME name) [RFC1468,Murai]
+MIBenum: 39
+Source: RFC-1468 (see also RFC-2237)
+Alias: csISO2022JP
+
+Name: ISO-2022-JP-2 (preferred MIME name) [RFC1554,Ohta]
+MIBenum: 40
+Source: RFC-1554
+Alias: csISO2022JP2
+
+Name: ISO-2022-CN [RFC1922]
+MIBenum: 104
+Source: RFC-1922
+
+Name: ISO-2022-CN-EXT [RFC1922]
+MIBenum: 105
+Source: RFC-1922
+
+Name: JIS_C6220-1969-jp [RFC1345,KXS2]
+MIBenum: 41
+Source: ECMA registry
+Alias: JIS_C6220-1969
+Alias: iso-ir-13
+Alias: katakana
+Alias: x0201-7
+Alias: csISO13JISC6220jp
+
+Name: JIS_C6220-1969-ro [RFC1345,KXS2]
+MIBenum: 42
+Source: ECMA registry
+Alias: iso-ir-14
+Alias: jp
+Alias: ISO646-JP
+Alias: csISO14JISC6220ro
+
+Name: IT [RFC1345,KXS2]
+MIBenum: 22
+Source: ECMA registry
+Alias: iso-ir-15
+Alias: ISO646-IT
+Alias: csISO15Italian
+
+Name: PT [RFC1345,KXS2]
+MIBenum: 43
+Source: ECMA registry
+Alias: iso-ir-16
+Alias: ISO646-PT
+Alias: csISO16Portuguese
+
+Name: ES [RFC1345,KXS2]
+MIBenum: 23
+Source: ECMA registry
+Alias: iso-ir-17
+Alias: ISO646-ES
+Alias: csISO17Spanish
+
+Name: greek7-old [RFC1345,KXS2]
+MIBenum: 44
+Source: ECMA registry
+Alias: iso-ir-18
+Alias: csISO18Greek7Old
+
+Name: latin-greek [RFC1345,KXS2]
+MIBenum: 45
+Source: ECMA registry
+Alias: iso-ir-19
+Alias: csISO19LatinGreek
+
+Name: DIN_66003 [RFC1345,KXS2]
+MIBenum: 24
+Source: ECMA registry
+Alias: iso-ir-21
+Alias: de
+Alias: ISO646-DE
+Alias: csISO21German
+
+Name: NF_Z_62-010_(1973) [RFC1345,KXS2]
+MIBenum: 46
+Source: ECMA registry
+Alias: iso-ir-25
+Alias: ISO646-FR1
+Alias: csISO25French
+
+Name: Latin-greek-1 [RFC1345,KXS2]
+MIBenum: 47
+Source: ECMA registry
+Alias: iso-ir-27
+Alias: csISO27LatinGreek1
+
+Name: ISO_5427 [RFC1345,KXS2]
+MIBenum: 48
+Source: ECMA registry
+Alias: iso-ir-37
+Alias: csISO5427Cyrillic
+
+Name: JIS_C6226-1978 [RFC1345,KXS2]
+MIBenum: 49
+Source: ECMA registry
+Alias: iso-ir-42
+Alias: csISO42JISC62261978
+
+Name: BS_viewdata [RFC1345,KXS2]
+MIBenum: 50
+Source: ECMA registry
+Alias: iso-ir-47
+Alias: csISO47BSViewdata
+
+Name: INIS [RFC1345,KXS2]
+MIBenum: 51
+Source: ECMA registry
+Alias: iso-ir-49
+Alias: csISO49INIS
+
+Name: INIS-8 [RFC1345,KXS2]
+MIBenum: 52
+Source: ECMA registry
+Alias: iso-ir-50
+Alias: csISO50INIS8
+
+Name: INIS-cyrillic [RFC1345,KXS2]
+MIBenum: 53
+Source: ECMA registry
+Alias: iso-ir-51
+Alias: csISO51INISCyrillic
+
+Name: ISO_5427:1981 [RFC1345,KXS2]
+MIBenum: 54
+Source: ECMA registry
+Alias: iso-ir-54
+Alias: ISO5427Cyrillic1981
+
+Name: ISO_5428:1980 [RFC1345,KXS2]
+MIBenum: 55
+Source: ECMA registry
+Alias: iso-ir-55
+Alias: csISO5428Greek
+
+Name: GB_1988-80 [RFC1345,KXS2]
+MIBenum: 56
+Source: ECMA registry
+Alias: iso-ir-57
+Alias: cn
+Alias: ISO646-CN
+Alias: csISO57GB1988
+
+Name: GB_2312-80 [RFC1345,KXS2]
+MIBenum: 57
+Source: ECMA registry
+Alias: iso-ir-58
+Alias: chinese
+Alias: csISO58GB231280
+
+Name: NS_4551-1 [RFC1345,KXS2]
+MIBenum: 25
+Source: ECMA registry
+Alias: iso-ir-60
+Alias: ISO646-NO
+Alias: no
+Alias: csISO60DanishNorwegian
+Alias: csISO60Norwegian1
+
+Name: NS_4551-2 [RFC1345,KXS2]
+MIBenum: 58
+Source: ECMA registry
+Alias: ISO646-NO2
+Alias: iso-ir-61
+Alias: no2
+Alias: csISO61Norwegian2
+
+Name: NF_Z_62-010 [RFC1345,KXS2]
+MIBenum: 26
+Source: ECMA registry
+Alias: iso-ir-69
+Alias: ISO646-FR
+Alias: fr
+Alias: csISO69French
+
+Name: videotex-suppl [RFC1345,KXS2]
+MIBenum: 59
+Source: ECMA registry
+Alias: iso-ir-70
+Alias: csISO70VideotexSupp1
+
+Name: PT2 [RFC1345,KXS2]
+MIBenum: 60
+Source: ECMA registry
+Alias: iso-ir-84
+Alias: ISO646-PT2
+Alias: csISO84Portuguese2
+
+Name: ES2 [RFC1345,KXS2]
+MIBenum: 61
+Source: ECMA registry
+Alias: iso-ir-85
+Alias: ISO646-ES2
+Alias: csISO85Spanish2
+
+Name: MSZ_7795.3 [RFC1345,KXS2]
+MIBenum: 62
+Source: ECMA registry
+Alias: iso-ir-86
+Alias: ISO646-HU
+Alias: hu
+Alias: csISO86Hungarian
+
+Name: JIS_C6226-1983 [RFC1345,KXS2]
+MIBenum: 63
+Source: ECMA registry
+Alias: iso-ir-87
+Alias: x0208
+Alias: JIS_X0208-1983
+Alias: csISO87JISX0208
+
+Name: greek7 [RFC1345,KXS2]
+MIBenum: 64
+Source: ECMA registry
+Alias: iso-ir-88
+Alias: csISO88Greek7
+
+Name: ASMO_449 [RFC1345,KXS2]
+MIBenum: 65
+Source: ECMA registry
+Alias: ISO_9036
+Alias: arabic7
+Alias: iso-ir-89
+Alias: csISO89ASMO449
+
+Name: iso-ir-90 [RFC1345,KXS2]
+MIBenum: 66
+Source: ECMA registry
+Alias: csISO90
+
+Name: JIS_C6229-1984-a [RFC1345,KXS2]
+MIBenum: 67
+Source: ECMA registry
+Alias: iso-ir-91
+Alias: jp-ocr-a
+Alias: csISO91JISC62291984a
+
+Name: JIS_C6229-1984-b [RFC1345,KXS2]
+MIBenum: 68
+Source: ECMA registry
+Alias: iso-ir-92
+Alias: ISO646-JP-OCR-B
+Alias: jp-ocr-b
+Alias: csISO92JISC62991984b
+
+Name: JIS_C6229-1984-b-add [RFC1345,KXS2]
+MIBenum: 69
+Source: ECMA registry
+Alias: iso-ir-93
+Alias: jp-ocr-b-add
+Alias: csISO93JIS62291984badd
+
+Name: JIS_C6229-1984-hand [RFC1345,KXS2]
+MIBenum: 70
+Source: ECMA registry
+Alias: iso-ir-94
+Alias: jp-ocr-hand
+Alias: csISO94JIS62291984hand
+
+Name: JIS_C6229-1984-hand-add [RFC1345,KXS2]
+MIBenum: 71
+Source: ECMA registry
+Alias: iso-ir-95
+Alias: jp-ocr-hand-add
+Alias: csISO95JIS62291984handadd
+
+Name: JIS_C6229-1984-kana [RFC1345,KXS2]
+MIBenum: 72
+Source: ECMA registry
+Alias: iso-ir-96
+Alias: csISO96JISC62291984kana
+
+Name: ISO_2033-1983 [RFC1345,KXS2]
+MIBenum: 73
+Source: ECMA registry
+Alias: iso-ir-98
+Alias: e13b
+Alias: csISO2033
+
+Name: ANSI_X3.110-1983 [RFC1345,KXS2]
+MIBenum: 74
+Source: ECMA registry
+Alias: iso-ir-99
+Alias: CSA_T500-1983
+Alias: NAPLPS
+Alias: csISO99NAPLPS
+
+Name: ISO_8859-1:1987 [RFC1345,KXS2]
+MIBenum: 4
+Source: ECMA registry
+Alias: iso-ir-100
+Alias: ISO_8859-1
+Alias: ISO-8859-1 (preferred MIME name)
+Alias: latin1
+Alias: l1
+Alias: IBM819
+Alias: CP819
+Alias: csISOLatin1
+
+Name: ISO_8859-2:1987 [RFC1345,KXS2]
+MIBenum: 5
+Source: ECMA registry
+Alias: iso-ir-101
+Alias: ISO_8859-2
+Alias: ISO-8859-2 (preferred MIME name)
+Alias: latin2
+Alias: l2
+Alias: csISOLatin2
+
+Name: T.61-7bit [RFC1345,KXS2]
+MIBenum: 75
+Source: ECMA registry
+Alias: iso-ir-102
+Alias: csISO102T617bit
+
+Name: T.61-8bit [RFC1345,KXS2]
+MIBenum: 76
+Alias: T.61
+Source: ECMA registry
+Alias: iso-ir-103
+Alias: csISO103T618bit
+
+Name: ISO_8859-3:1988 [RFC1345,KXS2]
+MIBenum: 6
+Source: ECMA registry
+Alias: iso-ir-109
+Alias: ISO_8859-3
+Alias: ISO-8859-3 (preferred MIME name)
+Alias: latin3
+Alias: l3
+Alias: csISOLatin3
+
+Name: ISO_8859-4:1988 [RFC1345,KXS2]
+MIBenum: 7
+Source: ECMA registry
+Alias: iso-ir-110
+Alias: ISO_8859-4
+Alias: ISO-8859-4 (preferred MIME name)
+Alias: latin4
+Alias: l4
+Alias: csISOLatin4
+
+Name: ECMA-cyrillic
+MIBenum: 77
+Source: ISO registry (formerly ECMA registry)
+ http://www.itscj.ipsj.jp/ISO-IR/111.pdf
+Alias: iso-ir-111
+Alias: KOI8-E
+Alias: csISO111ECMACyrillic
+
+Name: CSA_Z243.4-1985-1 [RFC1345,KXS2]
+MIBenum: 78
+Source: ECMA registry
+Alias: iso-ir-121
+Alias: ISO646-CA
+Alias: csa7-1
+Alias: ca
+Alias: csISO121Canadian1
+
+Name: CSA_Z243.4-1985-2 [RFC1345,KXS2]
+MIBenum: 79
+Source: ECMA registry
+Alias: iso-ir-122
+Alias: ISO646-CA2
+Alias: csa7-2
+Alias: csISO122Canadian2
+
+Name: CSA_Z243.4-1985-gr [RFC1345,KXS2]
+MIBenum: 80
+Source: ECMA registry
+Alias: iso-ir-123
+Alias: csISO123CSAZ24341985gr
+
+Name: ISO_8859-6:1987 [RFC1345,KXS2]
+MIBenum: 9
+Source: ECMA registry
+Alias: iso-ir-127
+Alias: ISO_8859-6
+Alias: ISO-8859-6 (preferred MIME name)
+Alias: ECMA-114
+Alias: ASMO-708
+Alias: arabic
+Alias: csISOLatinArabic
+
+Name: ISO_8859-6-E [RFC1556,IANA]
+MIBenum: 81
+Source: RFC1556
+Alias: csISO88596E
+Alias: ISO-8859-6-E (preferred MIME name)
+
+Name: ISO_8859-6-I [RFC1556,IANA]
+MIBenum: 82
+Source: RFC1556
+Alias: csISO88596I
+Alias: ISO-8859-6-I (preferred MIME name)
+
+Name: ISO_8859-7:1987 [RFC1947,RFC1345,KXS2]
+MIBenum: 10
+Source: ECMA registry
+Alias: iso-ir-126
+Alias: ISO_8859-7
+Alias: ISO-8859-7 (preferred MIME name)
+Alias: ELOT_928
+Alias: ECMA-118
+Alias: greek
+Alias: greek8
+Alias: csISOLatinGreek
+
+Name: T.101-G2 [RFC1345,KXS2]
+MIBenum: 83
+Source: ECMA registry
+Alias: iso-ir-128
+Alias: csISO128T101G2
+
+Name: ISO_8859-8:1988 [RFC1345,KXS2]
+MIBenum: 11
+Source: ECMA registry
+Alias: iso-ir-138
+Alias: ISO_8859-8
+Alias: ISO-8859-8 (preferred MIME name)
+Alias: hebrew
+Alias: csISOLatinHebrew
+
+Name: ISO_8859-8-E [RFC1556,Nussbacher]
+MIBenum: 84
+Source: RFC1556
+Alias: csISO88598E
+Alias: ISO-8859-8-E (preferred MIME name)
+
+Name: ISO_8859-8-I [RFC1556,Nussbacher]
+MIBenum: 85
+Source: RFC1556
+Alias: csISO88598I
+Alias: ISO-8859-8-I (preferred MIME name)
+
+Name: CSN_369103 [RFC1345,KXS2]
+MIBenum: 86
+Source: ECMA registry
+Alias: iso-ir-139
+Alias: csISO139CSN369103
+
+Name: JUS_I.B1.002 [RFC1345,KXS2]
+MIBenum: 87
+Source: ECMA registry
+Alias: iso-ir-141
+Alias: ISO646-YU
+Alias: js
+Alias: yu
+Alias: csISO141JUSIB1002
+
+Name: ISO_6937-2-add [RFC1345,KXS2]
+MIBenum: 14
+Source: ECMA registry and ISO 6937-2:1983
+Alias: iso-ir-142
+Alias: csISOTextComm
+
+Name: IEC_P27-1 [RFC1345,KXS2]
+MIBenum: 88
+Source: ECMA registry
+Alias: iso-ir-143
+Alias: csISO143IECP271
+
+Name: ISO_8859-5:1988 [RFC1345,KXS2]
+MIBenum: 8
+Source: ECMA registry
+Alias: iso-ir-144
+Alias: ISO_8859-5
+Alias: ISO-8859-5 (preferred MIME name)
+Alias: cyrillic
+Alias: csISOLatinCyrillic
+
+Name: JUS_I.B1.003-serb [RFC1345,KXS2]
+MIBenum: 89
+Source: ECMA registry
+Alias: iso-ir-146
+Alias: serbian
+Alias: csISO146Serbian
+
+Name: JUS_I.B1.003-mac [RFC1345,KXS2]
+MIBenum: 90
+Source: ECMA registry
+Alias: macedonian
+Alias: iso-ir-147
+Alias: csISO147Macedonian
+
+Name: ISO_8859-9:1989 [RFC1345,KXS2]
+MIBenum: 12
+Source: ECMA registry
+Alias: iso-ir-148
+Alias: ISO_8859-9
+Alias: ISO-8859-9 (preferred MIME name)
+Alias: latin5
+Alias: l5
+Alias: csISOLatin5
+
+Name: greek-ccitt [RFC1345,KXS2]
+MIBenum: 91
+Source: ECMA registry
+Alias: iso-ir-150
+Alias: csISO150
+Alias: csISO150GreekCCITT
+
+Name: NC_NC00-10:81 [RFC1345,KXS2]
+MIBenum: 92
+Source: ECMA registry
+Alias: cuba
+Alias: iso-ir-151
+Alias: ISO646-CU
+Alias: csISO151Cuba
+
+Name: ISO_6937-2-25 [RFC1345,KXS2]
+MIBenum: 93
+Source: ECMA registry
+Alias: iso-ir-152
+Alias: csISO6937Add
+
+Name: GOST_19768-74 [RFC1345,KXS2]
+MIBenum: 94
+Source: ECMA registry
+Alias: ST_SEV_358-88
+Alias: iso-ir-153
+Alias: csISO153GOST1976874
+
+Name: ISO_8859-supp [RFC1345,KXS2]
+MIBenum: 95
+Source: ECMA registry
+Alias: iso-ir-154
+Alias: latin1-2-5
+Alias: csISO8859Supp
+
+Name: ISO_10367-box [RFC1345,KXS2]
+MIBenum: 96
+Source: ECMA registry
+Alias: iso-ir-155
+Alias: csISO10367Box
+
+Name: ISO-8859-10 (preferred MIME name) [RFC1345,KXS2]
+MIBenum: 13
+Source: ECMA registry
+Alias: iso-ir-157
+Alias: l6
+Alias: ISO_8859-10:1992
+Alias: csISOLatin6
+Alias: latin6
+
+Name: latin-lap [RFC1345,KXS2]
+MIBenum: 97
+Source: ECMA registry
+Alias: lap
+Alias: iso-ir-158
+Alias: csISO158Lap
+
+Name: JIS_X0212-1990 [RFC1345,KXS2]
+MIBenum: 98
+Source: ECMA registry
+Alias: x0212
+Alias: iso-ir-159
+Alias: csISO159JISX02121990
+
+Name: DS_2089 [RFC1345,KXS2]
+MIBenum: 99
+Source: Danish Standard, DS 2089, February 1974
+Alias: DS2089
+Alias: ISO646-DK
+Alias: dk
+Alias: csISO646Danish
+
+Name: us-dk [RFC1345,KXS2]
+MIBenum: 100
+Alias: csUSDK
+
+Name: dk-us [RFC1345,KXS2]
+MIBenum: 101
+Alias: csDKUS
+
+Name: JIS_X0201 [RFC1345,KXS2]
+MIBenum: 15
+Source: JIS X 0201-1976. One byte only, this is equivalent to
+ JIS/Roman (similar to ASCII) plus eight-bit half-width
+ Katakana
+Alias: X0201
+Alias: csHalfWidthKatakana
+
+Name: KSC5636 [RFC1345,KXS2]
+MIBenum: 102
+Alias: ISO646-KR
+Alias: csKSC5636
+
+Name: ISO-10646-UCS-2
+MIBenum: 1000
+Source: the 2-octet Basic Multilingual Plane, aka Unicode
+ this needs to specify network byte order: the standard
+ does not specify (it is a 16-bit integer space)
+Alias: csUnicode
+
+Name: ISO-10646-UCS-4
+MIBenum: 1001
+Source: the full code space. (same comment about byte order,
+ these are 31-bit numbers.
+Alias: csUCS4
+
+Name: DEC-MCS [RFC1345,KXS2]
+MIBenum: 2008
+Source: VAX/VMS User's Manual,
+ Order Number: AI-Y517A-TE, April 1986.
+Alias: dec
+Alias: csDECMCS
+
+Name: hp-roman8 [HP-PCL5,RFC1345,KXS2]
+MIBenum: 2004
+Source: LaserJet IIP Printer User's Manual,
+ HP part no 33471-90901, Hewlet-Packard, June 1989.
+Alias: roman8
+Alias: r8
+Alias: csHPRoman8
+
+Name: macintosh [RFC1345,KXS2]
+MIBenum: 2027
+Source: The Unicode Standard ver1.0, ISBN 0-201-56788-1, Oct 1991
+Alias: mac
+Alias: csMacintosh
+
+Name: IBM037 [RFC1345,KXS2]
+MIBenum: 2028
+Source: IBM NLS RM Vol2 SE09-8002-01, March 1990
+Alias: cp037
+Alias: ebcdic-cp-us
+Alias: ebcdic-cp-ca
+Alias: ebcdic-cp-wt
+Alias: ebcdic-cp-nl
+Alias: csIBM037
+
+Name: IBM038 [RFC1345,KXS2]
+MIBenum: 2029
+Source: IBM 3174 Character Set Ref, GA27-3831-02, March 1990
+Alias: EBCDIC-INT
+Alias: cp038
+Alias: csIBM038
+
+Name: IBM273 [RFC1345,KXS2]
+MIBenum: 2030
+Source: IBM NLS RM Vol2 SE09-8002-01, March 1990
+Alias: CP273
+Alias: csIBM273
+
+Name: IBM274 [RFC1345,KXS2]
+MIBenum: 2031
+Source: IBM 3174 Character Set Ref, GA27-3831-02, March 1990
+Alias: EBCDIC-BE
+Alias: CP274
+Alias: csIBM274
+
+Name: IBM275 [RFC1345,KXS2]
+MIBenum: 2032
+Source: IBM NLS RM Vol2 SE09-8002-01, March 1990
+Alias: EBCDIC-BR
+Alias: cp275
+Alias: csIBM275
+
+Name: IBM277 [RFC1345,KXS2]
+MIBenum: 2033
+Source: IBM NLS RM Vol2 SE09-8002-01, March 1990
+Alias: EBCDIC-CP-DK
+Alias: EBCDIC-CP-NO
+Alias: csIBM277
+
+Name: IBM278 [RFC1345,KXS2]
+MIBenum: 2034
+Source: IBM NLS RM Vol2 SE09-8002-01, March 1990
+Alias: CP278
+Alias: ebcdic-cp-fi
+Alias: ebcdic-cp-se
+Alias: csIBM278
+
+Name: IBM280 [RFC1345,KXS2]
+MIBenum: 2035
+Source: IBM NLS RM Vol2 SE09-8002-01, March 1990
+Alias: CP280
+Alias: ebcdic-cp-it
+Alias: csIBM280
+
+Name: IBM281 [RFC1345,KXS2]
+MIBenum: 2036
+Source: IBM 3174 Character Set Ref, GA27-3831-02, March 1990
+Alias: EBCDIC-JP-E
+Alias: cp281
+Alias: csIBM281
+
+Name: IBM284 [RFC1345,KXS2]
+MIBenum: 2037
+Source: IBM NLS RM Vol2 SE09-8002-01, March 1990
+Alias: CP284
+Alias: ebcdic-cp-es
+Alias: csIBM284
+
+Name: IBM285 [RFC1345,KXS2]
+MIBenum: 2038
+Source: IBM NLS RM Vol2 SE09-8002-01, March 1990
+Alias: CP285
+Alias: ebcdic-cp-gb
+Alias: csIBM285
+
+Name: IBM290 [RFC1345,KXS2]
+MIBenum: 2039
+Source: IBM 3174 Character Set Ref, GA27-3831-02, March 1990
+Alias: cp290
+Alias: EBCDIC-JP-kana
+Alias: csIBM290
+
+Name: IBM297 [RFC1345,KXS2]
+MIBenum: 2040
+Source: IBM NLS RM Vol2 SE09-8002-01, March 1990
+Alias: cp297
+Alias: ebcdic-cp-fr
+Alias: csIBM297
+
+Name: IBM420 [RFC1345,KXS2]
+MIBenum: 2041
+Source: IBM NLS RM Vol2 SE09-8002-01, March 1990,
+ IBM NLS RM p 11-11
+Alias: cp420
+Alias: ebcdic-cp-ar1
+Alias: csIBM420
+
+Name: IBM423 [RFC1345,KXS2]
+MIBenum: 2042
+Source: IBM NLS RM Vol2 SE09-8002-01, March 1990
+Alias: cp423
+Alias: ebcdic-cp-gr
+Alias: csIBM423
+
+Name: IBM424 [RFC1345,KXS2]
+MIBenum: 2043
+Source: IBM NLS RM Vol2 SE09-8002-01, March 1990
+Alias: cp424
+Alias: ebcdic-cp-he
+Alias: csIBM424
+
+Name: IBM437 [RFC1345,KXS2]
+MIBenum: 2011
+Source: IBM NLS RM Vol2 SE09-8002-01, March 1990
+Alias: cp437
+Alias: 437
+Alias: csPC8CodePage437
+
+Name: IBM500 [RFC1345,KXS2]
+MIBenum: 2044
+Source: IBM NLS RM Vol2 SE09-8002-01, March 1990
+Alias: CP500
+Alias: ebcdic-cp-be
+Alias: ebcdic-cp-ch
+Alias: csIBM500
+
+Name: IBM775 [HP-PCL5]
+MIBenum: 2087
+Source: HP PCL 5 Comparison Guide (P/N 5021-0329) pp B-13, 1996
+Alias: cp775
+Alias: csPC775Baltic
+
+Name: IBM850 [RFC1345,KXS2]
+MIBenum: 2009
+Source: IBM NLS RM Vol2 SE09-8002-01, March 1990
+Alias: cp850
+Alias: 850
+Alias: csPC850Multilingual
+
+Name: IBM851 [RFC1345,KXS2]
+MIBenum: 2045
+Source: IBM NLS RM Vol2 SE09-8002-01, March 1990
+Alias: cp851
+Alias: 851
+Alias: csIBM851
+
+Name: IBM852 [RFC1345,KXS2]
+MIBenum: 2010
+Source: IBM NLS RM Vol2 SE09-8002-01, March 1990
+Alias: cp852
+Alias: 852
+Alias: csPCp852
+
+Name: IBM855 [RFC1345,KXS2]
+MIBenum: 2046
+Source: IBM NLS RM Vol2 SE09-8002-01, March 1990
+Alias: cp855
+Alias: 855
+Alias: csIBM855
+
+Name: IBM857 [RFC1345,KXS2]
+MIBenum: 2047
+Source: IBM NLS RM Vol2 SE09-8002-01, March 1990
+Alias: cp857
+Alias: 857
+Alias: csIBM857
+
+Name: IBM860 [RFC1345,KXS2]
+MIBenum: 2048
+Source: IBM NLS RM Vol2 SE09-8002-01, March 1990
+Alias: cp860
+Alias: 860
+Alias: csIBM860
+
+Name: IBM861 [RFC1345,KXS2]
+MIBenum: 2049
+Source: IBM NLS RM Vol2 SE09-8002-01, March 1990
+Alias: cp861
+Alias: 861
+Alias: cp-is
+Alias: csIBM861
+
+Name: IBM862 [RFC1345,KXS2]
+MIBenum: 2013
+Source: IBM NLS RM Vol2 SE09-8002-01, March 1990
+Alias: cp862
+Alias: 862
+Alias: csPC862LatinHebrew
+
+Name: IBM863 [RFC1345,KXS2]
+MIBenum: 2050
+Source: IBM Keyboard layouts and code pages, PN 07G4586 June 1991
+Alias: cp863
+Alias: 863
+Alias: csIBM863
+
+Name: IBM864 [RFC1345,KXS2]
+MIBenum: 2051
+Source: IBM Keyboard layouts and code pages, PN 07G4586 June 1991
+Alias: cp864
+Alias: csIBM864
+
+Name: IBM865 [RFC1345,KXS2]
+MIBenum: 2052
+Source: IBM DOS 3.3 Ref (Abridged), 94X9575 (Feb 1987)
+Alias: cp865
+Alias: 865
+Alias: csIBM865
+
+Name: IBM866 [Pond]
+MIBenum: 2086
+Source: IBM NLDG Volume 2 (SE09-8002-03) August 1994
+Alias: cp866
+Alias: 866
+Alias: csIBM866
+
+Name: IBM868 [RFC1345,KXS2]
+MIBenum: 2053
+Source: IBM NLS RM Vol2 SE09-8002-01, March 1990
+Alias: CP868
+Alias: cp-ar
+Alias: csIBM868
+
+Name: IBM869 [RFC1345,KXS2]
+MIBenum: 2054
+Source: IBM Keyboard layouts and code pages, PN 07G4586 June 1991
+Alias: cp869
+Alias: 869
+Alias: cp-gr
+Alias: csIBM869
+
+Name: IBM870 [RFC1345,KXS2]
+MIBenum: 2055
+Source: IBM NLS RM Vol2 SE09-8002-01, March 1990
+Alias: CP870
+Alias: ebcdic-cp-roece
+Alias: ebcdic-cp-yu
+Alias: csIBM870
+
+Name: IBM871 [RFC1345,KXS2]
+MIBenum: 2056
+Source: IBM NLS RM Vol2 SE09-8002-01, March 1990
+Alias: CP871
+Alias: ebcdic-cp-is
+Alias: csIBM871
+
+Name: IBM880 [RFC1345,KXS2]
+MIBenum: 2057
+Source: IBM NLS RM Vol2 SE09-8002-01, March 1990
+Alias: cp880
+Alias: EBCDIC-Cyrillic
+Alias: csIBM880
+
+Name: IBM891 [RFC1345,KXS2]
+MIBenum: 2058
+Source: IBM NLS RM Vol2 SE09-8002-01, March 1990
+Alias: cp891
+Alias: csIBM891
+
+Name: IBM903 [RFC1345,KXS2]
+MIBenum: 2059
+Source: IBM NLS RM Vol2 SE09-8002-01, March 1990
+Alias: cp903
+Alias: csIBM903
+
+Name: IBM904 [RFC1345,KXS2]
+MIBenum: 2060
+Source: IBM NLS RM Vol2 SE09-8002-01, March 1990
+Alias: cp904
+Alias: 904
+Alias: csIBBM904
+
+Name: IBM905 [RFC1345,KXS2]
+MIBenum: 2061
+Source: IBM 3174 Character Set Ref, GA27-3831-02, March 1990
+Alias: CP905
+Alias: ebcdic-cp-tr
+Alias: csIBM905
+
+Name: IBM918 [RFC1345,KXS2]
+MIBenum: 2062
+Source: IBM NLS RM Vol2 SE09-8002-01, March 1990
+Alias: CP918
+Alias: ebcdic-cp-ar2
+Alias: csIBM918
+
+Name: IBM1026 [RFC1345,KXS2]
+MIBenum: 2063
+Source: IBM NLS RM Vol2 SE09-8002-01, March 1990
+Alias: CP1026
+Alias: csIBM1026
+
+Name: EBCDIC-AT-DE [RFC1345,KXS2]
+MIBenum: 2064
+Source: IBM 3270 Char Set Ref Ch 10, GA27-2837-9, April 1987
+Alias: csIBMEBCDICATDE
+
+Name: EBCDIC-AT-DE-A [RFC1345,KXS2]
+MIBenum: 2065
+Source: IBM 3270 Char Set Ref Ch 10, GA27-2837-9, April 1987
+Alias: csEBCDICATDEA
+
+Name: EBCDIC-CA-FR [RFC1345,KXS2]
+MIBenum: 2066
+Source: IBM 3270 Char Set Ref Ch 10, GA27-2837-9, April 1987
+Alias: csEBCDICCAFR
+
+Name: EBCDIC-DK-NO [RFC1345,KXS2]
+MIBenum: 2067
+Source: IBM 3270 Char Set Ref Ch 10, GA27-2837-9, April 1987
+Alias: csEBCDICDKNO
+
+Name: EBCDIC-DK-NO-A [RFC1345,KXS2]
+MIBenum: 2068
+Source: IBM 3270 Char Set Ref Ch 10, GA27-2837-9, April 1987
+Alias: csEBCDICDKNOA
+
+Name: EBCDIC-FI-SE [RFC1345,KXS2]
+MIBenum: 2069
+Source: IBM 3270 Char Set Ref Ch 10, GA27-2837-9, April 1987
+Alias: csEBCDICFISE
+
+Name: EBCDIC-FI-SE-A [RFC1345,KXS2]
+MIBenum: 2070
+Source: IBM 3270 Char Set Ref Ch 10, GA27-2837-9, April 1987
+Alias: csEBCDICFISEA
+
+Name: EBCDIC-FR [RFC1345,KXS2]
+MIBenum: 2071
+Source: IBM 3270 Char Set Ref Ch 10, GA27-2837-9, April 1987
+Alias: csEBCDICFR
+
+Name: EBCDIC-IT [RFC1345,KXS2]
+MIBenum: 2072
+Source: IBM 3270 Char Set Ref Ch 10, GA27-2837-9, April 1987
+Alias: csEBCDICIT
+
+Name: EBCDIC-PT [RFC1345,KXS2]
+MIBenum: 2073
+Source: IBM 3270 Char Set Ref Ch 10, GA27-2837-9, April 1987
+Alias: csEBCDICPT
+
+Name: EBCDIC-ES [RFC1345,KXS2]
+MIBenum: 2074
+Source: IBM 3270 Char Set Ref Ch 10, GA27-2837-9, April 1987
+Alias: csEBCDICES
+
+Name: EBCDIC-ES-A [RFC1345,KXS2]
+MIBenum: 2075
+Source: IBM 3270 Char Set Ref Ch 10, GA27-2837-9, April 1987
+Alias: csEBCDICESA
+
+Name: EBCDIC-ES-S [RFC1345,KXS2]
+MIBenum: 2076
+Source: IBM 3270 Char Set Ref Ch 10, GA27-2837-9, April 1987
+Alias: csEBCDICESS
+
+Name: EBCDIC-UK [RFC1345,KXS2]
+MIBenum: 2077
+Source: IBM 3270 Char Set Ref Ch 10, GA27-2837-9, April 1987
+Alias: csEBCDICUK
+
+Name: EBCDIC-US [RFC1345,KXS2]
+MIBenum: 2078
+Source: IBM 3270 Char Set Ref Ch 10, GA27-2837-9, April 1987
+Alias: csEBCDICUS
+
+Name: UNKNOWN-8BIT [RFC1428]
+MIBenum: 2079
+Alias: csUnknown8BiT
+
+Name: MNEMONIC [RFC1345,KXS2]
+MIBenum: 2080
+Source: RFC 1345, also known as "mnemonic+ascii+38"
+Alias: csMnemonic
+
+Name: MNEM [RFC1345,KXS2]
+MIBenum: 2081
+Source: RFC 1345, also known as "mnemonic+ascii+8200"
+Alias: csMnem
+
+Name: VISCII [RFC1456]
+MIBenum: 2082
+Source: RFC 1456
+Alias: csVISCII
+
+Name: VIQR [RFC1456]
+MIBenum: 2083
+Source: RFC 1456
+Alias: csVIQR
+
+Name: KOI8-R (preferred MIME name) [RFC1489]
+MIBenum: 2084
+Source: RFC 1489, based on GOST-19768-74, ISO-6937/8,
+ INIS-Cyrillic, ISO-5427.
+Alias: csKOI8R
+
+Name: KOI8-U [RFC2319]
+MIBenum: 2088
+Source: RFC 2319
+
+Name: IBM00858
+MIBenum: 2089
+Source: IBM See (http://www.iana.org/assignments/charset-reg/IBM00858) [Mahdi]
+Alias: CCSID00858
+Alias: CP00858
+Alias: PC-Multilingual-850+euro
+
+Name: IBM00924
+MIBenum: 2090
+Source: IBM See (http://www.iana.org/assignments/charset-reg/IBM00924) [Mahdi]
+Alias: CCSID00924
+Alias: CP00924
+Alias: ebcdic-Latin9--euro
+
+Name: IBM01140
+MIBenum: 2091
+Source: IBM See (http://www.iana.org/assignments/charset-reg/IBM01140) [Mahdi]
+Alias: CCSID01140
+Alias: CP01140
+Alias: ebcdic-us-37+euro
+
+Name: IBM01141
+MIBenum: 2092
+Source: IBM See (http://www.iana.org/assignments/charset-reg/IBM01141) [Mahdi]
+Alias: CCSID01141
+Alias: CP01141
+Alias: ebcdic-de-273+euro
+
+Name: IBM01142
+MIBenum: 2093
+Source: IBM See (http://www.iana.org/assignments/charset-reg/IBM01142) [Mahdi]
+Alias: CCSID01142
+Alias: CP01142
+Alias: ebcdic-dk-277+euro
+Alias: ebcdic-no-277+euro
+
+Name: IBM01143
+MIBenum: 2094
+Source: IBM See (http://www.iana.org/assignments/charset-reg/IBM01143) [Mahdi]
+Alias: CCSID01143
+Alias: CP01143
+Alias: ebcdic-fi-278+euro
+Alias: ebcdic-se-278+euro
+
+Name: IBM01144
+MIBenum: 2095
+Source: IBM See (http://www.iana.org/assignments/charset-reg/IBM01144) [Mahdi]
+Alias: CCSID01144
+Alias: CP01144
+Alias: ebcdic-it-280+euro
+
+Name: IBM01145
+MIBenum: 2096
+Source: IBM See (http://www.iana.org/assignments/charset-reg/IBM01145) [Mahdi]
+Alias: CCSID01145
+Alias: CP01145
+Alias: ebcdic-es-284+euro
+
+Name: IBM01146
+MIBenum: 2097
+Source: IBM See (http://www.iana.org/assignments/charset-reg/IBM01146) [Mahdi]
+Alias: CCSID01146
+Alias: CP01146
+Alias: ebcdic-gb-285+euro
+
+Name: IBM01147
+MIBenum: 2098
+Source: IBM See (http://www.iana.org/assignments/charset-reg/IBM01147) [Mahdi]
+Alias: CCSID01147
+Alias: CP01147
+Alias: ebcdic-fr-297+euro
+
+Name: IBM01148
+MIBenum: 2099
+Source: IBM See (http://www.iana.org/assignments/charset-reg/IBM01148) [Mahdi]
+Alias: CCSID01148
+Alias: CP01148
+Alias: ebcdic-international-500+euro
+
+Name: IBM01149
+MIBenum: 2100
+Source: IBM See (http://www.iana.org/assignments/charset-reg/IBM01149) [Mahdi]
+Alias: CCSID01149
+Alias: CP01149
+Alias: ebcdic-is-871+euro
+
+Name: Big5-HKSCS [Yick]
+MIBenum: 2101
+Source: See (http://www.iana.org/assignments/charset-reg/Big5-HKSCS)
+Alias: None
+
+Name: IBM1047 [Robrigado]
+MIBenum: 2102
+Source: IBM1047 (EBCDIC Latin 1/Open Systems)
+http://www-1.ibm.com/servers/eserver/iseries/software/globalization/pdf/cp01047z.pdf
+Alias: IBM-1047
+
+Name: PTCP154 [Uskov]
+MIBenum: 2103
+Source: See (http://www.iana.org/assignments/charset-reg/PTCP154)
+Alias: csPTCP154
+Alias: PT154
+Alias: CP154
+Alias: Cyrillic-Asian
+
+Name: Amiga-1251
+MIBenum: 2104
+Source: See (http://www.amiga.ultranet.ru/Amiga-1251.html)
+Alias: Ami1251
+Alias: Amiga1251
+Alias: Ami-1251
+(Aliases are provided for historical reasons and should not be used)
+ [Malyshev]
+
+Name: KOI7-switched
+MIBenum: 2105
+Source: See <http://www.iana.org/assignments/charset-reg/KOI7-switched>
+Aliases: None
+
+Name: UNICODE-1-1 [RFC1641]
+MIBenum: 1010
+Source: RFC 1641
+Alias: csUnicode11
+
+Name: SCSU
+MIBenum: 1011
+Source: SCSU See (http://www.iana.org/assignments/charset-reg/SCSU) [Scherer]
+Alias: None
+
+Name: UTF-7 [RFC2152]
+MIBenum: 1012
+Source: RFC 2152
+Alias: None
+
+Name: UTF-16BE [RFC2781]
+MIBenum: 1013
+Source: RFC 2781
+Alias: None
+
+Name: UTF-16LE [RFC2781]
+MIBenum: 1014
+Source: RFC 2781
+Alias: None
+
+Name: UTF-16 [RFC2781]
+MIBenum: 1015
+Source: RFC 2781
+Alias: None
+
+Name: CESU-8 [Phipps]
+MIBenum: 1016
+Source: <http://www.unicode.org/unicode/reports/tr26>
+Alias: csCESU-8
+
+Name: UTF-32 [Davis]
+MIBenum: 1017
+Source: <http://www.unicode.org/unicode/reports/tr19/>
+Alias: None
+
+Name: UTF-32BE [Davis]
+MIBenum: 1018
+Source: <http://www.unicode.org/unicode/reports/tr19/>
+Alias: None
+
+Name: UTF-32LE [Davis]
+MIBenum: 1019
+Source: <http://www.unicode.org/unicode/reports/tr19/>
+Alias: None
+
+Name: BOCU-1 [Scherer]
+MIBenum: 1020
+Source: http://www.unicode.org/notes/tn6/
+Alias: csBOCU-1
+
+Name: UNICODE-1-1-UTF-7 [RFC1642]
+MIBenum: 103
+Source: RFC 1642
+Alias: csUnicode11UTF7
+
+Name: UTF-8 [RFC3629]
+MIBenum: 106
+Source: RFC 3629
+Alias: None
+
+Name: ISO-8859-13
+MIBenum: 109
+Source: ISO See (http://www.iana.org/assignments/charset-reg/iso-8859-13)[Tumasonis]
+Alias: None
+
+Name: ISO-8859-14
+MIBenum: 110
+Source: ISO See (http://www.iana.org/assignments/charset-reg/iso-8859-14) [Simonsen]
+Alias: iso-ir-199
+Alias: ISO_8859-14:1998
+Alias: ISO_8859-14
+Alias: latin8
+Alias: iso-celtic
+Alias: l8
+
+Name: ISO-8859-15
+MIBenum: 111
+Source: ISO
+ Please see: <http://www.iana.org/assignments/charset-reg/ISO-8859-15>
+Alias: ISO_8859-15
+Alias: Latin-9
+
+Name: ISO-8859-16
+MIBenum: 112
+Source: ISO
+Alias: iso-ir-226
+Alias: ISO_8859-16:2001
+Alias: ISO_8859-16
+Alias: latin10
+Alias: l10
+
+Name: GBK
+MIBenum: 113
+Source: Chinese IT Standardization Technical Committee
+ Please see: <http://www.iana.org/assignments/charset-reg/GBK>
+Alias: CP936
+Alias: MS936
+Alias: windows-936
+
+Name: GB18030
+MIBenum: 114
+Source: Chinese IT Standardization Technical Committee
+ Please see: <http://www.iana.org/assignments/charset-reg/GB18030>
+Alias: None
+
+Name: OSD_EBCDIC_DF04_15
+MIBenum: 115
+Source: Fujitsu-Siemens standard mainframe EBCDIC encoding
+ Please see: <http://www.iana.org/assignments/charset-reg/OSD-EBCDIC-DF04-15>
+Alias: None
+
+Name: OSD_EBCDIC_DF03_IRV
+MIBenum: 116
+Source: Fujitsu-Siemens standard mainframe EBCDIC encoding
+ Please see: <http://www.iana.org/assignments/charset-reg/OSD-EBCDIC-DF03-IRV>
+Alias: None
+
+Name: OSD_EBCDIC_DF04_1
+MIBenum: 117
+Source: Fujitsu-Siemens standard mainframe EBCDIC encoding
+ Please see: <http://www.iana.org/assignments/charset-reg/OSD-EBCDIC-DF04-1>
+Alias: None
+
+Name: JIS_Encoding
+MIBenum: 16
+Source: JIS X 0202-1991. Uses ISO 2022 escape sequences to
+ shift code sets as documented in JIS X 0202-1991.
+Alias: csJISEncoding
+
+Name: Shift_JIS (preferred MIME name)
+MIBenum: 17
+Source: This charset is an extension of csHalfWidthKatakana by
+ adding graphic characters in JIS X 0208. The CCS's are
+ JIS X0201:1997 and JIS X0208:1997. The
+ complete definition is shown in Appendix 1 of JIS
+ X0208:1997.
+ This charset can be used for the top-level media type "text".
+Alias: MS_Kanji
+Alias: csShiftJIS
+
+Name: Extended_UNIX_Code_Packed_Format_for_Japanese
+MIBenum: 18
+Source: Standardized by OSF, UNIX International, and UNIX Systems
+ Laboratories Pacific. Uses ISO 2022 rules to select
+ code set 0: US-ASCII (a single 7-bit byte set)
+ code set 1: JIS X0208-1990 (a double 8-bit byte set)
+ restricted to A0-FF in both bytes
+ code set 2: Half Width Katakana (a single 7-bit byte set)
+ requiring SS2 as the character prefix
+ code set 3: JIS X0212-1990 (a double 7-bit byte set)
+ restricted to A0-FF in both bytes
+ requiring SS3 as the character prefix
+Alias: csEUCPkdFmtJapanese
+Alias: EUC-JP (preferred MIME name)
+
+Name: Extended_UNIX_Code_Fixed_Width_for_Japanese
+MIBenum: 19
+Source: Used in Japan. Each character is 2 octets.
+ code set 0: US-ASCII (a single 7-bit byte set)
+ 1st byte = 00
+ 2nd byte = 20-7E
+ code set 1: JIS X0208-1990 (a double 7-bit byte set)
+ restricted to A0-FF in both bytes
+ code set 2: Half Width Katakana (a single 7-bit byte set)
+ 1st byte = 00
+ 2nd byte = A0-FF
+ code set 3: JIS X0212-1990 (a double 7-bit byte set)
+ restricted to A0-FF in
+ the first byte
+ and 21-7E in the second byte
+Alias: csEUCFixWidJapanese
+
+Name: ISO-10646-UCS-Basic
+MIBenum: 1002
+Source: ASCII subset of Unicode. Basic Latin = collection 1
+ See ISO 10646, Appendix A
+Alias: csUnicodeASCII
+
+Name: ISO-10646-Unicode-Latin1
+MIBenum: 1003
+Source: ISO Latin-1 subset of Unicode. Basic Latin and Latin-1
+ Supplement = collections 1 and 2. See ISO 10646,
+ Appendix A. See RFC 1815.
+Alias: csUnicodeLatin1
+Alias: ISO-10646
+
+Name: ISO-10646-J-1
+Source: ISO 10646 Japanese, see RFC 1815.
+
+Name: ISO-Unicode-IBM-1261
+MIBenum: 1005
+Source: IBM Latin-2, -3, -5, Extended Presentation Set, GCSGID: 1261
+Alias: csUnicodeIBM1261
+
+Name: ISO-Unicode-IBM-1268
+MIBenum: 1006
+Source: IBM Latin-4 Extended Presentation Set, GCSGID: 1268
+Alias: csUnicodeIBM1268
+
+Name: ISO-Unicode-IBM-1276
+MIBenum: 1007
+Source: IBM Cyrillic Greek Extended Presentation Set, GCSGID: 1276
+Alias: csUnicodeIBM1276
+
+Name: ISO-Unicode-IBM-1264
+MIBenum: 1008
+Source: IBM Arabic Presentation Set, GCSGID: 1264
+Alias: csUnicodeIBM1264
+
+Name: ISO-Unicode-IBM-1265
+MIBenum: 1009
+Source: IBM Hebrew Presentation Set, GCSGID: 1265
+Alias: csUnicodeIBM1265
+
+Name: ISO-8859-1-Windows-3.0-Latin-1 [HP-PCL5]
+MIBenum: 2000
+Source: Extended ISO 8859-1 Latin-1 for Windows 3.0.
+ PCL Symbol Set id: 9U
+Alias: csWindows30Latin1
+
+Name: ISO-8859-1-Windows-3.1-Latin-1 [HP-PCL5]
+MIBenum: 2001
+Source: Extended ISO 8859-1 Latin-1 for Windows 3.1.
+ PCL Symbol Set id: 19U
+Alias: csWindows31Latin1
+
+Name: ISO-8859-2-Windows-Latin-2 [HP-PCL5]
+MIBenum: 2002
+Source: Extended ISO 8859-2. Latin-2 for Windows 3.1.
+ PCL Symbol Set id: 9E
+Alias: csWindows31Latin2
+
+Name: ISO-8859-9-Windows-Latin-5 [HP-PCL5]
+MIBenum: 2003
+Source: Extended ISO 8859-9. Latin-5 for Windows 3.1
+ PCL Symbol Set id: 5T
+Alias: csWindows31Latin5
+
+Name: Adobe-Standard-Encoding [Adobe]
+MIBenum: 2005
+Source: PostScript Language Reference Manual
+ PCL Symbol Set id: 10J
+Alias: csAdobeStandardEncoding
+
+Name: Ventura-US [HP-PCL5]
+MIBenum: 2006
+Source: Ventura US. ASCII plus characters typically used in
+ publishing, like pilcrow, copyright, registered, trade mark,
+ section, dagger, and double dagger in the range A0 (hex)
+ to FF (hex).
+ PCL Symbol Set id: 14J
+Alias: csVenturaUS
+
+Name: Ventura-International [HP-PCL5]
+MIBenum: 2007
+Source: Ventura International. ASCII plus coded characters similar
+ to Roman8.
+ PCL Symbol Set id: 13J
+Alias: csVenturaInternational
+
+Name: PC8-Danish-Norwegian [HP-PCL5]
+MIBenum: 2012
+Source: PC Danish Norwegian
+ 8-bit PC set for Danish Norwegian
+ PCL Symbol Set id: 11U
+Alias: csPC8DanishNorwegian
+
+Name: PC8-Turkish [HP-PCL5]
+MIBenum: 2014
+Source: PC Latin Turkish. PCL Symbol Set id: 9T
+Alias: csPC8Turkish
+
+Name: IBM-Symbols [IBM-CIDT]
+MIBenum: 2015
+Source: Presentation Set, CPGID: 259
+Alias: csIBMSymbols
+
+Name: IBM-Thai [IBM-CIDT]
+MIBenum: 2016
+Source: Presentation Set, CPGID: 838
+Alias: csIBMThai
+
+Name: HP-Legal [HP-PCL5]
+MIBenum: 2017
+Source: PCL 5 Comparison Guide, Hewlett-Packard,
+ HP part number 5961-0510, October 1992
+ PCL Symbol Set id: 1U
+Alias: csHPLegal
+
+Name: HP-Pi-font [HP-PCL5]
+MIBenum: 2018
+Source: PCL 5 Comparison Guide, Hewlett-Packard,
+ HP part number 5961-0510, October 1992
+ PCL Symbol Set id: 15U
+Alias: csHPPiFont
+
+Name: HP-Math8 [HP-PCL5]
+MIBenum: 2019
+Source: PCL 5 Comparison Guide, Hewlett-Packard,
+ HP part number 5961-0510, October 1992
+ PCL Symbol Set id: 8M
+Alias: csHPMath8
+
+Name: Adobe-Symbol-Encoding [Adobe]
+MIBenum: 2020
+Source: PostScript Language Reference Manual
+ PCL Symbol Set id: 5M
+Alias: csHPPSMath
+
+Name: HP-DeskTop [HP-PCL5]
+MIBenum: 2021
+Source: PCL 5 Comparison Guide, Hewlett-Packard,
+ HP part number 5961-0510, October 1992
+ PCL Symbol Set id: 7J
+Alias: csHPDesktop
+
+Name: Ventura-Math [HP-PCL5]
+MIBenum: 2022
+Source: PCL 5 Comparison Guide, Hewlett-Packard,
+ HP part number 5961-0510, October 1992
+ PCL Symbol Set id: 6M
+Alias: csVenturaMath
+
+Name: Microsoft-Publishing [HP-PCL5]
+MIBenum: 2023
+Source: PCL 5 Comparison Guide, Hewlett-Packard,
+ HP part number 5961-0510, October 1992
+ PCL Symbol Set id: 6J
+Alias: csMicrosoftPublishing
+
+Name: Windows-31J
+MIBenum: 2024
+Source: Windows Japanese. A further extension of Shift_JIS
+ to include NEC special characters (Row 13), NEC
+ selection of IBM extensions (Rows 89 to 92), and IBM
+ extensions (Rows 115 to 119). The CCS's are
+ JIS X0201:1997, JIS X0208:1997, and these extensions.
+ This charset can be used for the top-level media type "text",
+ but it is of limited or specialized use (see RFC2278).
+ PCL Symbol Set id: 19K
+Alias: csWindows31J
+
+Name: GB2312 (preferred MIME name)
+MIBenum: 2025
+Source: Chinese for People's Republic of China (PRC) mixed one byte,
+ two byte set:
+ 20-7E = one byte ASCII
+ A1-FE = two byte PRC Kanji
+ See GB 2312-80
+ PCL Symbol Set Id: 18C
+Alias: csGB2312
+
+Name: Big5 (preferred MIME name)
+MIBenum: 2026
+Source: Chinese for Taiwan Multi-byte set.
+ PCL Symbol Set Id: 18T
+Alias: csBig5
+
+Name: windows-1250
+MIBenum: 2250
+Source: Microsoft (http://www.iana.org/assignments/charset-reg/windows-1250) [Lazhintseva]
+Alias: None
+
+Name: windows-1251
+MIBenum: 2251
+Source: Microsoft (http://www.iana.org/assignments/charset-reg/windows-1251) [Lazhintseva]
+Alias: None
+
+Name: windows-1252
+MIBenum: 2252
+Source: Microsoft (http://www.iana.org/assignments/charset-reg/windows-1252) [Wendt]
+Alias: None
+
+Name: windows-1253
+MIBenum: 2253
+Source: Microsoft (http://www.iana.org/assignments/charset-reg/windows-1253) [Lazhintseva]
+Alias: None
+
+Name: windows-1254
+MIBenum: 2254
+Source: Microsoft (http://www.iana.org/assignments/charset-reg/windows-1254) [Lazhintseva]
+Alias: None
+
+Name: windows-1255
+MIBenum: 2255
+Source: Microsoft (http://www.iana.org/assignments/charset-reg/windows-1255) [Lazhintseva]
+Alias: None
+
+Name: windows-1256
+MIBenum: 2256
+Source: Microsoft (http://www.iana.org/assignments/charset-reg/windows-1256) [Lazhintseva]
+Alias: None
+
+Name: windows-1257
+MIBenum: 2257
+Source: Microsoft (http://www.iana.org/assignments/charset-reg/windows-1257) [Lazhintseva]
+Alias: None
+
+Name: windows-1258
+MIBenum: 2258
+Source: Microsoft (http://www.iana.org/assignments/charset-reg/windows-1258) [Lazhintseva]
+Alias: None
+
+Name: TIS-620
+MIBenum: 2259
+Source: Thai Industrial Standards Institute (TISI) [Tantsetthi]
+
+Name: HZ-GB-2312
+MIBenum: 2085
+Source: RFC 1842, RFC 1843 [RFC1842, RFC1843]
+
+
+REFERENCES
+----------
+
+[RFC1345] Simonsen, K., "Character Mnemonics & Character Sets",
+ RFC 1345, Rationel Almen Planlaegning, Rationel Almen
+ Planlaegning, June 1992.
+
+[RFC1428] Vaudreuil, G., "Transition of Internet Mail from
+ Just-Send-8 to 8bit-SMTP/MIME", RFC1428, CNRI, February
+ 1993.
+
+[RFC1456] Vietnamese Standardization Working Group, "Conventions for
+ Encoding the Vietnamese Language VISCII: VIetnamese
+ Standard Code for Information Interchange VIQR: VIetnamese
+ Quoted-Readable Specification Revision 1.1", RFC 1456, May
+ 1993.
+
+[RFC1468] Murai, J., Crispin, M., and E. van der Poel, "Japanese
+ Character Encoding for Internet Messages", RFC 1468,
+ Keio University, Panda Programming, June 1993.
+
+[RFC1489] Chernov, A., "Registration of a Cyrillic Character Set",
+ RFC1489, RELCOM Development Team, July 1993.
+
+[RFC1554] Ohta, M., and K. Handa, "ISO-2022-JP-2: Multilingual
+ Extension of ISO-2022-JP", RFC1554, Tokyo Institute of
+ Technology, ETL, December 1993.
+
+[RFC1556] Nussbacher, H., "Handling of Bi-directional Texts in MIME",
+ RFC1556, Israeli Inter-University, December 1993.
+
+[RFC1557] Choi, U., Chon, K., and H. Park, "Korean Character Encoding
+ for Internet Messages", KAIST, Solvit Chosun Media,
+ December 1993.
+
+[RFC1641] Goldsmith, D., and M. Davis, "Using Unicode with MIME",
+ RFC1641, Taligent, Inc., July 1994.
+
+[RFC1642] Goldsmith, D., and M. Davis, "UTF-7", RFC1642, Taligent,
+ Inc., July 1994.
+
+[RFC1815] Ohta, M., "Character Sets ISO-10646 and ISO-10646-J-1",
+ RFC 1815, Tokyo Institute of Technology, July 1995.
+
+
+[Adobe] Adobe Systems Incorporated, PostScript Language Reference
+ Manual, second edition, Addison-Wesley Publishing Company,
+ Inc., 1990.
+
+[ECMA Registry] ISO-IR: International Register of Escape Sequences
+ http://www.itscj.ipsj.or.jp/ISO-IE/ Note: The current
+ registration authority is IPSJ/ITSCJ, Japan.
+
+[HP-PCL5] Hewlett-Packard Company, "HP PCL 5 Comparison Guide",
+ (P/N 5021-0329) pp B-13, 1996.
+
+[IBM-CIDT] IBM Corporation, "ABOUT TYPE: IBM's Technical Reference
+ for Core Interchange Digitized Type", Publication number
+ S544-3708-01
+
+[RFC1842] Wei, Y., J. Li, and Y. Jiang, "ASCII Printable
+ Characters-Based Chinese Character Encoding for Internet
+ Messages", RFC 1842, Harvard University, Rice University,
+ University of Maryland, August 1995.
+
+[RFC1843] Lee, F., "HZ - A Data Format for Exchanging Files of
+ Arbitrarily Mixed Chinese and ASCII Characters", RFC 1843,
+ Stanford University, August 1995.
+
+[RFC2152] Goldsmith, D., M. Davis, "UTF-7: A Mail-Safe Transformation
+ Format of Unicode", RFC 2152, Apple Computer, Inc.,
+ Taligent Inc., May 1997.
+
+[RFC2279] Yergeau, F., "UTF-8, A Transformation Format of ISO 10646",
+ RFC 2279, Alis Technologies, January, 1998.
+
+[RFC2781] Hoffman, P., Yergeau, F., "UTF-16, an encoding of ISO 10646",
+ RFC 2781, February 2000.
+
+[RFC3629] Yergeau, F., "UTF-8, a transformation format of ISO 10646",
+ RFC3629, November 2003.
+
+PEOPLE
+------
+
+[KXS2] Keld Simonsen <Keld.Simonsen@dkuug.dk>
+
+[Choi] Woohyong Choi <whchoi@cosmos.kaist.ac.kr>
+
+[Davis] Mark Davis, <mark@unicode.org>, April 2002.
+
+[Lazhintseva] Katya Lazhintseva, <katyal@MICROSOFT.com>, May 1996.
+
+[Mahdi] Tamer Mahdi, <tamer@ca.ibm.com>, August 2000.
+
+[Malyshev] Michael Malyshev, <michael_malyshev@mail.ru>, January 2004
+
+[Murai] Jun Murai <jun@wide.ad.jp>
+
+[Nussbacher] Hank Nussbacher, <hank@vm.tau.ac.il>
+
+[Ohta] Masataka Ohta, <mohta@cc.titech.ac.jp>, July 1995.
+
+[Phipps] Toby Phipps, <tphipps@peoplesoft.com>, March 2002.
+
+[Pond] Rick Pond, <rickpond@vnet.ibm.com>, March 1997.
+
+[Robrigado] Reuel Robrigado, <reuelr@ca.ibm.com>, September 2002.
+
+[Scherer] Markus Scherer, <markus.scherer@jtcsv.com>, August 2000,
+ September 2002.
+
+[Simonsen] Keld Simonsen, <Keld.Simonsen@rap.dk>, August 2000.
+
+[Tantsetthi] Trin Tantsetthi, <trin@mozart.inet.co.th>, September 1998.
+
+[Tumasonis] Vladas Tumasonis, <vladas.tumasonis@maf.vu.lt>, August 2000.
+
+[Uskov] Alexander Uskov, <auskov@idc.kz>, September 2002.
+
+[Wendt] Chris Wendt, <christw@microsoft.com>, December 1999.
+
+[Yick] Nicky Yick, <cliac@itsd.gcn.gov.hk>, October 2000.
+
+[]
+
+
+
+
+
+
+
diff --git a/sys/src/cmd/abaco/cols.c b/sys/src/cmd/abaco/cols.c
new file mode 100755
index 000000000..5be689490
--- /dev/null
+++ b/sys/src/cmd/abaco/cols.c
@@ -0,0 +1,549 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <memdraw.h>
+#include <thread.h>
+#include <cursor.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include <frame.h>
+#include <plumb.h>
+#include <html.h>
+#include "dat.h"
+#include "fns.h"
+
+void
+colinit(Column *c, Rectangle r)
+{
+ Rectangle r1;
+ Text *t;
+
+ draw(screen, r, display->white, nil, ZP);
+ c->r = r;
+ c->w = nil;
+ c->nw = 0;
+ t = &c->tag;
+ t->w = nil;
+ t->col = c;
+ r1 = r;
+ r1.max.y = r1.min.y + font->height;
+ textinit(t, screen, r1, font, tagcols);
+ t->what = Columntag;
+ r1.min.y = r1.max.y;
+ r1.max.y += Border;
+ draw(screen, r1, display->black, nil, ZP);
+ textinsert(t, 0, L"New Cut Paste Snarf Sort Delcol ", 32);
+ textsetselect(t, t->rs.nr, t->rs.nr);
+ draw(screen, t->scrollr, colbutton, nil, colbutton->r.min);
+ c->safe = TRUE;
+}
+
+Window*
+coladd(Column *c, Window *w, Window *clone, int y)
+{
+ Rectangle r, r1;
+ Window *v;
+ int i, t;
+
+ v = nil;
+ r = c->r;
+ r.min.y = c->tag.r.max.y+Border;
+ if(y<r.min.y && c->nw>0){ /* steal half of last window by default */
+ v = c->w[c->nw-1];
+ y = v->page.all.min.y+Dy(v->page.all)/2;
+ }
+ /* look for window we'll land on */
+ for(i=0; i<c->nw; i++){
+ v = c->w[i];
+ if(y < v->r.max.y)
+ break;
+ }
+ if(c->nw > 0){
+ if(i < c->nw)
+ i++; /* new window will go after v */
+ /*
+ * if v's too small, grow it first.
+ */
+ if(!c->safe || Dy(v->page.all)<=3 ){
+ colgrow(c, v, 1);
+ y = v->page.all.min.y+Dy(v->page.all)/2;
+ }
+ r = v->r;
+ if(i == c->nw)
+ t = c->r.max.y;
+ else
+ t = c->w[i]->r.min.y-Border;
+ r.max.y = t;
+ r1 = r;
+ y = min(y, t-(Dy(v->r)-Dy(v->page.all)));
+ r1.max.y = min(y, v->page.all.min.y+Dy(v->page.all));
+ r1.min.y = winresize(v, r1, FALSE);
+ r1.max.y = r1.min.y+Border;
+ draw(screen, r1, display->black, nil, ZP);
+ r.min.y = r1.max.y;
+ }
+ if(w == nil){
+ w = emalloc(sizeof(Window));
+ w->col = c;
+ wininit(w, clone, r);
+ }else{
+ w->col = c;
+ winresize(w, r, FALSE);
+ }
+ w->tag.col = c;
+ w->tag.row = c->row;
+ w->url.col = c;
+ w->url.row = c->row;
+ w->page.col = c;
+ w->page.row = c->row;
+ w->status.col = c;
+ w->status.row = c->row;
+ c->w = realloc(c->w, (c->nw+1)*sizeof(Window*));
+ memmove(c->w+i+1, c->w+i, (c->nw-i)*sizeof(Window*));
+ c->nw++;
+ c->w[i] = w;
+ savemouse(w);
+ /* near but not on the button */
+ moveto(mousectl, addpt(w->tag.scrollr.max, Pt(3, 3)));
+ c->safe = TRUE;
+ return w;
+}
+
+void
+colclose(Column *c, Window *w, int dofree)
+{
+ Rectangle r;
+ int i;
+
+ /* w is locked */
+ if(!c->safe)
+ colgrow(c, w, 1);
+ for(i=0; i<c->nw; i++)
+ if(c->w[i] == w)
+ goto Found;
+ error("can't find window");
+ Found:
+ r = w->r;
+ w->tag.col = nil;
+ w->url.col = nil;
+ w->page.col = nil;
+ w->col = nil;
+ restoremouse(w);
+ if(dofree)
+ winclose(w);
+ memmove(c->w+i, c->w+i+1, (c->nw-i)*sizeof(Window*));
+ c->nw--;
+ c->w = realloc(c->w, c->nw*sizeof(Window*));
+ if(c->nw == 0){
+ draw(screen, r, display->white, nil, ZP);
+ return;
+ }
+ if(i == c->nw){ /* extend last window down */
+ w = c->w[i-1];
+ r.min.y = w->r.min.y;
+ r.max.y = c->r.max.y;
+ }else{ /* extend next window up */
+ w = c->w[i];
+ r.max.y = w->r.max.y;
+ }
+ if(c->safe)
+ winresize(w, r, FALSE);
+}
+
+void
+colcloseall(Column *c)
+{
+ int i;
+ Window *w;
+
+ if(c == activecol)
+ activecol = nil;
+ if(seltext && c==seltext->col)
+ seltext = nil;
+ textclose(&c->tag);
+ for(i=0; i<c->nw; i++){
+ w = c->w[i];
+ w->tag.col = nil;
+ w->url.col = nil;
+ w->page.col = nil;
+ w->col = nil;
+ winclose(w);
+ }
+ c->nw = 0;
+ free(c->w);
+ free(c);
+ clearmouse();
+}
+
+void
+colmousebut(Column *c)
+{
+ moveto(mousectl, divpt(addpt(c->tag.scrollr.min, c->tag.scrollr.max), 2));
+}
+
+void
+colresize(Column *c, Rectangle r)
+{
+ int i;
+ Rectangle r1, r2;
+ Window *w;
+
+ clearmouse();
+ r1 = r;
+ r1.max.y = r1.min.y + c->tag.font->height;
+ textresize(&c->tag, screen, r1);
+ draw(screen, c->tag.scrollr, colbutton, nil, colbutton->r.min);
+ r1.min.y = r1.max.y;
+ r1.max.y += Border;
+ draw(screen, r1, display->black, nil, ZP);
+ r1.max.y = r.max.y;
+ for(i=0; i<c->nw; i++){
+ w = c->w[i];
+ if(i == c->nw-1)
+ r1.max.y = r.max.y;
+ else
+ r1.max.y = r1.min.y+(Dy(w->r)+Border)*Dy(r)/Dy(c->r);
+ r2 = r1;
+ r2.max.y = r2.min.y+Border;
+ draw(screen, r2, display->black, nil, ZP);
+ r1.min.y = r2.max.y;
+ r1.min.y = winresize(w, r1, FALSE);
+ }
+ c->r = r;
+}
+
+static
+int
+colcmp(void *a, void *b)
+{
+ Rune *r1, *r2;
+ int i, nr1, nr2;
+
+ r1 = (*(Window**)a)->page.title.r;
+ nr1 = (*(Window**)a)->page.title.nr;
+ r2 = (*(Window**)b)->page.title.r;
+ nr2 = (*(Window**)b)->page.title.nr;
+ for(i=0; i<nr1 && i<nr2; i++){
+ if(*r1 != *r2)
+ return *r1-*r2;
+ r1++;
+ r2++;
+ }
+ return nr1-nr2;
+}
+
+void
+colsort(Column *c)
+{
+ int i, y;
+ Rectangle r, r1, *rp;
+ Window **wp, *w;
+
+ if(c->nw == 0)
+ return;
+ clearmouse();
+ rp = emalloc(c->nw*sizeof(Rectangle));
+ wp = emalloc(c->nw*sizeof(Window*));
+ memmove(wp, c->w, c->nw*sizeof(Window*));
+ qsort(wp, c->nw, sizeof(Window*), colcmp);
+ for(i=0; i<c->nw; i++)
+ rp[i] = wp[i]->r;
+ r = c->r;
+ y = c->tag.r.max.y;
+ for(i=0; i<c->nw; i++){
+ w = wp[i];
+ r.min.y = y;
+ if(i == c->nw-1)
+ r.max.y = c->r.max.y;
+ else
+ r.max.y = r.min.y+Dy(w->r)+Border;
+ r1 = r;
+ r1.max.y = r1.min.y+Border;
+ draw(screen, r1, display->black, nil, ZP);
+ r.min.y = r1.max.y;
+ y = winresize(w, r, FALSE);
+ }
+ free(rp);
+ free(c->w);
+ c->w = wp;
+}
+
+void
+colgrow(Column *c, Window *w, int but)
+{
+ Rectangle r, cr;
+ int i, j, k, l, y1, y2, *nl, *ny, tot, nnl, onl, dnl, h, wnl;
+ Window *v;
+
+ for(i=0; i<c->nw; i++)
+ if(c->w[i] == w)
+ goto Found;
+ error("can't find window");
+
+ Found:
+ cr = c->r;
+ if(but < 0){ /* make sure window fills its own space properly */
+ r = w->r;
+ if(i==c->nw-1 || c->safe==FALSE)
+ r.max.y = cr.max.y;
+ else
+ r.max.y = c->w[i+1]->r.min.y;
+ winresize(w, r, FALSE);
+ return;
+ }
+ cr.min.y = c->w[0]->r.min.y;
+ if(but == 3){ /* full size */
+ if(i != 0){
+ v = c->w[0];
+ c->w[0] = w;
+ c->w[i] = v;
+ }
+ for(j=1; j<c->nw; j++)
+ c->w[j]->page.all = ZR;
+ winresize(w, cr, FALSE);
+ c->safe = FALSE;
+ return;
+ }
+ /* store old #lines for each window */
+ wnl = Dy(w->page.all);
+ onl = wnl;
+ nl = emalloc(c->nw * sizeof(int));
+ ny = emalloc(c->nw * sizeof(int));
+ tot = 0;
+ for(j=0; j<c->nw; j++){
+ l = Dy(c->w[j]->page.all);
+ nl[j] = l;
+ tot += l;
+ }
+ /* approximate new #lines for this window */
+ if(but == 2){ /* as big as can be */
+ memset(nl, 0, c->nw * sizeof(int));
+ goto Pack;
+ }
+ nnl = min(onl + max(min(5, wnl), onl/2), tot);
+ if(nnl < wnl)
+ nnl = (wnl+nnl)/2;
+ if(nnl == 0)
+ nnl = 2;
+ dnl = nnl - onl;
+ /* compute new #lines for each window */
+ for(k=1; k<c->nw; k++){
+ /* prune from later window */
+ j = i+k;
+ if(j<c->nw && nl[j]){
+ l = min(dnl, max(1, nl[j]/2));
+ nl[j] -= l;
+ nl[i] += l;
+ dnl -= l;
+ }
+ /* prune from earlier window */
+ j = i-k;
+ if(j>=0 && nl[j]){
+ l = min(dnl, max(1, nl[j]/2));
+ nl[j] -= l;
+ nl[i] += l;
+ dnl -= l;
+ }
+ }
+ Pack:
+ /* pack everyone above */
+ y1 = cr.min.y;
+ for(j=0; j<i; j++){
+ v = c->w[j];
+ r = v->r;
+ r.min.y = y1;
+ r.max.y = y1+Dy(v->tag.all)+Border+Dy(v->url.all);
+ if(nl[j])
+ r.max.y += 1 + nl[j];
+ if(!c->safe || !eqrect(v->r, r))
+ winresize(v, r, c->safe);
+
+ r.min.y = v->r.max.y;
+ r.max.y += Border;
+ draw(screen, r, display->black, nil, ZP);
+ y1 = r.max.y;
+ }
+ /* scan to see new size of everyone below */
+ y2 = c->r.max.y;
+ for(j=c->nw-1; j>i; j--){
+ v = c->w[j];
+ r = v->r;
+ r.min.y = y2-Dy(v->tag.all)-Border-Dy(v->url.all);
+ if(nl[j])
+ r.min.y -= 1 + nl[j];
+ r.min.y -= Border;
+ ny[j] = r.min.y;
+ y2 = r.min.y;
+ }
+ /* compute new size of window */
+ r = w->r;
+ r.min.y = y1;
+ if(i == c->nw-1)
+ r.max.y = c->r.max.y;
+ else{
+ r.max.y = r.min.y+Dy(w->tag.all)+Border+Dy(w->url.all);
+ h = font->height;
+ if(y2-r.max.y >= 2*(1+h+Border)){
+ r.max.y += 1;
+ r.max.y += h*((y2-r.max.y)/h);
+ }
+ }
+ /* draw window */
+ if(!c->safe || !eqrect(w->r, r))
+ winresize(w, r, c->safe);
+
+ if(i < c->nw-1){
+ r.min.y = r.max.y;
+ r.max.y += Border;
+ draw(screen, r, display->black, nil, ZP);
+ for(j=i+1; j<c->nw; j++)
+ ny[j] -= (y2-r.max.y);
+ }
+ /* pack everyone below */
+ y1 = r.max.y;
+ for(j=i+1; j<c->nw; j++){
+ v = c->w[j];
+ r = v->r;
+ r.min.y = y1;
+ if(j == c->nw-1)
+ r.max.y = c->r.max.y;
+ else{
+ r.max.y = y1+Dy(v->tag.all)+Border+Dy(v->url.all);
+ if(nl[j])
+ r.max.y += 1 + nl[j];
+ }
+ if(!c->safe || !eqrect(v->r, r))
+ winresize(v, r, c->safe);
+ if(j < c->nw-1){ /* no border on last window */
+ r.min.y = v->r.max.y;
+ r.max.y += Border;
+ draw(screen, r, display->black, nil, ZP);
+ }
+ y1 = r.max.y;
+ }
+ free(nl);
+ free(ny);
+ c->safe = TRUE;
+ winmousebut(w);
+}
+
+void
+coldragwin(Column *c, Window *w, int but)
+{
+ Rectangle r;
+ int i, b;
+ Point p, op;
+ Window *v;
+ Column *nc;
+
+ clearmouse();
+ setcursor(mousectl, &boxcursor);
+ b = mouse->buttons;
+ op = mouse->xy;
+ while(mouse->buttons == b)
+ readmouse(mousectl);
+ setcursor(mousectl, nil);
+ if(mouse->buttons){
+ while(mouse->buttons)
+ readmouse(mousectl);
+ return;
+ }
+
+ for(i=0; i<c->nw; i++)
+ if(c->w[i] == w)
+ goto Found;
+ error("can't find window");
+
+ Found:
+ p = mouse->xy;
+ if(abs(p.x-op.x)<5 && abs(p.y-op.y)<5){
+ colgrow(c, w, but);
+ winmousebut(w);
+ return;
+ }
+ /* is it a flick to the right? */
+ if(abs(p.y-op.y)<10 && p.x>op.x+30 && rowwhichcol(c->row, p)==c)
+ p.x = op.x+Dx(w->r); /* yes: toss to next column */
+ nc = rowwhichcol(c->row, p);
+ if(nc!=nil && nc!=c){
+ colclose(c, w, FALSE);
+ coladd(nc, w, nil, p.y);
+ winmousebut(w);
+ return;
+ }
+ if(i==0 && c->nw==1)
+ return; /* can't do it */
+ if((i>0 && p.y<c->w[i-1]->r.min.y) || (i<c->nw-1 && p.y>w->r.max.y)
+ || (i==0 && p.y>w->r.max.y)){
+ /* shuffle */
+ colclose(c, w, FALSE);
+ coladd(c, w, nil, p.y);
+ winmousebut(w);
+ return;
+ }
+ if(i == 0)
+ return;
+ v = c->w[i-1];
+ if(p.y < v->url.all.max.y)
+ p.y = v->url.all.max.y;
+ if(p.y > w->r.max.y-Dy(w->tag.all)-Border-Dy(w->url.all))
+ p.y = w->r.max.y-Dy(w->tag.all)-Border-Dy(w->url.all);
+ r = v->r;
+ r.max.y = p.y;
+ if(r.max.y > v->page.all.min.y){
+ r.max.y -= (r.max.y-v->page.all.min.y)%font->height;
+ if(v->page.all.min.y == v->page.all.max.y)
+ r.max.y++;
+ }
+ if(!eqrect(v->r, r))
+ winresize(v, r, c->safe);
+ r.min.y = v->r.max.y;
+ r.max.y = r.min.y+Border;
+ draw(screen, r, display->black, nil, ZP);
+ r.min.y = r.max.y;
+ if(i == c->nw-1)
+ r.max.y = c->r.max.y;
+ else
+ r.max.y = c->w[i+1]->r.min.y-Border;
+ if(!eqrect(w->r, r))
+ winresize(w, r, c->safe);
+ c->safe = TRUE;
+ winmousebut(w);
+}
+
+Text*
+colwhich(Column *c, Point p, Rune r, int key)
+{
+ Window *w;
+ Text *t;
+ int i;
+
+ if(!ptinrect(p, c->r))
+ return nil;
+ if(ptinrect(p, c->tag.all))
+ return &c->tag;
+ for(i=0; i<c->nw; i++){
+ w = c->w[i];
+ if(ptinrect(p, w->r)){
+ winlock(w, key ? 'K' : 'M');
+ if(key)
+ t = wintype(w, p, r);
+ else
+ t = winmouse(w, p, r);
+ winunlock(w);
+ return t;
+ }
+ }
+ return nil;
+}
+
+int
+colclean(Column *c)
+{
+ int i, clean;
+
+ clean = TRUE;
+ for(i=0; i<c->nw; i++)
+ clean &= winclean(c->w[i], TRUE);
+ return clean;
+}
diff --git a/sys/src/cmd/abaco/dat.h b/sys/src/cmd/abaco/dat.h
new file mode 100755
index 000000000..afe553f5b
--- /dev/null
+++ b/sys/src/cmd/abaco/dat.h
@@ -0,0 +1,355 @@
+typedef struct Box Box;
+typedef struct Cimage Cimage;
+typedef struct Column Column;
+typedef struct Exec Exec;
+typedef struct Line Line;
+typedef struct Page Page;
+typedef struct Row Row;
+typedef struct Runestr Runestr;
+typedef struct Text Text;
+typedef struct Timer Timer;
+typedef struct Url Url;
+typedef struct Window Window;
+
+struct Runestr
+{
+ Rune *r;
+ int nr;
+};
+
+enum
+{
+ Rowtag,
+ Columntag,
+ Tag,
+ Urltag,
+ Statustag,
+ Entry,
+ Textarea,
+};
+
+struct Text
+{
+ Frame;
+ uint org;
+ uint q0;
+ uint q1;
+ int what;
+ Window *w;
+ Rectangle scrollr;
+ Rectangle lastsr;
+ Rectangle all;
+ Row *row;
+ Column *col;
+ Runestr rs;
+};
+
+uint textbacknl(Text*, uint, uint);
+int textbswidth(Text*, Rune);
+int textclickmatch(Text*, int, int, int, uint*);
+void textclose(Text*);
+void textdelete(Text*, uint, uint);
+void textdoubleclick(Text*, uint*, uint*);
+void textfill(Text*);
+void textframescroll(Text*, int);
+void textinit(Text *, Image *, Rectangle, Font *, Image **);
+void textinsert(Text*, uint, Rune*, uint);
+void textredraw(Text *, Rectangle, Font *, Image *);
+int textresize(Text *, Image *, Rectangle);
+void textscrdraw(Text*);
+void textscroll(Text*, int);
+void textselect(Text*);
+int textselect2(Text *, uint *, uint *, Text **);
+int textselect3(Text *, uint *, uint *);
+void textset(Text *, Rune *, int);
+void textsetorigin(Text*, uint, int);
+void textsetselect(Text*, uint, uint);
+void textshow(Text*, uint, uint, int);
+void texttype(Text*, Rune);
+void textmouse(Text *, Point, int);
+
+struct Line
+{
+ Rectangle r;
+ int state;
+ int hastext;
+ int hastable;
+ Box *boxes;
+ Box *lastbox;
+ Line *prev;
+ Line *next;
+};
+
+struct Box
+{
+ Item *i;
+ Rectangle r;
+
+ void (*draw)(Box *, Page *, Image *);
+ void (*mouse)(Box *, Page *, int);
+ void (*key)(Box *, Page *, Rune);
+ Box *prev;
+ Box *next;
+};
+
+Box* boxalloc(Line *, Item *, Rectangle);
+void boxinit(Box *);
+
+struct Lay
+{
+ Rectangle r;
+ int width;
+ int xwall;
+ Line *lines;
+ Line *lastline;
+ Font *font;
+ Ifloat *floats;
+ int laying;
+};
+
+void laypage(Page *p);
+Lay* layitems(Item *, Rectangle, int);
+void laydraw(Page *, Image *, Lay *);
+void layfree(Lay *);
+
+struct Cimage
+{
+ Ref;
+ Image *i;
+ Memimage *mi;
+ Url *url;
+ Cimage *next;
+};
+
+struct Url
+{
+ Ref; /* urls in window.url[] are not freed */
+ int id;
+ int method; /* HGet or HPost */
+ Runestr src; /* requested url */
+ Runestr act; /* actual url (redirection) */
+ Runestr post; /* only set if method==HPost */
+ Runestr ctype; /* content type */
+};
+
+Url* urlalloc(Runestr *, Runestr *, int);
+void urlfree(Url *);
+Url* urldup(Url *);
+int urlopen(Url *);
+
+struct Page
+{
+ Url *url;
+ Runestr title;
+ Window *w;
+ Image *b;
+
+ Rectangle r;
+ Rectangle all;
+ Rectangle vscrollr;
+ Rectangle hscrollr;
+ Row *row;
+ Column *col;
+
+ Docinfo *doc;
+ Kidinfo *kidinfo;
+ Item *items;
+ Lay *lay;
+ Point pos;
+
+ int selecting;
+ Point top, bot;
+ Box *topbx, *botbx;
+
+ int aborting;
+ int changed;
+ int loading;
+
+ Rune *status;
+
+ Page *parent;
+ Page *child;
+ Page *next;
+
+ Cimage **cimage;
+ int ncimage;
+
+ struct{
+ long t;
+ Runestr rs;
+ }refresh;
+};
+
+void pageget(Page *, Runestr *, Runestr *, int, int);
+void pageload(Page *, Url *, int);
+void pageclose(Page *);
+void pageredraw(Page *);
+void pagerender(Page *);
+void pagemouse(Page *, Point, int);
+void pagetype(Page *, Rune, Point);
+void pagescrldraw(Page *);
+void pagescroll(Page *, int, int);
+int pagescrollxy(Page *, int, int);
+int pageabort(Page *);
+void pagesnarf(Page *);
+void pagesetrefresh(Page *);
+int pagerefresh(Page *);
+
+struct Window
+{
+ Ref;
+ QLock;
+ Text tag;
+ Text url;
+ Page page;
+ Text status;
+ int owner;
+ int inpage;
+ Rectangle r;
+ Column *col;
+ struct{
+ Url **url;
+ int nurl;
+ int cid;
+ }history;
+};
+
+void wininit(Window *, Window *, Rectangle);
+int winclean(Window *, int);
+void winclose(Window *);
+int winresize(Window *, Rectangle, int);
+Text* wintext(Window *, Point);
+void winlock(Window *, int);
+void winunlock(Window *);
+void winaddhist(Window *, Url *);
+void wingohist(Window *, int);
+void winsettag(Window *);
+void winseturl(Window *);
+void winsetstatus(Window *w, Rune *);
+Text* wintype(Window *, Point, Rune);
+Text* winmouse(Window *, Point, int);
+void winmousebut(Window *);
+void windebug(Window *);
+
+struct Column
+{
+ Rectangle r;
+ Text tag;
+ Row *row;
+ Window **w;
+ int nw;
+ int safe;
+};
+
+void colinit(Column*, Rectangle);
+Window* coladd(Column*, Window*, Window*, int);
+void colclose(Column*, Window*, int);
+void colcloseall(Column*);
+void colresize(Column*, Rectangle);
+Text* colwhich(Column*, Point, Rune, int);
+void coldragwin(Column*, Window*, int);
+void colgrow(Column*, Window*, int);
+int colclean(Column*);
+void colsort(Column*);
+void colmousebut(Column*);
+
+struct Row
+{
+ QLock;
+ Rectangle r;
+ Text tag;
+ Column **col;
+ int ncol;
+
+};
+
+void rowinit(Row*, Rectangle);
+Column* rowadd(Row*, Column *c, int);
+void rowclose(Row*, Column*, int);
+Text* rowwhich(Row*, Point, Rune, int);
+Column* rowwhichcol(Row*, Point);
+void rowresize(Row*, Rectangle);
+void rowdragcol(Row*, Column*, int but);
+
+struct Exec
+{
+ char *cmd;
+ int p[2]; /* p[1] is write to program; p[0] set to prog fd 0*/
+ int q[2]; /* q[0] is read from program; q[1] set to prog fd 1 */
+ Channel *sync; /* chan(ulong) */
+};
+
+struct Timer
+{
+ int dt;
+ int cancel;
+ Channel *c; /* chan(int) */
+ Timer *next;
+};
+
+enum
+{
+ Scrollsize = 12,
+ Scrollgap = 4,
+ Margin = 4,
+ Border = 2,
+ Space = 2,
+ Tabspace = 30,
+ Boxsize = 12,
+ WFont = FntR*NumSize+Tiny,
+
+ Panspeed = 4,
+ Maxtab = 8,
+
+ BUFSIZE = 1024*8,
+ RBUFSIZE = BUFSIZE/sizeof(Rune),
+ STACK = 64*1024,
+};
+
+enum
+{
+ FALSE,
+ TRUE,
+ XXX,
+};
+
+enum
+{
+ Light = 0xEEEEEE,
+ Dark = 0x666666,
+ Red = 0xBB0000,
+ Back = 0xCCCCCC,
+};
+
+Mouse *mouse;
+Mousectl *mousectl;
+Keyboardctl *keyboardctl;
+Image *tagcols[NCOL];
+Image *textcols[NCOL];
+Image *but2col;
+Image *but3col;
+Image *button;
+Image *colbutton;
+Font *passfont;
+Cursor boxcursor;
+Row row;
+Text *argtext;
+Text *seltext;
+Text *typetext;
+Page *selpage;
+Column *activecol;
+char *webmountpt;
+int plumbsendfd;
+int webctlfd;
+char *charset;
+int procstderr;
+
+enum
+{
+ Kscrolloneup = KF|0x20,
+ Kscrollonedown = KF|0x21,
+};
+
+Channel *cplumb; /* chan(Plumbmsg*) */
+Channel *cexit; /* chan(int) */
+Channel *crefresh; /* chan(page *) */
diff --git a/sys/src/cmd/abaco/exec.c b/sys/src/cmd/abaco/exec.c
new file mode 100755
index 000000000..59c8f03d2
--- /dev/null
+++ b/sys/src/cmd/abaco/exec.c
@@ -0,0 +1,531 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <memdraw.h>
+#include <thread.h>
+#include <cursor.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include <frame.h>
+#include <plumb.h>
+#include <html.h>
+#include "dat.h"
+#include "fns.h"
+
+void del(Text *, Text *, int, int, Rune *, int);
+void delcol(Text *, Text *, int, int, Rune *, int);
+void cut(Text *, Text *, int, int, Rune *, int);
+void exit(Text *, Text *, int, int, Rune *, int);
+void get(Text *, Text *, int, int, Rune *, int);
+void go(Text *,Text *, int, int, Rune *, int);
+void google(Text *,Text *, int, int, Rune *, int);
+void new(Text*, Text *, int, int, Rune *, int);
+void newcol(Text*, Text *, int, int, Rune *, int);
+void paste(Text *, Text *, int, int, Rune *, int);
+void sort(Text *, Text *, int, int, Rune *, int);
+void stop(Text *, Text *, int, int, Rune *, int);
+void debug(Text *, Text *, int, int, Rune *, int);
+
+typedef struct Exectab Exectab;
+struct Exectab
+{
+ Rune *name;
+ void (*fn)(Text *, Text *, int, int, Rune *, int);
+ int flag1;
+ int flag2;
+};
+
+Exectab exectab[] = {
+ { L"Back", go, FALSE, XXX },
+ { L"Cut", cut, TRUE, TRUE },
+ { L"Debug", debug, XXX, XXX },
+ { L"Del", del, XXX, XXX },
+ { L"Delcol", delcol, FALSE, TRUE },
+ { L"Exit", exit, XXX, XXX },
+ { L"Get", get, XXX, XXX },
+ { L"Google", google, XXX, XXX },
+ { L"New", new, XXX, XXX },
+ { L"Newcol", newcol, XXX, XXX },
+ { L"Next", go, TRUE, XXX },
+ { L"Paste", paste, TRUE, XXX },
+ { L"Snarf", cut, TRUE, FALSE },
+ { L"Stop", stop, XXX, XXX },
+ { L"Sort", sort, XXX, XXX },
+ { nil, nil, 0, 0 },
+};
+
+static
+Exectab*
+lookup(Rune *r, int n)
+{
+ Exectab *e;
+ int nr;
+
+ r = skipbl(r, n, &n);
+ if(n == 0)
+ return nil;
+ findbl(r, n, &nr);
+ nr = n-nr;
+ for(e=exectab; e->name; e++)
+ if(runeeq(r, nr, e->name, runestrlen(e->name)) == TRUE)
+ return e;
+ return nil;
+}
+
+int
+isexecc(int c)
+{
+ if(isalnum(c))
+ return 1;
+ return c=='<' || c=='|' || c=='>';
+}
+
+void
+execute(Text *t, uint aq0, uint aq1, Text *)
+{
+ uint q0, q1;
+ Rune *r, *s;
+ Exectab *e;
+ int c, n;
+
+ q0 = aq0;
+ q1 = aq1;
+ if(q1 == q0){ /* expand to find word (actually file name) */
+ /* if in selection, choose selection */
+ if(t->q1>t->q0 && t->q0<=q0 && q0<=t->q1){
+ q0 = t->q0;
+ q1 = t->q1;
+ }else{
+ while(q1<t->rs.nr && isexecc(c=t->rs.r[q1]) && c!=':')
+ q1++;
+ while(q0>0 && isexecc(c=t->rs.r[q0-1]) && c!=':')
+ q0--;
+ if(q1 == q0)
+ return;
+ }
+ }
+ r = runemalloc(q1-q0);
+ runemove(r, t->rs.r+q0, q1-q0);
+ e = lookup(r, q1-q0);
+ if(e){
+ s = skipbl(r, q1-q0, &n);
+ s = findbl(s, n, &n);
+ s = skipbl(s, n, &n);
+ (*e->fn)(t, seltext, e->flag1, e->flag2, s, n);
+ }
+ free(r);
+}
+
+void
+newcol(Text *et, Text *, int, int, Rune *, int)
+{
+ Column *c;
+
+ c = rowadd(et->row, nil, -1);
+ if(c)
+ winsettag(coladd(c, nil, nil, -1));
+}
+
+void
+delcol(Text *t, Text *, int, int, Rune *, int)
+{
+ Column *c;
+
+ c = t->col;
+ if(c==nil || colclean(c)==0)
+ return;
+
+ rowclose(c->row, c, TRUE);
+}
+
+void
+del(Text *et, Text *, int flag1, int, Rune *, int)
+{
+ if(et->w==nil)
+ return;
+
+ if(flag1 || winclean(et->w, FALSE))
+ colclose(et->col, et->w, TRUE);
+}
+
+void
+sort(Text *et, Text *, int, int, Rune *, int)
+{
+ if(et->col)
+ colsort(et->col);
+}
+
+void
+exit(Text *, Text *, int, int, Rune *, int)
+{
+ sendul(cexit, 0);
+ threadexits(nil);
+}
+
+void
+debug(Text *, Text *, int, int, Rune *, int)
+{
+ Column *c;
+ int i, j;
+
+ for(j=0; j<row.ncol; j++){
+ c = row.col[j];
+ for(i=0; i<c->nw; i++){
+ fprint(2, "Col: %d; Win: %d\n", j, i);
+ windebug(c->w[i]);
+ }
+ }
+}
+
+void
+stop(Text *t, Text *, int, int, Rune *, int)
+{
+ if(t==nil || t->w==nil)
+ return;
+
+ pageabort(&t->w->page);
+}
+
+void
+get(Text *t, Text *, int, int, Rune *, int)
+{
+ Window *w;
+ int dohist;
+
+ if(t==nil || t->w==nil)
+ return;
+ w = t->w;
+ if(w->url.rs.nr == 0)
+ return;
+
+ dohist = FALSE;
+ if(w->page.url==nil || runestreq(w->page.url->act, w->url.rs)==FALSE)
+ dohist = TRUE;
+
+ pageget(&w->page, &w->url.rs, nil, HGet, dohist);
+}
+
+void
+go(Text *et, Text *t, int isnext, int, Rune *, int)
+{
+ if(et!=nil && et->w!=nil)
+ t = et;
+ if(t==nil || t->w==nil)
+ return;
+
+ wingohist(t->w, isnext);
+}
+
+void
+cut(Text *, Text *t, int dosnarf, int docut, Rune *, int)
+{
+ Runestr rs;
+ uint u;
+
+ if(selpage){
+ if(dosnarf && !docut && !eqpt(selpage->top, selpage->bot))
+ pagesnarf(selpage);
+ return;
+ }
+ if(t==nil){
+ /* can only happen if seltext == nil */
+ return;
+ }
+ if(t->q0 > t->q1){
+ u = t->q0;
+ t->q0 = t->q1;
+ t->q1 =u;
+ }
+
+ if(t->q0 == t->q1)
+ return;
+
+ if(dosnarf){
+ rs.nr = t->q1-t->q0;
+ rs.r = runemalloc(rs.nr);
+ runemove(rs.r, t->rs.r+t->q0, rs.nr);
+ putsnarf(&rs);
+ closerunestr(&rs);
+ }
+ if(docut){
+ textdelete(t, t->q0, t->q1);
+ textsetselect(t, t->q0, t->q0);
+ if(t->w)
+ textscrdraw(t);
+ }else if(dosnarf) /* Snarf command */
+ argtext = t;
+}
+
+void
+paste(Text *, Text *t, int selectall, int, Rune *, int)
+{
+ Runestr rs;
+ uint q1;
+
+ if(t == nil)
+ return;
+
+ getsnarf(&rs);
+ if(rs.nr == 0)
+ return;
+
+ cut(t, t, FALSE, TRUE, nil, 0);
+ textinsert(t, t->q0, rs.r, rs.nr);
+ q1 = t->q0+rs.nr;
+ if(selectall)
+ textsetselect(t, t->q0, q1);
+ else
+ textsetselect(t, q1, q1);
+ if(t->w)
+ textscrdraw(t);
+
+ closerunestr(&rs);
+}
+
+typedef struct Expand Expand;
+
+struct Expand
+{
+ uint q0;
+ uint q1;
+ Rune *name;
+ int nname;
+ int jump;
+ union{
+ Text *at;
+ Rune *ar;
+ };
+ int (*agetc)(void*, uint);
+ int a0;
+ int a1;
+};
+
+int
+expand(Text *t, uint q0, uint q1, Expand *e)
+{
+ memset(e, 0, sizeof *e);
+
+ /* if in selection, choose selection */
+ e->jump = TRUE;
+ if(q1==q0 && t->q1>t->q0 && t->q0<=q0 && q0<=t->q1){
+ q0 = t->q0;
+ q1 = t->q1;
+ if(t->what == Tag)
+ e->jump = FALSE;
+ }
+ if(q0 == q1){
+ while(q1<t->rs.nr && isalnum(t->rs.r[q1]))
+ q1++;
+ while(q0>0 && isalnum(t->rs.r[q0-1]))
+ q0--;
+ }
+ e->q0 = q0;
+ e->q1 = q1;
+ return q1 > q0;
+}
+
+void
+look3(Text *t, uint q0, uint q1)
+{
+ Expand e;
+ Text *ct;
+ Runestr rs;
+ char buf[32];
+ Rune *r, c;
+ uint p;
+ int n;
+
+ ct = seltext;
+ if(ct == nil)
+ ct = seltext = t;
+ if(expand(t, q0, q1, &e) == FALSE)
+ return;
+ if(plumbsendfd >= 0){
+ /* send whitespace-delimited word to plumber */
+ buf[0] = '\0';
+ if(q1 == q0){
+ if(t->q1>t->q0 && t->q0<=q0 && q0<=t->q1){
+ q0 = t->q0;
+ q1 = t->q1;
+ }else{
+ p = q0;
+ while(q0>0 && (c=t->rs.r[q0-1])!=L' ' && c!=L'\t' && c!=L'\n')
+ q0--;
+ while(q1<t->rs.nr && (c=t->rs.r[q1])!=L' ' && c!=L'\t' && c!=L'\n')
+ q1++;
+ if(q1 == q0)
+ return;
+ sprint(buf, "click=%d", p-q0);
+ }
+ }
+ rs.r = runemalloc(q1-q0);
+ runemove(rs.r, t->rs.r+q0, q1-q0);
+ rs.nr = q1-q0;
+ if(plumbrunestr(&rs, buf) >= 0){
+ closerunestr(&rs);
+ return;
+ }
+ /* plumber failed to match; fall through */
+ }
+ if(t == ct)
+ textsetselect(ct, e.q1, e.q1);
+ n = e.q1 - e.q0;
+ r = runemalloc(n);
+ runemove(r, t->rs.r+e.q0, n);
+ if(search(ct, r, n) && e.jump)
+ moveto(mousectl, addpt(frptofchar(ct, ct->p0), Pt(4, ct->font->height-4)));
+
+ free(r);
+}
+
+int
+search(Text *ct, Rune *r, uint n)
+{
+ uint q, nb, maxn;
+ int around;
+ Rune *s, *b, *c;
+
+ if(n==0 || n>ct->rs.nr || 2*n>RBUFSIZE)
+ return FALSE;
+
+ maxn = max(n*2, RBUFSIZE);
+ s = runemalloc(RBUFSIZE);
+ b = s;
+ nb = 0;
+ b[nb] = 0;
+ around = 0;
+ q = ct->q1;
+ for(;;){
+ if(q >= ct->rs.nr){
+ q = 0;
+ around = 1;
+ nb = 0;
+ b[nb] = 0;
+ }
+ if(nb > 0){
+ c = runestrchr(b, r[0]);
+ if(c == nil){
+ q += nb;
+ nb = 0;
+ b[nb] = 0;
+ if(around && q>=ct->q1)
+ break;
+ continue;
+ }
+ q += (c-b);
+ nb -= (c-b);
+ b = c;
+ }
+ /* reload if buffer covers neither string nor rest of file */
+ if(nb<n && nb!=ct->rs.nr-q){
+ nb = ct->rs.nr-q;
+ if(nb >= maxn)
+ nb = maxn-1;
+ runemove(s, ct->rs.r+q, nb);
+ b = s;
+ b[nb] = '\0';
+ }
+ /* this runeeq is fishy but the null at b[nb] makes it safe */
+ if(runeeq(b, n, r, n) == TRUE){
+ if(ct->w)
+ textshow(ct, q, q+n, 1);
+ else{
+ ct->q0 = q;
+ ct->q1 = q+n;
+ }
+ seltext = ct;
+ free(s);
+ return TRUE;
+ }
+ if(around && q>=ct->q1)
+ break;
+ --nb;
+ b++;
+ q++;
+ }
+ free(s);
+ return FALSE;
+}
+
+Window*
+lookpage(Rune *s, int n)
+{
+ int i, j;
+ Window *w;
+ Column *c;
+ Page *p;
+
+ /* avoid terminal slash on directories */
+ if(n>1 && s[n-1] == '/')
+ --n;
+ for(j=0; j<row.ncol; j++){
+ c = row.col[j];
+ for(i=0; i<c->nw; i++){
+ w = c->w[i];
+ p = &w->page;
+ if(p->url && runeeq(p->url->src.r, p->url->src.nr, s, n))
+ if(w->col != nil)
+ return w;
+ }
+ }
+ return nil;
+}
+
+Window *
+openpage(Page *p, Runestr *rs)
+{
+ Window *w;
+
+ if(!validurl(rs->r))
+ return nil;
+
+ w = lookpage(rs->r, rs->nr);
+ if(w){
+ p = &w->page;
+ if(!p->col->safe && Dy(p->r)==0) /* window is obscured by full-column window */
+ colgrow(p->col, p->col->w[0], 1);
+ }else{
+ w = makenewwindow(p);
+ winsettag(w);
+ pageget(&w->page, rs, nil, HGet, TRUE);
+ }
+ return w;
+}
+
+void
+plumblook(Plumbmsg *m)
+{
+ Runestr rs;
+
+ if(m->ndata >= BUFSIZE){
+ fprint(2, "insanely long file name (%d bytes) in plumb message (%.32s...)\n", m->ndata, m->data);
+ return;
+ }
+ if(m->data[0] == '\0')
+ return;
+
+ bytetorunestr(m->data, &rs);
+ openpage(nil, &rs);
+ closerunestr(&rs);
+}
+
+void
+new(Text *et, Text *, int, int, Rune *, int)
+{
+ if(et->col != nil)
+ winsettag(coladd(et->col, nil, nil, -1));
+}
+
+void
+google(Text *, Text *, int, int, Rune *arg, int narg)
+{
+ Runestr rs;
+ Rune *s;
+
+ s = ucvt(arg);
+ rs.r = runesmprint("http://www.google.com/search?hl=en&ie=UTF-8&q=%.*S", narg, s);
+ rs.nr = runestrlen(rs.r);
+ openpage(nil, &rs);
+ free(s);
+ closerunestr(&rs);
+}
diff --git a/sys/src/cmd/abaco/fns.h b/sys/src/cmd/abaco/fns.h
new file mode 100755
index 000000000..3e739a7e8
--- /dev/null
+++ b/sys/src/cmd/abaco/fns.h
@@ -0,0 +1,85 @@
+#define runemalloc(a) emalloc((a)*sizeof(Rune))
+#define runerealloc(a, b) erealloc(a, (b)*sizeof(Rune))
+#define runemove(a, b, c) memmove(a, b, (c)*sizeof(Rune))
+
+#define hasbrk(x) ((x)&IFbrk || (x)&IFbrksp)
+#define istrue(x) ((x) ? "true" : "false")
+
+void plumblook(Plumbmsg*m);
+int plumbrunestr(Runestr *, char *);
+void putsnarf(Runestr *);
+void getsnarf(Runestr *);
+
+void tablesize(Table *, int);
+void drawtable(Box *, Page *, Image *);
+void laytable(Itable *, Rectangle);
+void settables(Page *);
+void laysnarf(Page *, Lay *, Runestr *);
+Timer* timerstart(int);
+void timerstop(Timer*);
+void timercancel(Timer*);
+void timerinit(void);
+
+void cut(Text *, Text *, int, int, Rune *, int);
+void get(Text *, Text *, int, int, Rune *, int);
+void paste(Text *, Text *, int, int, Rune *, int);
+void execute(Text *, uint, uint, Text *);
+void look3(Text *, uint, uint);
+int search(Text *, Rune *, uint);
+
+void scrsleep(uint);
+void scrlresize(void);
+void tmpresize(void);
+
+void initfontpaths(void);
+void cvttorunes(char*, int, Rune*, int*, int*, int*);
+void error(char *);
+void closerunestr(Runestr *);
+void copyrunestr(Runestr *, Runestr *);
+int runestreq(Runestr, Runestr);
+int validurl(Rune *);
+int runeeq(Rune *, uint, Rune *, uint);
+int min(int, int);
+int max(int, int);
+int isalnum(Rune);
+Rune* skipbl(Rune *, int, int *);
+Rune* findbl(Rune *r, int, int *);
+char* estrdup(char *);
+Rune* erunestrdup(Rune *);
+Rune* ucvt(Rune *s);
+int dimwidth(Dimen , int);
+void frdims(Dimen *, int, int, int **);
+Image* getbg(Page *);
+Rune* getbase(Page *);
+Image* eallocimage(Display *, Rectangle, ulong, int, int);
+Image* getcolor(int);
+void freecolors(void);
+Font* getfont(int);
+void freefonts(void);
+void colarray(Image **, Image *, Image *, Image *, int);
+void rect3d(Image *, Rectangle, int, Image **, Point);
+void ellipse3d(Image *, Point, int, int, Image **, Point);
+void reverseimages(Iimage **);
+void setstatus(Window *, char *, ...);
+int istextfield(Item *);
+int forceitem(Item *);
+int xtofchar(Rune *, Font *, long);
+int istextsel(Page *, Rectangle, int *, int *, Rune *, Font *);
+char* convert(Runestr, char *, long *);
+void execproc(void *);
+void getimage(Cimage *, Rune *);
+Point getpt(Page *p, Point);
+Rune *urlcombine(Rune *, Rune *);
+void fixtext(Page *);
+void addrefresh(Page *, char *, ...);
+void flushrefresh(void);
+void savemouse(Window *);
+void restoremouse(Window *);
+void clearmouse(void);
+void bytetorunestr(char *, Runestr *);
+Window* makenewwindow(Page *);
+
+Line* linewhich(Lay *, Point);
+Box* pttobox(Line *, Point);
+Box* boxwhich(Lay *, Point);
+
diff --git a/sys/src/cmd/abaco/fonts.h b/sys/src/cmd/abaco/fonts.h
new file mode 100755
index 000000000..54719852c
--- /dev/null
+++ b/sys/src/cmd/abaco/fonts.h
@@ -0,0 +1,20 @@
+ "/lib/font/bit/lucidasans/unicode.6.font",
+ "/lib/font/bit/lucidasans/unicode.7.font",
+ "/lib/font/bit/lucidasans/unicode.8.font",
+ "/lib/font/bit/lucidasans/unicode.10.font",
+ "/lib/font/bit/lucidasans/unicode.13.font",
+ "/lib/font/bit/lucidasans/italicunicode.6.font",
+ "/lib/font/bit/lucidasans/italicunicode.7.font",
+ "/lib/font/bit/lucidasans/italicunicode.8.font",
+ "/lib/font/bit/lucidasans/italicunicode.10.font",
+ "/lib/font/bit/lucidasans/italicunicode.13.font",
+ "/lib/font/bit/lucidasans/boldunicode.6.font",
+ "/lib/font/bit/lucidasans/boldunicode.7.font",
+ "/lib/font/bit/lucidasans/boldunicode.8.font",
+ "/lib/font/bit/lucidasans/boldunicode.10.font",
+ "/lib/font/bit/lucidasans/boldunicode.13.font",
+ "/lib/font/bit/fixed/unicode.6x12.font",
+ "/lib/font/bit/fixed/unicode.8x13.font",
+ "/lib/font/bit/fixed/unicode.9x15.font",
+ "/lib/font/bit/fixed/unicode.9x18.font",
+ "/lib/font/bit/fixed/unicode.10x20.font",
diff --git a/sys/src/cmd/abaco/html.c b/sys/src/cmd/abaco/html.c
new file mode 100755
index 000000000..9a53eec53
--- /dev/null
+++ b/sys/src/cmd/abaco/html.c
@@ -0,0 +1,1072 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <memdraw.h>
+#include <thread.h>
+#include <cursor.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include <frame.h>
+#include <plumb.h>
+#include <html.h>
+#include "dat.h"
+#include "fns.h"
+
+static void sizeitem(Lay *, Item *);
+
+static
+void
+sizetext(Lay *lay, Itext *i)
+{
+ lay->font = getfont(i->fnt);
+ i->height = lay->font->height + 2*Space;
+ i->width = runestringwidth(lay->font, i->s);
+ i->width += runestringnwidth(lay->font, L" ", 1);
+}
+
+static
+void
+sizerule(Lay *lay, Irule *i)
+{
+ i->width = lay->width;
+ i->height = Space + i->size + Space;
+}
+
+static
+void
+sizeimage(Lay *, Iimage *i)
+{
+ Cimage *ci;
+
+ ci = (Cimage *)i->aux;
+
+ if(ci==nil)
+ return;
+
+ if(ci->i == nil)
+ getimage(ci, i->altrep);
+ if(ci->i == nil)
+ return;
+ i->width = Dx(ci->i->r) + i->border + i->hspace;
+ i->height = Dy(ci->i->r) + i->border + i->vspace;
+}
+
+static
+void
+sizetextfield(Lay *, Iformfield *i)
+{
+ Formfield *ff;
+ Font *f;
+ int w, h;
+
+ ff = i->formfield;
+ if(ff->ftype == Ftextarea){
+ w = ff->cols;
+ h = ff->rows;
+ }else{
+ w = ff->size;
+ h = 1;
+ }
+ f = getfont(WFont);
+ i->width = runestringnwidth(f, L"0", 1)*w + 2*(Space+Border+Margin);
+ i->width += Scrollsize+Scrollgap;
+ i->height = f->height*h + 2*(Space+Border+Margin);
+}
+
+static
+void
+sizecheck(Lay *, Iformfield *i)
+{
+ i->width = Boxsize + Space;
+ i->height = Boxsize;
+}
+
+static
+void
+sizebutton(Lay *, Iformfield *i)
+{
+ Font *f;
+ int x;
+
+ x = Margin + Border + Space;
+ f = getfont(WFont);
+ i->width = runestringwidth(f, i->formfield->value) + 2*x + Space;
+ i->height = f->height + 2*x;
+}
+
+static
+void
+sizefimage(Lay *lay, Iformfield *i)
+{
+ Iimage *ii;
+
+ ii = (Iimage *)i->formfield->image;
+ sizeimage(lay, ii);
+ i->width = ii->width;
+ i->height = ii->height;
+}
+
+static
+void
+sizeselect(Lay *, Iformfield *i)
+{
+ Option *o;
+ Font *f;
+ int x;
+
+ f = getfont(WFont);
+ i->width = 0;
+ for(o=i->formfield->options; o!=nil; o=o->next)
+ i->width = max(i->width, runestringwidth(f, o->display));
+ x = Margin + Border + Space;
+ i->width += 2*x;
+ i->height = f->height+2*x;
+}
+
+static
+void
+sizeformfield(Lay *lay, Iformfield *i)
+{
+ int type;
+
+ type = i->formfield->ftype;
+
+ if(type==Ftext || type==Ftextarea || type==Fpassword)
+ sizetextfield(lay, i);
+ else if(type==Fcheckbox || type==Fradio)
+ sizecheck(lay, i);
+ else if(type==Fbutton || type==Freset || type==Fsubmit)
+ sizebutton(lay, i);
+ else if(type == Fimage)
+ sizefimage(lay, i);
+ else if(type == Fselect)
+ sizeselect(lay, i);
+}
+
+static
+void
+sizetable(Lay *lay, Itable *i)
+{
+ tablesize(i->table, lay->width);
+ i->width = i->table->totw;
+ i->height = i->table->toth;
+}
+
+static
+void
+sizefloat(Lay *lay, Ifloat *i)
+{
+ sizeitem(lay, i->item);
+ i->width = i->item->width;
+ i->height = i->item->height;
+}
+
+static
+void
+sizespacer(Lay *lay, Ispacer *i)
+{
+ if(i->spkind != ISPnull){
+ if(i->spkind == ISPhspace)
+ i->width = stringnwidth(lay->font, " ", 1);
+ i->height = lay->font->height + 2*Space;
+ }
+}
+
+static
+void
+sizeitem(Lay *lay, Item *i)
+{
+
+ switch(i->tag){
+ case Itexttag:
+ sizetext(lay, (Itext *)i);
+ break;
+ case Iruletag:
+ sizerule(lay, (Irule *)i);
+ break;
+ case Iimagetag:
+ sizeimage(lay, (Iimage *)i);
+ break;
+ case Iformfieldtag:
+ sizeformfield(lay, (Iformfield *)i);
+ break;
+ case Itabletag:
+ sizetable(lay, (Itable *)i);
+ break;
+ case Ifloattag:
+ sizefloat(lay, (Ifloat *)i);
+ break;
+ case Ispacertag:
+ sizespacer(lay, (Ispacer *)i);
+ break;
+ default:
+ error("can't happen");
+ }
+}
+
+static
+void
+drawtext(Box *b, Page *p, Image *im)
+{
+ Rectangle r, r1;
+ Image *c;
+ Point pt;
+ Font *f;
+ Itext *i;
+ int q0, q1;
+
+ r = rectsubpt(b->r, p->pos);
+ i = (Itext *)b->i;
+ f = getfont(i->fnt);
+ if(istextsel(p, b->r, &q0, &q1, i->s, f)){
+ r1 = r;
+ if(q0 > 0)
+ r1.min.x += runestringnwidth(f, i->s, q0);
+ if(q1 > 0)
+ r1.max.x = r1.min.x + runestringnwidth(f, i->s+q0, q1-q0);
+ draw(im, r1, textcols[HIGH], nil, ZP);
+ }
+ c = getcolor(i->fg);
+ runestringbg(im, r.min, c, ZP, f, i->s, im, addpt(r.min, im->r.min));
+
+ if(i->ul == ULnone)
+ return;
+
+ if(i->ul == ULmid)
+ r.min.y += f->height/2;
+ else
+ r.min.y +=f->height-1;
+ pt = r.min;
+ pt.x += runestringwidth(f, i->s);
+ line(im, r.min, pt, Enddisc, Enddisc, 0, c, ZP);
+}
+
+static
+void
+drawrule(Box *b, Page *p, Image *im)
+{
+ Rectangle r;
+ Irule *i;
+
+ i = ((Irule *)b->i);
+ r = rectsubpt(b->r, p->pos);
+ r.min.y += Space;
+ r.max.y -=Space;
+ draw(im, r, getcolor(i->color), nil, ZP);
+}
+
+static
+void
+drawimage(Box *b, Page *p, Image *im)
+{
+ Rectangle r;
+ Cimage *ci;
+ Iimage *i;
+ Image *c;
+
+ if(b->i->tag==Iimagetag)
+ i = (Iimage *)b->i;
+ else
+ i = (Iimage *)((Iformfield *)b->i)->formfield->image;
+
+ ci = (Cimage *)i->aux;
+ if(ci==nil || ci->i==nil)
+ return;
+
+ r = rectsubpt(b->r, p->pos);
+ r.min.x += i->border + i->hspace;
+ r.min.y += i->border + i->vspace;
+ r.max.x -= i->border + i->hspace;
+ r.max.y -= i->border + i->vspace;
+
+ draw(im, r, ci->i, nil, ci->i->r.min);
+
+ if(i->border){
+ if(i->anchorid >= 0)
+ c = getcolor(p->doc->link);
+ else
+ c = display->black;
+
+ border(im, r, i->border, c, ZP);
+ }
+}
+
+static
+void
+drawtextfield(Image *im, Rectangle r, Iformfield *i)
+{
+ Formfield *ff;
+ Image *c[3];
+ Text *t;
+ Font *f;
+
+ r = insetrect(r, Space);
+ colarray(c, getcolor(Dark), getcolor(Light), display->white, 1);
+ rect3d(im, r, Border, c, ZP);
+ r = insetrect(r, Border+Margin);
+
+ if(i->aux == nil){
+ ff = i->formfield;
+ t = emalloc(sizeof(Text));
+ if(ff->ftype == Ftextarea)
+ t->what = Textarea;
+ else
+ t->what = Entry;
+ if(ff->ftype == Fpassword)
+ f = passfont;
+ else
+ f = getfont(WFont);
+ textinit(t, im, r, f, textcols);
+ if(ff->value!=nil){
+ textinsert(t, 0, ff->value, runestrlen(ff->value));
+ textsetselect(t, t->rs.nr, t->rs.nr);
+ }
+ if(t->what == Textarea)
+ textscrdraw(t);
+ i->aux = t;
+ }else
+ textresize(i->aux, im, r);
+}
+
+void
+drawcheck(Image *im, Rectangle r, Formfield *f)
+{
+ Image *c[3];
+ Point pt;
+ int n;
+
+ if(f->flags & FFchecked)
+ colarray(c, getcolor(Dark), getcolor(Light), getcolor(Red), TRUE);
+ else
+ colarray(c, getcolor(Dark), getcolor(Light), getcolor(Back), FALSE);
+
+ if(f->ftype == Fradio){
+ n = Boxsize/2-1;
+ pt = addpt(r.min, Pt(n,n));
+ ellipse3d(im, pt, n, Border, c, ZP);
+ }else
+ rect3d(im, r, Border, c, ZP);
+}
+
+void
+drawbutton(Image *im, Rectangle r, Formfield *f, int checked)
+{
+ Image *c[3];
+
+ r = insetrect(r, Space);
+ colarray(c, getcolor(Dark), getcolor(Light), getcolor(Back), checked);
+ rect3d(im, r, Border, c, ZP);
+ r.min.x += Border + Margin;
+ r.min.y += Border + Margin;
+ runestringbg(im, r.min, display->black, ZP, getfont(WFont), f->value, c[2], ZP);
+}
+
+void
+drawselect(Image *im, Rectangle r, Iformfield *i)
+{
+ Formfield *f;
+ Image *c[3];
+
+ f = i->formfield;
+ if(f->options == nil)
+ return;
+ r = insetrect(r, Space);
+ colarray(c, getcolor(Dark), getcolor(Light), display->white, 1);
+ rect3d(im, r, Border, c, ZP);
+ r = insetrect(r, Border+Margin);
+ draw(im, r, textcols[HIGH], nil, ZP);
+ if(i->aux==nil){
+ i->aux = f->options->display;
+ i->formfield->value = erunestrdup(f->options->value);
+ }
+ runestring(im, r.min, display->black, ZP, getfont(WFont), i->aux);
+}
+
+/* Formfields are a special case */
+static
+void
+drawformfield(Box *b, Page *p, Image *im)
+{
+ Formfield *f;
+ int type;
+
+ f = ((Iformfield *)b->i)->formfield;
+ type =f->ftype;
+ if(istextfield(b->i))
+ drawtextfield(im, rectsubpt(b->r, p->pos), (Iformfield *)b->i);
+ else if(type==Fcheckbox || type==Fradio)
+ drawcheck(im, rectsubpt(b->r, p->pos), f);
+ else if(type==Fbutton || type==Freset || type==Fsubmit)
+ drawbutton(im, rectsubpt(b->r, p->pos), f, FALSE);
+ else if(type == Fimage)
+ drawimage(b, p, im);
+ else if(type == Fselect)
+ drawselect(im, rectsubpt(b->r, p->pos), (Iformfield *)b->i);
+}
+
+static
+void
+drawnull(Box *, Page *, Image *)
+{
+}
+
+static
+Page *
+whichtarget1(Page *p, Rune *r)
+{
+ Kidinfo *k;
+ Page *c, *ret;
+
+ k = p->kidinfo;
+ if(k && k->name && runestrcmp(k->name, r)==0)
+ return p;
+ for(c=p->child; c; c=c->next){
+ ret = whichtarget1(c, r);
+ if(ret)
+ return ret;
+ }
+ return nil;
+}
+
+static
+Page *
+whichtarget(Page *p, int t)
+{
+ Page *r;
+
+ switch(t){
+ case FTblank:
+ case FTtop:
+ r = &p->w->page;
+ break;
+ case FTself:
+ r = p;
+ break;
+ case FTparent:
+ r = p->parent;
+ break;
+ default:
+ if(targetname(t) == L"?")
+ error("targetname");
+ r = whichtarget1(&p->w->page, targetname(t));
+ }
+
+ return r ? r: &p->w->page;
+}
+
+static
+void
+mouselink(Box *b, Page *p, int but)
+{
+ Runestr rs;
+ Anchor *a;
+
+ /* eat mouse */
+ while(mousectl->buttons)
+ readmouse(mousectl);
+
+ if(b->i->anchorid < 0)
+ return;
+
+ /* binary search would be better */
+ for(a=p->doc->anchors; a!=nil; a=a->next)
+ if(a->index == b->i->anchorid)
+ break;
+
+ if(a==nil || a->href==nil)
+ return;
+
+ p = whichtarget(p, a->target);
+ rs.r = urlcombine(getbase(p), a->href);
+ if(rs.r == nil)
+ return;
+ rs.nr = runestrlen(rs.r);
+
+ if(but == 1)
+ pageget(p, &rs, nil, HGet, p==&p->w->page);
+ else if(but == 2)
+ textset(&p->w->status, rs.r, rs.nr);
+ else if(but == 3)
+ plumbrunestr(&rs, nil);
+ closerunestr(&rs);
+}
+
+static
+void
+submit(Page *p, Formfield *formfield, int subfl)
+{
+ Formfield *f;
+ Form *form;
+ Runestr src, post;
+ Rune *x, *sep, *y, *z;
+
+ form = formfield->form;
+ x = erunestrdup(L"");
+ sep = L"";
+ for(f=form->fields; f!=nil; f=f->next){
+ if(f->ftype == Freset)
+ continue;
+ if((f->ftype==Fradio || f->ftype==Fcheckbox) && !(f->flags&FFchecked))
+ continue;
+ if(f->ftype==Fsubmit && (f!=formfield || !subfl))
+ continue;
+ if(f->value==nil || f->name==nil || runestrcmp(f->name, L"_no_name_submit_")==0)
+ continue;
+
+ z = ucvt(f->value);
+ y = runesmprint("%S%S%S=%S", x, sep, f->name, z);
+ free(z);
+ sep = L"&";
+ free(x);
+ x = y;
+ }
+ p = whichtarget(p, form->target);
+ y = urlcombine(getbase(p), form->action);
+
+ memset(&src, 0, sizeof(Runestr));
+ memset(&post, 0, sizeof(Runestr));
+ if(form->method == HGet){
+ if(y[runestrlen(y)-1] == L'?')
+ sep = L"";
+ else
+ sep = L"?";
+ src.r = runesmprint("%S%S%S",y, sep, x);
+ free(x);
+ free(y);
+ }else{
+ src.r = y;
+ post.r = x;
+ post.nr = runestrlen(x);
+ if(post.nr == 0){
+ free(post.r);
+ post.r = nil;
+ }
+ }
+ src.nr = runestrlen(src.r);
+ pageget(p, &src, &post, form->method, p==&p->w->page);
+ closerunestr(&src);
+ closerunestr(&post);
+}
+
+static
+void
+setradios(Formfield *formfield)
+{
+ Formfield *f;
+
+ for(f=formfield->form->fields; f!=nil; f=f->next)
+ if(f->ftype==Fradio && f!=formfield && runestrcmp(f->name, formfield->name)==0)
+ f->flags &=~FFchecked;
+}
+
+static
+void
+selectmouse(Box *b, Page *p, int but)
+{
+ Formfield *f;
+ Option *o;
+ Menu m;
+ char **item;
+ int i, n;
+
+ f = ((Iformfield *)b->i)->formfield;
+ n = 0;
+ item = nil;
+ for(o=f->options; o!=nil; o=o->next){
+ item = erealloc(item, ++n*sizeof(char *));
+ if(o->display)
+ item[n-1] = smprint("%S", o->display);
+ else
+ item[n-1] = estrdup("--");
+ }
+ if(item == nil)
+ return;
+
+ item[n] = 0;
+ m.item = item;
+ i = menuhit(but, mousectl, &m, nil);
+ if(i >= 0){
+ for(o=f->options; o!=nil; o=o->next, i--){
+ if(i == 0)
+ break;
+ }
+ ((Iformfield *)b->i)->aux = o->display;
+ drawselect(p->b, rectaddpt(rectsubpt(b->r, p->pos), p->r.min), (Iformfield *)b->i);
+ if(f->value != nil)
+ free(f->value);
+ f->value = erunestrdup(o->value);
+ }
+ for(i=0; i< n; i++)
+ free(item[i]);
+// free(item);
+}
+
+static
+void
+mouseform(Box *b, Page *p, int but)
+{
+ Rectangle r, cr;
+ Formfield *f;
+ Text *t;
+
+ f = ((Iformfield *)b->i)->formfield;
+ r = rectaddpt(rectsubpt(b->r, p->pos), p->r.min);
+ if(istextfield(b->i)){
+ cr = p->b->clipr;
+ replclipr(p->b, 0, p->r);
+ t = ((Iformfield *)b->i)->aux;
+ if(p->b != t->b)
+ drawtextfield(p->b, r, (Iformfield *)b->i);
+ textmouse(t, mouse->xy, but);
+ if(f->value)
+ free(f->value);
+ f->value = runesmprint("%.*S", t->rs.nr, t->rs.r);
+ replclipr(p->b, 0, cr);
+ return;
+ }
+
+ if(but != 1)
+ return;
+
+ if(f->ftype==Fselect){
+ selectmouse(b, p, but);
+ return;
+ }
+ if(f->ftype==Fsubmit || f->ftype==Fimage){
+ if(f->ftype == Fsubmit)
+ drawbutton(p->b, r, f, TRUE);
+ while(mouse->buttons == but)
+ readmouse(mousectl);
+ if(f->ftype == Fsubmit)
+ drawbutton(p->b, r, f, FALSE);
+ if(mouse->buttons==0 && ptinrect(mouse->xy, r))
+ submit(p, f, TRUE);
+ return;
+ }
+ if(f->ftype==Fradio || f->ftype==Fcheckbox){
+ if(f->flags&FFchecked){
+ if(f->ftype==Fcheckbox)
+ f->flags &=~FFchecked;
+ }else{
+ f->flags |= FFchecked;
+ }
+ if(f->ftype == Fradio)
+ setradios(f);
+ pageredraw(p);
+ }
+}
+
+static
+void
+keyform(Box *b, Page *p, Rune r)
+{
+ Rectangle cr;
+ Formfield *f;
+ Text *t;
+
+ f = ((Iformfield *)b->i)->formfield;
+ if(r==L'\n' && f->ftype==Ftext){
+ submit(p, f, FALSE);
+ return;
+ }
+ t = ((Iformfield *)b->i)->aux;
+ cr = p->b->clipr;
+ replclipr(p->b, 0, p->r);
+ if(t->b != p->b)
+ drawtextfield(p->b, rectaddpt(rectsubpt(b->r, p->pos), p->r.min), (Iformfield *)b->i);
+ texttype(t, r);
+ if(f->value)
+ free(f->value);
+ f->value = runesmprint("%.*S", t->rs.nr, t->rs.r);
+ replclipr(p->b, 0, cr);
+}
+
+void
+boxinit(Box *b)
+{
+ if(b->i->anchorid)
+ b->mouse = mouselink;
+ /* override mouselink for forms */
+ if(b->i->tag == Iformfieldtag){
+ b->mouse = mouseform;
+ if(istextfield(b->i))
+ b->key = keyform;
+ }
+ switch(b->i->tag){
+ case Itexttag:
+ b->draw = drawtext;
+ break;
+ case Iruletag:
+ b->draw = drawrule;
+ break;
+ case Iimagetag:
+ b->draw = drawimage;
+ break;
+ case Iformfieldtag:
+ b->draw = drawformfield;
+ break;
+ case Itabletag:
+ b->draw = drawtable;
+ break;
+ case Ifloattag:
+ b->draw = drawnull;
+ break;
+ case Ispacertag:
+ b->draw = drawnull;
+ }
+}
+
+Box *
+boxalloc(Line *l, Item *i, Rectangle r)
+{
+ Box *b;
+
+ b = emalloc(sizeof(Box));
+ b->i = i;
+ b->r = r;
+ if(l->boxes == nil)
+ l->boxes = b;
+ else{
+ b->prev = l->lastbox;
+ l->lastbox->next = b;
+ }
+ l->lastbox = b;
+
+ return b;
+}
+
+Box *
+pttobox(Line *l, Point xy)
+{
+ Box *b;
+
+ for(b=l->boxes; b!=nil; b=b->next)
+ if(ptinrect(xy, b->r))
+ return b;
+
+ return nil;
+}
+
+static
+Line *
+tbtoline(Itable *i, Point xy)
+{
+ Tablecell *c;
+
+ for(c=i->table->cells; c!=nil; c=c->next)
+ if(ptinrect(xy, c->lay->r))
+ return linewhich(c->lay, xy);
+
+ return nil;
+}
+
+Line *
+linewhich(Lay *lay, Point xy)
+{
+ Line *l, *t;
+ Box *b;
+
+ t = nil;
+ for(l=lay->lines; l!=nil; l=l->next)
+ if(ptinrect(xy, l->r))
+ break;
+
+ if(l!=nil && l->hastable){
+ b = pttobox(l, xy);
+ if(b!=nil && b->i->tag==Itabletag)
+ t = tbtoline((Itable *)b->i, xy);
+ }
+ return t? t: l;
+}
+
+Box *
+boxwhich(Lay *lay, Point xy)
+{
+ Line *l;
+
+ l = linewhich(lay, xy);
+ if(l)
+ return pttobox(l, xy);
+
+ return nil;
+}
+
+static void justline1(Line *, int);
+
+static
+void
+justlay(Lay *lay, int x)
+{
+ Line *l;
+
+ lay->r.min.x += x;
+ lay->r.max.x += x;
+
+ for(l=lay->lines; l!=nil; l=l->next)
+ justline1(l, x);
+}
+
+static
+void
+justtable(Itable *i, int x)
+{
+ Tablecell *c;
+
+ for(c=i->table->cells; c!=nil; c=c->next)
+ justlay(c->lay, x);
+}
+
+static
+void
+justline1(Line *l, int x)
+{
+ Box *b;
+
+ l->r.min.x += x;
+ l->r.max.x += x;
+ for(b=l->boxes; b!=nil; b=b->next){
+ if(b->i->tag == Itabletag)
+ justtable((Itable *)b->i, x);
+ b->r.min.x += x;
+ b->r.max.x += x;
+ }
+}
+
+static
+void
+justline(Lay *lay, Line *l)
+{
+
+ int w, x;
+
+ w = Dx(l->r);
+ if(w>0 && w<lay->width){
+ x = 0;
+ if(l->state & IFrjust)
+ x = lay->width - w;
+ else if(l->state & IFcjust)
+ x = lay->width/2 - w/2;
+ if(x > 0)
+ justline1(l, x);
+ }
+}
+
+static
+void
+newline(Lay *lay, int state)
+{
+ Line *l, *last;
+ int indent, nl;
+
+ last = lay->lastline;
+ if(lay->laying == TRUE)
+ justline(lay, last);
+
+ lay->r.max.x = max(lay->r.max.x, last->r.max.x);
+ lay->r.max.y = last->r.max.y;
+
+ indent = ((state&IFindentmask)>>IFindentshift) * Tabspace;
+ nl = (state & IFbrksp) ? 1 : 0;
+
+ l = emalloc(sizeof(Line));
+ l->state = state;
+ l->hastext = FALSE;
+ l->hastable = FALSE;
+ l->r.min.x = lay->r.min.x + indent;
+ l->r.min.y = last->r.max.y + font->height*nl;
+ l->r.max = l->r.min;
+ l->prev = last;
+ last->next = l;
+ lay->lastline = l;
+}
+
+
+static
+void
+layitem(Lay *lay, Item *i)
+{
+ Rectangle r;
+ Line *l;
+ Box *b;
+
+ if(i->state&IFbrk || i->state&IFbrksp)
+ newline(lay, i->state);
+ else if(lay->lastline->r.max.x+i->width>lay->xwall && forceitem(i)==FALSE)
+ newline(lay, i->state);
+
+ l = lay->lastline;
+ r = Rect(l->r.max.x, l->r.min.y, l->r.max.x+i->width, l->r.min.y+i->height);
+ l->r.max.x = r.max.x;
+ if(l->r.max.y < r.max.y)
+ l->r.max.y = r.max.y;
+
+ if(i->tag == Ifloattag)
+ i = ((Ifloat *)i)->item;
+ if(i->tag == Itexttag)
+ l->hastext = TRUE;
+ else if(i->tag == Itabletag && lay->laying==TRUE){
+ laytable((Itable *)i, r);
+ l->hastable = TRUE;
+ }
+ b = boxalloc(l, i, r);
+ if(lay->laying)
+ boxinit(b);
+}
+
+static
+void
+linefix(Lay *lay)
+{
+ Line *l;
+
+ for(l=lay->lines; l!=nil; l=l->next){
+ l->r.min.x = lay->r.min.x;
+ l->r.max.x = lay->r.max.x;
+ }
+}
+
+Lay *
+layitems(Item *items, Rectangle r, int laying)
+{
+ Lay *lay;
+ Line *l;
+ Item *i;
+
+ lay = emalloc(sizeof(Lay));
+ lay->r.min = r.min;
+ lay->r.max = r.min;
+ lay->xwall = r.max.x;
+ lay->width = Dx(r);
+ lay->laying = laying;
+ l = emalloc(sizeof(Line));
+ l->r.min = lay->r.min;
+ l->r.max = lay->r.min;
+ l->state = IFbrk;
+ l->boxes = nil;
+ lay->lines = l;
+ lay->lastline = l;
+ lay->font = font;
+
+ for(i=items; i; i=i->next){
+ sizeitem(lay, i);
+ layitem(lay, i);
+ }
+ newline(lay, IFbrk);
+ if(laying)
+ linefix(lay);
+
+ return lay;
+}
+
+void
+laypage(Page *p)
+{
+ settables(p);
+ layfree(p->lay);
+ p->lay = layitems(p->items, Rect(0,0,Dx(p->r),Dy(p->r)), TRUE);
+ p->lay->r.max.y = max(p->lay->r.max.y, Dy(p->r));
+}
+
+static
+void
+drawline(Page *p, Image *im, Line *l)
+{
+ Box *b;
+
+ for(b=l->boxes; b!=nil; b=b->next)
+ b->draw(b, p, im);
+}
+
+void
+laydraw(Page *p, Image *im, Lay *lay)
+{
+ Rectangle r;
+ Line *l;
+
+ r = rectaddpt(p->lay->r, p->pos);
+ for(l=lay->lines; l!=nil; l=l->next){
+ if(rectXrect(r, l->r))
+ drawline(p, im, l);
+ }
+}
+
+static
+void
+laytablefree(Table *t)
+{
+ Tablecell *c;
+
+ for(c=t->cells; c!=nil; c=c->next){
+ layfree(c->lay);
+ c->lay = nil;
+ }
+}
+
+void
+layfree(Lay *lay)
+{
+ Line *l, *nextline;
+ Box *b, *nextbox;
+ void **aux;
+
+ if(lay == nil)
+ return;
+
+ for(l=lay->lines; l!=nil; l=nextline){
+ for(b=l->boxes; b!=nil; b=nextbox){
+ nextbox = b->next;
+ if(b->i->tag==Iformfieldtag && istextfield(b->i)){
+ aux = &((Iformfield *)b->i)->aux;
+ if(*aux){
+ textclose(*aux);
+ free(*aux);
+ }
+ *aux = nil;
+ }else if(b->i->tag == Itabletag)
+ laytablefree(((Itable *)b->i)->table);
+
+ free(b);
+ }
+ nextline = l->next;
+ free(l);
+ }
+ free(lay);
+}
+
+void
+laysnarf(Page *p, Lay *lay, Runestr *rs)
+{
+ Tablecell *c;
+ Itext *i;
+ Font *f;
+ Line *l;
+ Box *b;
+ int q0, q1, n;
+
+ for(l=lay->lines; l!=nil; l=l->next) for(b=l->boxes; b!=nil; b=b->next){
+ if(p->selecting && hasbrk(b->i->state)){
+ rs->r = runerealloc(rs->r, rs->nr+2);
+ rs->r[rs->nr++] = L'\n';
+ rs->r[rs->nr] = L'\0';
+ }
+ if(b->i->tag==Itexttag){
+ i = (Itext *)b->i;
+ f = getfont(i->fnt);
+ if(istextsel(p, b->r, &q0, &q1, i->s, f)){
+ if(q1 == 0)
+ q1 = runestrlen(i->s);
+ n = q1-q0;
+ if(n == 0)
+ n = runestrlen(i->s);
+ rs->r = runerealloc(rs->r, rs->nr+n+2);
+ runemove(rs->r+rs->nr, i->s+q0, n);
+ rs->nr += n;
+ rs->r[rs->nr++] = L' ';
+ rs->r[rs->nr] = L'\0';
+ }
+ }else if(b->i->tag == Itabletag)
+ for(c=((Itable *)b->i)->table->cells; c!=nil; c=c->next)
+ if(c->lay)
+ laysnarf(p, c->lay, rs);
+ }
+}
diff --git a/sys/src/cmd/abaco/main.c b/sys/src/cmd/abaco/main.c
new file mode 100755
index 000000000..b190e9f02
--- /dev/null
+++ b/sys/src/cmd/abaco/main.c
@@ -0,0 +1,433 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <memdraw.h>
+#include <thread.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include <cursor.h>
+#include <frame.h>
+#include <regexp.h>
+#include <plumb.h>
+#include <html.h>
+#include "dat.h"
+#include "fns.h"
+
+enum {
+ WPERCOL = 8,
+};
+void mousethread(void *);
+void keyboardthread(void *);
+void iconinit(void);
+void plumbproc(void*);
+
+Channel *cexit;
+Channel *cplumb;
+Mousectl *mousectl;
+
+char *fontnames[2] = {
+ "/lib/font/bit/lucidasans/unicode.8.font",
+ "/lib/font/bit/lucidasans/passwd.6.font",
+};
+
+int snarffd = -1;
+int mainpid;
+int plumbwebfd;
+int plumbsendfd ;
+char *webmountpt = "/mnt/web";
+char *charset = "iso-8859-1";
+int mainstacksize = STACK;
+
+void readpage(Column *, char *);
+int shutdown(void *, char *);
+
+void
+derror(Display *, char *s)
+{
+ error(s);
+}
+
+static void
+usage(void)
+{
+ fprint(2, "usage: %s [-c ncol] [-m mtpt] [-t charset] [url...]\n",
+ argv0);
+ exits("usage");
+}
+
+void
+threadmain(int argc, char *argv[])
+{
+ Column *c;
+ char buf[256];
+ int i, ncol;
+
+ rfork(RFENVG|RFNAMEG);
+
+ ncol = 1;
+ ARGBEGIN{
+ case 'c':
+ ncol = atoi(EARGF(usage()));
+ if(ncol <= 0)
+ usage();
+ break;
+ case 'm':
+ webmountpt = EARGF(usage());
+ break;
+ case 'p':
+ procstderr++;
+ break;
+ case 't':
+ charset = EARGF(usage());
+ break;
+ default:
+ usage();
+ break;
+ }ARGEND
+
+ snprint(buf, sizeof(buf), "%s/ctl", webmountpt);
+ webctlfd = open(buf, ORDWR);
+ if(webctlfd < 0)
+ sysfatal("can't initialize webfs: %r");
+
+ snarffd = open("/dev/snarf", OREAD|OCEXEC);
+
+ if(initdraw(derror, fontnames[0], "abaco") < 0)
+ sysfatal("can't open display: %r");
+ memimageinit();
+ iconinit();
+ timerinit();
+ initfontpaths();
+
+ cexit = chancreate(sizeof(int), 0);
+ crefresh = chancreate(sizeof(Page *), 0);
+ if(cexit==nil || crefresh==nil)
+ sysfatal("can't create initial channels: %r");
+
+ mousectl = initmouse(nil, screen);
+ if(mousectl == nil)
+ sysfatal("can't initialize mouse: %r");
+ mouse = mousectl;
+ keyboardctl = initkeyboard(nil);
+ if(keyboardctl == nil)
+ sysfatal("can't initialize keyboard: %r");
+ mainpid = getpid();
+ plumbwebfd = plumbopen("web", OREAD|OCEXEC);
+ if(plumbwebfd >= 0){
+ cplumb = chancreate(sizeof(Plumbmsg*), 0);
+ proccreate(plumbproc, nil, STACK);
+ }
+ plumbsendfd = plumbopen("send", OWRITE|OCEXEC);
+
+ rowinit(&row, screen->clipr);
+ for(i=0; i<ncol; i++){
+ c = rowadd(&row, nil, -1);
+ if(c==nil && i==0)
+ error("initializing columns");
+ }
+ c = row.col[row.ncol-1];
+ for(i=0; i<argc; i++)
+ if(i/WPERCOL >= row.ncol)
+ readpage(c, argv[i]);
+ else
+ readpage(row.col[i/WPERCOL], argv[i]);
+ flushimage(display, 1);
+ threadcreate(keyboardthread, nil, STACK);
+ threadcreate(mousethread, nil, STACK);
+
+ threadnotify(shutdown, 1);
+ recvul(cexit);
+ threadexitsall(nil);
+}
+
+void
+readpage(Column *c, char *s)
+{
+ Window *w;
+ Runestr rs;
+
+ w = coladd(c, nil, nil, -1);
+ bytetorunestr(s, &rs);
+ pageget(&w->page, &rs, nil, HGet, TRUE);
+ closerunestr(&rs);
+}
+
+char *oknotes[] = {
+ "delete",
+ "hangup",
+ "kill",
+ "exit",
+ nil
+};
+
+int
+shutdown(void*, char *msg)
+{
+ int i;
+
+ for(i=0; oknotes[i]; i++)
+ if(strncmp(oknotes[i], msg, strlen(oknotes[i])) == 0)
+ threadexitsall(msg);
+ print("abaco: %s\n", msg);
+// abort();
+ return 0;
+}
+
+void
+plumbproc(void *)
+{
+ Plumbmsg *m;
+
+ threadsetname("plumbproc");
+ for(;;){
+ m = plumbrecv(plumbwebfd);
+ if(m == nil)
+ threadexits(nil);
+ sendp(cplumb, m);
+ }
+}
+
+enum { KTimer, KKey, NKALT, };
+
+void
+keyboardthread(void *)
+{
+ Timer *timer;
+ Text *t;
+ Rune r;
+
+ static Alt alts[NKALT+1];
+
+ alts[KTimer].c = nil;
+ alts[KTimer].v = nil;
+ alts[KTimer].op = CHANNOP;
+ alts[KKey].c = keyboardctl->c;
+ alts[KKey].v = &r;
+ alts[KKey].op = CHANRCV;
+ alts[NKALT].op = CHANEND;
+
+ timer = nil;
+ threadsetname("keyboardthread");
+ for(;;){
+ switch(alt(alts)){
+ case KTimer:
+ timerstop(timer);
+ alts[KTimer].c = nil;
+ alts[KTimer].op = CHANNOP;
+ break;
+ case KKey:
+ casekeyboard:
+ typetext = rowwhich(&row, mouse->xy, r, TRUE);
+ t = typetext;
+ if(t!=nil && t->col!=nil &&
+ !(r==Kdown || r==Kleft || r==Kright))
+ /* scrolling doesn't change activecol */
+ activecol = t->col;
+ if(timer != nil)
+ timercancel(timer);
+ if(t!=nil){
+ texttype(t, r);
+ timer = timerstart(500);
+ alts[KTimer].c = timer->c;
+ alts[KTimer].op = CHANRCV;
+ }else{
+ timer = nil;
+ alts[KTimer].c = nil;
+ alts[KTimer].op = CHANNOP;
+ }
+ if(nbrecv(keyboardctl->c, &r) > 0)
+ goto casekeyboard;
+ flushimage(display, 1);
+ break;
+ }
+ }
+}
+
+void
+mousethread(void *)
+{
+ Plumbmsg *pm;
+ Mouse m;
+ Text *t;
+ int but;
+ enum { MResize, MMouse, MPlumb, MRefresh, NMALT };
+ static Alt alts[NMALT+1];
+
+ threadsetname("mousethread");
+ alts[MResize].c = mousectl->resizec;
+ alts[MResize].v = nil;
+ alts[MResize].op = CHANRCV;
+ alts[MMouse].c = mousectl->c;
+ alts[MMouse].v = &mousectl->Mouse;
+ alts[MMouse].op = CHANRCV;
+ alts[MPlumb].c = cplumb;
+ alts[MPlumb].v = &pm;
+ alts[MPlumb].op = CHANRCV;
+ alts[MRefresh].c = crefresh;
+ alts[MRefresh].v = nil;
+ alts[MRefresh].op = CHANRCV;
+ if(cplumb == nil)
+ alts[MPlumb].op = CHANNOP;
+ alts[NMALT].op = CHANEND;
+
+ for(;;){
+ qlock(&row);
+ flushrefresh();
+ qunlock(&row);
+ flushimage(display, 1);
+ switch(alt(alts)){
+ case MResize:
+ if(getwindow(display, Refnone) < 0)
+ error("resized");
+ scrlresize();
+ tmpresize();
+ rowresize(&row, screen->clipr);
+ break;
+ case MPlumb:
+ plumblook(pm);
+ plumbfree(pm);
+ break;
+ case MRefresh:
+ break;
+ case MMouse:
+ m = mousectl->Mouse;
+ if(m.buttons == 0)
+ continue;
+
+ qlock(&row);
+ but = 0;
+ if(m.buttons == 1)
+ but = 1;
+ else if(m.buttons == 2)
+ but = 2;
+ else if(m.buttons == 4)
+ but = 3;
+
+ if(m.buttons & (8|16)){
+ if(m.buttons & 8)
+ but = Kscrolloneup;
+ else
+ but = Kscrollonedown;
+ rowwhich(&row, m.xy, but, TRUE);
+ }else if(but){
+ t = rowwhich(&row, m.xy, but, FALSE);
+ if(t)
+ textmouse(t, m.xy, but);
+ }
+ qunlock(&row);
+ break;
+ }
+ }
+}
+
+Cursor boxcursor = {
+ {-7, -7},
+ {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xF8, 0x1F, 0xF8, 0x1F, 0xF8, 0x1F,
+ 0xF8, 0x1F, 0xF8, 0x1F, 0xF8, 0x1F, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF},
+ {0x00, 0x00, 0x7F, 0xFE, 0x7F, 0xFE, 0x7F, 0xFE,
+ 0x70, 0x0E, 0x70, 0x0E, 0x70, 0x0E, 0x70, 0x0E,
+ 0x70, 0x0E, 0x70, 0x0E, 0x70, 0x0E, 0x70, 0x0E,
+ 0x7F, 0xFE, 0x7F, 0xFE, 0x7F, 0xFE, 0x00, 0x00}
+};
+
+void
+iconinit(void)
+{
+ Rectangle r;
+
+ /* Green */
+ tagcols[BACK] = allocimagemix(display, DPalegreen, DWhite);
+ if(tagcols[BACK] == nil)
+ error("allocimagemix");
+ tagcols[HIGH] = eallocimage(display, Rect(0,0,1,1), screen->chan, 1, DDarkgreen);
+ tagcols[BORD] = eallocimage(display, Rect(0,0,1,1), screen->chan, 1, DMedgreen);
+ tagcols[TEXT] = display->black;
+ tagcols[HTEXT] = display->black;
+
+ /* Grey */
+ textcols[BACK] = display->white;
+ textcols[HIGH] = eallocimage(display, Rect(0,0,1,1), CMAP8,1, 0xCCCCCCFF);
+ textcols[BORD] = display->black;
+ textcols[TEXT] = display->black;
+ textcols[HTEXT] = display->black;
+
+ r = Rect(0, 0, Scrollsize+2, font->height+1);
+ button = eallocimage(display, r, screen->chan, 0, DNofill);
+ draw(button, r, tagcols[BACK], nil, r.min);
+ r.max.x -= 2;
+ border(button, r, 2, tagcols[BORD], ZP);
+
+ r = button->r;
+ colbutton = eallocimage(display, r, screen->chan, 0, 0x00994CFF);
+
+ but2col = eallocimage(display, Rect(0,0,1,2), screen->chan, 1, 0xAA0000FF);
+ but3col = eallocimage(display, Rect(0,0,1,2), screen->chan, 1, 0x444488FF);
+
+ passfont = openfont(display, fontnames[1]);
+ if(passfont == nil)
+ error("openfont");
+}
+
+/*
+ * /dev/snarf updates when the file is closed, so we must open our own
+ * fd here rather than use snarffd
+ */
+
+/*
+ * rio truncates large snarf buffers, so this avoids using the
+ * service if the string is huge
+ */
+
+enum
+{
+ NSnarf = 1000,
+ MAXSNARF = 100*1024,
+};
+
+void
+putsnarf(Runestr *rs)
+{
+ int fd, i, n;
+
+ if(snarffd<0 || rs->nr==0)
+ return;
+ if(rs->nr > MAXSNARF)
+ return;
+ fd = open("/dev/snarf", OWRITE);
+ if(fd < 0)
+ return;
+ for(i=0; i<rs->nr; i+=n){
+ n = rs->nr-i;
+ if(n > NSnarf)
+ n =NSnarf;
+ if(fprint(fd, "%.*S", n, rs->r) < 0)
+ break;
+ }
+ close(fd);
+}
+
+void
+getsnarf(Runestr *rs)
+{
+ int i, n, nb, nulls;
+ char *sn, buf[BUFSIZE];
+
+ if(snarffd < 0)
+ return;
+ sn = nil;
+ i = 0;
+ seek(snarffd, 0, 0);
+ while((n=read(snarffd, buf, sizeof(buf))) > 0){
+ sn = erealloc(sn, i+n+1);
+ memmove(sn+i, buf, n);
+ i += n;
+ sn[i] = 0;
+ }
+ if(i > 0){
+ rs->r = runemalloc(i+1);
+ cvttorunes(sn, i, rs->r, &nb, &rs->nr, &nulls);
+ free(sn);
+ }
+}
diff --git a/sys/src/cmd/abaco/mkfile b/sys/src/cmd/abaco/mkfile
new file mode 100755
index 000000000..5348a41bb
--- /dev/null
+++ b/sys/src/cmd/abaco/mkfile
@@ -0,0 +1,52 @@
+</$objtype/mkfile
+
+BIN=/$objtype/bin
+
+TARG=abaco
+
+OFILES=\
+ cols.$O\
+ exec.$O\
+ html.$O\
+ main.$O\
+ page.$O\
+ rows.$O\
+ scrl.$O\
+ tabs.$O\
+ text.$O\
+ time.$O\
+ urls.$O\
+ util.$O\
+ wind.$O\
+
+HFILES=\
+ dat.h\
+ fns.h\
+ tcs.h\
+
+UPDATE=\
+ mkfile\
+ $HFILES\
+ ${OFILES:%.$O=%.c}\
+
+</sys/src/cmd/mkone
+
+charsets.txt:
+ hget http://www.iana.org/assignments/character-sets | sed 's/ //' > charsets.txt
+
+tcs.h: charsets.awk charsets.txt tcs.txt
+ charsets.awk charsets.txt tcs.txt > tcs.h
+
+syms:V:
+ 8c -a $CFLAGS main.c > syms
+ 8c -aa ????.c >> syms
+
+tgz:V:
+ cd ..
+ tar c abaco | gzip -9 > $home/src/tar/abaco_^`{date -n}^.tgz
+
+dist:
+ 9fs sources
+ mk clean
+ cd ..
+ tar c $TARG | gzip -9 > /n/sources/contrib/fgb/abaco-test.tgz
diff --git a/sys/src/cmd/abaco/page.c b/sys/src/cmd/abaco/page.c
new file mode 100755
index 000000000..6f75f6afb
--- /dev/null
+++ b/sys/src/cmd/abaco/page.c
@@ -0,0 +1,839 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <memdraw.h>
+#include <thread.h>
+#include <cursor.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include <frame.h>
+#include <plumb.h>
+#include <html.h>
+#include "dat.h"
+#include "fns.h"
+
+static void pageload1(Page *, Url *, int);
+
+static
+void
+addchild(Page *p, Page *c)
+{
+ Page *t;
+
+ c->parent = p;
+ c->w = p->w;
+ c->b = p->b;
+ c->col = p->col;
+ c->row = p->row;
+ if(p->child == nil)
+ p->child = c;
+ else{
+ for(t=p->child; t->next!=nil; t=t->next)
+ ;
+ t->next = c;
+ }
+}
+
+static
+void
+loadchilds(Page *p, Kidinfo *k)
+{
+ Runestr rs;
+ Kidinfo *t;
+ Page *c;
+
+ addrefresh(p, "loading frames...");
+ p->kidinfo = k;
+ for(t=k->kidinfos; t!=nil; t=t->next){
+ c = emalloc(sizeof(Page));
+ addchild(p, c);
+ if(t->isframeset){
+ c->url = urldup(p->url);
+ loadchilds(c, t);
+ }else{
+ c->kidinfo = t;
+ /* this check shouldn't be necessary, but... */
+ if(t->src){
+ rs.r = urlcombine(p->url->act.r, t->src);
+ rs.nr = runestrlen(rs.r);
+ pageload1(c, urlalloc(&rs, nil, HGet), FALSE);
+ closerunestr(&rs);
+ }
+ }
+ }
+}
+
+static struct {
+ char *mime;
+ char *filter;
+}filtertab[] = {
+ "image/gif", "gif -t9",
+ "image/jpeg", "jpg -t9",
+ "image/jpg", "jpg -t9",
+ "image/pjpeg", "jpg -t9",
+ "image/png", "png -t9",
+ "image/ppm", "ppm -t9",
+ nil, nil,
+};
+
+char *
+getfilter(Rune *r, int x, int y)
+{
+ char buf[128];
+ int i;
+
+ snprint(buf, sizeof(buf), "%S", r);
+ for(i=0; filtertab[i].mime!=nil; i++)
+ if(cistrncmp(buf, filtertab[i].mime, strlen(filtertab[i].mime)) == 0)
+ break;
+
+ if(filtertab[i].filter == nil)
+ return nil;
+
+ if(x==0 && y==0)
+ return smprint("%s", filtertab[i].filter);
+ if(x!=0 && y!=0)
+ return smprint("%s | resample -x %d -y %d", filtertab[i].filter, x, y);
+ if(x != 0)
+ return smprint("%s | resample -x %d", filtertab[i].filter, x);
+ /* y != 0 */
+ return smprint("%s | resample -y %d", filtertab[i].filter, y);
+}
+
+static Cimage *cimages = nil;
+static QLock cimagelock;
+
+static
+void
+freecimage(Cimage *ci)
+{
+ Cimage *ci1;
+
+ qlock(&cimagelock);
+ if(decref(ci) == 0){
+ if(ci->i)
+ freeimage(ci->i);
+ else if(ci->mi)
+ freememimage(ci->mi);
+ urlfree(ci->url);
+ ci1 = cimages;
+ if(ci1 == ci)
+ cimages = ci->next;
+ else{
+ while(ci1){
+ if(ci1->next == ci){
+ ci1->next = ci->next;
+ break;
+ }
+ ci1 = ci1->next;
+ }
+ }
+ free(ci);
+ }
+ qunlock(&cimagelock);
+}
+
+static
+void
+closeimages(Page *p)
+{
+ int i;
+
+ for(i=0; i<p->ncimage; i++)
+ freecimage(p->cimage[i]);
+ free(p->cimage);
+ p->cimage =nil;
+ p->ncimage = 0;
+}
+
+static
+Cimage *
+loadimg(Rune *src, int x , int y)
+{
+ Channel *sync;
+ Cimage *ci;
+ Runestr rs;
+ Exec *e;
+ char *filter;
+ int fd, p[2], q[2];
+
+ ci = emalloc(sizeof(Cimage));
+ rs. r = src;
+ rs.nr = runestrlen(rs.r);
+ ci->url = urlalloc(&rs, nil, HGet);
+ fd = urlopen(ci->url);
+ if(fd < 0){
+ Err1:
+ return ci;
+ }
+ filter = getfilter(ci->url->ctype.r, x, y);
+ if(filter == nil){
+ werrstr("%S unsupported: %S", ci->url->ctype.r, ci->url->act.r);
+ Err2:
+ close(fd);
+ goto Err1;
+ }
+
+ if(pipe(p)<0 || pipe(q)<0)
+ error("can't create pipe");
+ close(p[0]);
+ p[0] = fd;
+ sync = chancreate(sizeof(ulong), 0);
+ if(sync == nil)
+ error("can't create channel");
+ e = emalloc(sizeof(Exec));
+ e->p[0] = p[0];
+ e->p[1] = p[1];
+ e->q[0] = q[0];
+ e->q[1] = q[1];
+ e->cmd = filter;
+ e->sync = sync;
+ proccreate(execproc, e, STACK);
+ recvul(sync);
+ chanfree(sync);
+ close(p[0]);
+ close(p[1]);
+ close(q[1]);
+
+ ci->mi = readmemimage(q[0]);
+ close(q[0]);
+ if(ci->mi == nil){
+ werrstr("can't read image");
+ goto Err2;
+ }
+ free(filter);
+ return ci;
+}
+
+static
+Cimage *
+findimg(Rune *s)
+{
+ Cimage *ci;
+
+ qlock(&cimagelock);
+ for(ci=cimages; ci!=nil; ci=ci->next)
+ if(runestrcmp(ci->url->src.r, s) == 0)
+ break;
+
+ qunlock(&cimagelock);
+ return ci;
+}
+
+void
+loadimages(Page *p)
+{
+ Cimage *ci;
+ Iimage *i;
+ Rune *src;
+
+ addrefresh(p, "loading images...");
+ reverseimages(&p->doc->images);
+ for(i=p->doc->images; i!=nil; i=i->nextimage){
+ if(p->aborting)
+ break;
+ src = urlcombine(getbase(p), i->imsrc);
+ ci = findimg(src);
+ if(ci == nil){
+ ci = loadimg(src, i->imwidth, i->imheight);
+ qlock(&cimagelock);
+ ci->next = cimages;
+ cimages = ci;
+ qunlock(&cimagelock);
+ }
+ free(src);
+ incref(ci);
+ i->aux = ci;
+ p->cimage = erealloc(p->cimage, ++p->ncimage*sizeof(Cimage *));
+ p->cimage[p->ncimage-1] = ci;
+ p->changed = TRUE;
+ addrefresh(p, "");
+ }
+}
+
+static char *mimetab[] = {
+ "text/html",
+ "application/xhtml",
+ nil,
+};
+
+static
+void
+pageloadproc(void *v)
+{
+ Page *p;
+ char buf[BUFSIZE], *s;
+ long n, l;
+ int fd, i, ctype;
+
+ threadsetname("pageloadproc");
+ rfork(RFFDG);
+
+ p = v;
+ addrefresh(p, "opening: %S...", p->url->src.r);
+ fd = urlopen(p->url);
+ if(fd < 0){
+ addrefresh(p, "%S: %r", p->url->src.r);
+ Err:
+ p->loading = FALSE;
+ return;
+ }
+ if(runestrlen(p->url->ctype.r) == 0) /* assume .html when headers don't say anyting */
+ goto Html;
+
+ snprint(buf, sizeof(buf), "%S", p->url->ctype.r);
+ for(i=0; mimetab[i]!=nil; i++)
+ if(cistrncmp(buf, mimetab[i], strlen(mimetab[i])) == 0)
+ break;
+
+ if(mimetab[i]){
+ Html:
+ ctype = TextHtml;
+ }else if(cistrncmp(buf, "text/", 5) == 0)
+ ctype = TextPlain;
+ else{
+ close(fd);
+ addrefresh(p, "%S: unsupported mime type: '%S'", p->url->act.r, p->url->ctype.r);
+ goto Err;
+ }
+ addrefresh(p, "loading: %S...", p->url->src.r);
+ s = nil;
+ l = 0;
+ while((n=read(fd, buf, sizeof(buf))) > 0){
+ if(p->aborting){
+ if(s){
+ free(s);
+ s = nil;
+ }
+ break;
+ }
+ s = erealloc(s, l+n+1);
+ memmove(s+l, buf, n);
+ l += n;
+ s[l] = '\0';
+ }
+ close(fd);
+ n = l;
+ if(s){
+ s = convert(p->url->ctype, s, &n);
+ p->items = parsehtml((uchar *)s, n, p->url->act.r, ctype, UTF_8, &p->doc);
+ free(s);
+ fixtext(p);
+ if(ctype==TextHtml && p->aborting==FALSE){
+ p->changed = TRUE;
+ addrefresh(p, "");
+ if(p->doc->doctitle){
+ p->title.r = erunestrdup(p->doc->doctitle);
+ p->title.nr = runestrlen(p->title.r);
+ }
+ p->loading = XXX;
+ if(p->doc->kidinfo)
+ loadchilds(p, p->doc->kidinfo);
+ else if(p->doc->images)
+ loadimages(p);
+ }
+ }
+ p->changed = TRUE;
+ p->loading = FALSE;
+ addrefresh(p, "");
+}
+
+static
+void
+pageload1(Page *p, Url *u, int dohist)
+{
+ pageclose(p);
+ p->loading = TRUE;
+ p->url = u;
+ if(dohist)
+ winaddhist(p->w, p->url);
+ proccreate(pageloadproc, p, STACK);
+}
+
+void
+pageload(Page *p, Url *u, int dohist)
+{
+ if(p->parent == nil)
+ textset(&p->w->url, u->src.r, u->src.nr);
+ draw(p->b, p->all, display->white, nil, ZP);
+ pageload1(p, u, dohist);
+}
+
+void
+pageget(Page *p, Runestr *src, Runestr *post, int m, int dohist)
+{
+ pageload(p, urlalloc(src, post, m), dohist);
+}
+
+void
+pageclose(Page *p)
+{
+ Page *c, *nc;
+
+ if(p == selpage)
+ selpage = nil;
+ pageabort(p);
+ closeimages(p);
+ urlfree(p->url);
+ p->url = nil;
+ if(p->doc){
+ freedocinfo(p->doc);
+ p->doc = nil;
+ }
+ layfree(p->lay);
+ p->lay = nil;
+ freeitems(p->items);
+ p->items = nil;
+ for(c=p->child; c!=nil; c=nc){
+ nc = c->next;
+ pageclose(c);
+ free(c);
+ }
+ p->child = nil;
+ closerunestr(&p->title);
+ closerunestr(&p->refresh.rs);
+ p->refresh.t = 0;
+ p->pos = ZP;
+ p->top = ZP;
+ p->bot = ZP;
+ p->loading = p->aborting = FALSE;
+}
+
+int
+pageabort(Page *p)
+{
+ Page *c;
+
+ for(c=p->child; c!=nil; c=c->next)
+ pageabort(c);
+
+ p->aborting = TRUE;
+ while(p->loading)
+ sleep(100);
+
+ p->aborting = FALSE;
+ return TRUE;
+}
+
+
+static Image *tmp;
+
+void
+tmpresize(void)
+{
+ if(tmp)
+ freeimage(tmp);
+
+ tmp = eallocimage(display, Rect(0,0,Dx(screen->r),Dy(screen->r)), screen->chan, 0, -1);
+}
+
+static
+void
+renderchilds(Page *p)
+{
+ Rectangle r;
+ Kidinfo *k;
+ Page *c;
+ int i, j, x, y, *w, *h;
+
+ draw(p->b, p->all, display->white, nil, ZP);
+ r = p->all;
+ y = r.min.y;
+ c = p->child;
+ k = p->kidinfo;
+ frdims(k->rows, k->nrows, Dy(r), &h);
+ frdims(k->cols, k->ncols, Dx(r), &w);
+ for(i=0; i<k->nrows; i++){
+ x = r.min.x;
+ for(j=0; j<k->ncols; j++){
+ if(c->aborting)
+ return;
+ c->b = p->b;
+ c->all = Rect(x,y,x+w[j],y+h[i]);
+ c->w = p->w;
+ pagerender(c);
+ c = c->next;
+ x += w[j];
+ }
+ y += h[i];
+ }
+ free(w);
+ free(h);
+}
+
+static
+void
+pagerender1(Page *p)
+{
+ Rectangle r;
+
+ r = p->all;
+ p->hscrollr = r;
+ p->hscrollr.min.y = r.max.y-Scrollsize;
+ p->vscrollr = r;
+ p->vscrollr.max.x = r.min.x+Scrollsize;
+ r.max.y -= Scrollsize;
+ r.min.x += Scrollsize;
+ p->r = r;
+ p->vscrollr.max.y = r.max.y;
+ p->hscrollr.min.x = r.min.x;
+ laypage(p);
+ pageredraw(p);
+}
+
+void
+pagerender(Page *p)
+{
+ if(p->child && p->loading==FALSE)
+ renderchilds(p);
+ else if(p->doc)
+ pagerender1(p);
+}
+
+void
+pageredraw(Page *p)
+{
+ Rectangle r;
+
+ r = p->lay->r;
+ if(Dx(r)==0 || Dy(r)==0){
+ draw(p->b, p->r, display->white, nil, ZP);
+ return;
+ }
+ if(tmp == nil)
+ tmpresize();
+
+ p->selecting = FALSE;
+ draw(tmp, tmp->r, getbg(p), nil, ZP);
+ laydraw(p, tmp, p->lay);
+ draw(p->b, p->r, tmp, nil, tmp->r.min);
+ r = p->vscrollr;
+ r.min.y = r.max.y;
+ r.max.y += Scrollsize;
+ draw(p->b, r, tagcols[HIGH], nil, ZP);
+ draw(p->b, insetrect(r, 1), tagcols[BACK], nil, ZP);
+ pagescrldraw(p);
+}
+
+static
+void
+pageselect1(Page *p) /* when called, button 1 is down */
+{
+ Point mp, npos, opos;
+ int b, scrled, x, y;
+
+ b = mouse->buttons;
+ mp = mousectl->xy;
+ opos = getpt(p, mp);
+ do{
+ x = y = 0;
+ if(mp.x < p->r.min.x)
+ x -= p->r.min.x-mp.x;
+ else if(mp.x > p->r.max.x)
+ x += mp.x-p->r.max.x;
+ if(mp.y < p->r.min.y)
+ y -= (p->r.min.y-mp.y)*Panspeed;
+ else if(mp.y > p->r.max.y)
+ y += (mp.y-p->r.max.y)*Panspeed;
+
+ scrled = pagescrollxy(p, x, y);
+ npos = getpt(p, mp);
+ if(opos.y < npos.y){
+ p->top = opos;
+ p->bot = npos;
+ }else{
+ p->top = npos;
+ p->bot = opos;
+ }
+ pageredraw(p);
+ if(scrled == TRUE)
+ scrsleep(100);
+ else
+ readmouse(mousectl);
+
+ mp = mousectl->xy;
+ }while(mousectl->buttons == b);
+}
+
+static Rune left1[] = { L'{', L'[', L'(', L'<', L'«', 0 };
+static Rune right1[] = { L'}', L']', L')', L'>', L'»', 0 };
+static Rune left2[] = { L'\'', L'"', L'`', 0 };
+
+static
+Rune *left[] = {
+ left1,
+ left2,
+ nil
+};
+static
+Rune *right[] = {
+ right1,
+ left2,
+ nil
+};
+
+void
+pagedoubleclick(Page *p)
+{
+ Point xy;
+ Line *l;
+ Box *b;
+
+ xy = getpt(p, mouse->xy);
+ l = linewhich(p->lay, xy);
+ if(l==nil || l->hastext==FALSE)
+ return;
+
+ if(xy.x<l->boxes->r.min.x && hasbrk(l->state)){ /* beginning of line? */
+ p->top = l->boxes->r.min;
+ if(l->next && !hasbrk(l->next->state)){
+ for(l=l->next; l->next!=nil; l=l->next)
+ if(hasbrk(l->next->state))
+ break;
+ }
+ p->bot = l->lastbox->r.max;;
+ }else if(xy.x>l->lastbox->r.max.x && hasbrk(l->next->state)){ /* end of line? */
+ p->bot = l->lastbox->r.max;
+ if(!hasbrk(l->state) && l->prev!=nil){
+ for(l=l->prev; l->prev!=nil; l=l->prev)
+ if(hasbrk(l->state))
+ break;
+ }
+ p->top = l->boxes->r.min;
+ }else{
+ b = pttobox(l, xy);
+ if(b!=nil && b->i->tag==Itexttag){
+ p->top = b->r.min;
+ p->bot = b->r.max;
+ }
+ }
+ p->top.y += 2;
+ p->bot.y -= 2;
+ pageredraw(p);
+}
+
+static uint clickmsec;
+
+void
+pageselect(Page *p)
+{
+ int b, x, y;
+
+
+ selpage = p;
+ /*
+ * To have double-clicking and chording, we double-click
+ * immediately if it might make sense.
+ */
+ b = mouse->buttons;
+ if(mouse->msec-clickmsec<500){
+ pagedoubleclick(p);
+ x = mouse->xy.x;
+ y = mouse->xy.y;
+ /* stay here until something interesting happens */
+ do
+ readmouse(mousectl);
+ while(mouse->buttons==b && abs(mouse->xy.x-x)<3 && abs(mouse->xy.y-y)<3);
+ mouse->xy.x = x; /* in case we're calling pageselect1 */
+ mouse->xy.y = y;
+ }
+ if(mousectl->buttons == b)
+ pageselect1(p);
+
+ if(eqpt(p->top, p->bot)){
+ if(mouse->msec-clickmsec<500)
+ pagedoubleclick(p);
+ else
+ clickmsec = mouse->msec;
+ }
+ while(mouse->buttons){
+ mouse->msec = 0;
+ b = mouse->buttons;
+ if(b & 2) /* snarf only */
+ cut(nil, nil, TRUE, FALSE, nil, 0);
+ while(mouse->buttons == b)
+ readmouse(mousectl);
+ }
+}
+
+Page *
+pagewhich(Page *p, Point xy)
+{
+ Page *c;
+
+ if(p->child == nil)
+ return p;
+
+ for(c=p->child; c!=nil; c=c->next)
+ if(ptinrect(xy, c->all))
+ return pagewhich(c, xy);
+
+ return nil;
+}
+
+void
+pagemouse(Page *p, Point xy, int but)
+{
+ Box *b;
+
+ p = pagewhich(p, xy);
+ if(p == nil)
+ return;
+
+ if(pagerefresh(p))
+ return;
+
+ if(p->lay == nil)
+ return;
+
+ if(ptinrect(xy, p->vscrollr)){
+ pagescroll(p, but, FALSE);
+ return;
+ }
+ if(ptinrect(xy, p->hscrollr)){
+ pagescroll(p, but, TRUE);
+ return;
+ }
+ xy = getpt(p, xy);
+ b = boxwhich(p->lay, xy);
+ if(b && b->mouse)
+ b->mouse(b, p, but);
+ else if(but == 1)
+ pageselect(p);
+}
+
+void
+pagetype(Page *p, Rune r, Point xy)
+{
+ Box *b;
+ int x, y;
+
+ p = pagewhich(p, xy);
+ if(p == nil)
+ return;
+
+ if(pagerefresh(p))
+ return;
+
+ if(p->lay == nil)
+ return;
+
+ /* text field? */
+ xy = getpt(p, xy);
+ b = boxwhich(p->lay, xy);
+ if(b && b->key){
+ b->key(b, p, r);
+ return;
+ }
+ /* ^H: same as 'Back' */
+ if(r == 0x08){
+ wingohist(p->w, FALSE);
+ return;
+ }
+
+ x = 0;
+ y = 0;
+ switch(r){
+ case Kleft:
+ x -= Dx(p->r)/2;
+ break;
+ case Kright:
+ x += Dx(p->r)/2;
+ break;
+ case Kdown:
+ case Kscrollonedown:
+ y += Dy(p->r)/2;
+ break;
+ case Kpgdown:
+ y += Dy(p->r);
+ break;
+ case Kup:
+ case Kscrolloneup:
+ y -= Dy(p->r)/2;
+ break;
+ case Kpgup:
+ y -= Dy(p->r);
+ break;
+ case Khome:
+ y -= Dy(p->lay->r); /* force p->pos.y = 0 */
+ break;
+ case Kend:
+ y = Dy(p->lay->r) - Dy(p->r);
+ break;
+ default:
+ return;
+ }
+ if(pagescrollxy(p, x, y))
+ pageredraw(p);
+}
+
+void
+pagesnarf(Page *p)
+{
+ Runestr rs;
+
+ memset(&rs, 0, sizeof(Runestr));
+ laysnarf(p, p->lay, &rs);
+ putsnarf(&rs);
+ closerunestr(&rs);
+}
+
+void
+pagesetrefresh(Page *p)
+{
+ Runestr rs;
+ Rune *s, *q, *t;
+ char *v;
+ int n;
+
+ if(!p->doc || !p->doc->refresh)
+ return;
+
+ s = p->doc->refresh;
+ q = runestrchr(s, L'=');
+ if(q == nil)
+ return;
+ q++;
+ if(!q)
+ return;
+ n = runestrlen(q);
+ if(*q == L'''){
+ q++;
+ n -= 2;
+ }
+ if(n <= 0)
+ return;
+ t = runesmprint("%.*S", n, q);
+ rs.r = urlcombine(getbase(p), t);
+ rs.nr = runestrlen(rs.r);
+ copyrunestr(&p->refresh.rs, &rs);
+ closerunestr(&rs);
+ free(t);
+
+ /* now the time */
+ q = runestrchr(s, L';');
+ if(q){
+ v = smprint("%.*S", (int)(q-s), s);
+ p->refresh.t = atoi(v);
+ free(v);
+ }else
+ p->refresh.t = 1;
+
+ p->refresh.t += time(0);
+}
+
+int
+pagerefresh(Page *p)
+{
+ int t;
+
+ if(!p->refresh.t)
+ return 0;
+
+ t = p->refresh.t - time(0);
+ if(t > 0)
+ return 0;
+
+ pageget(p, &p->refresh.rs, nil, HGet, FALSE);
+ return 1;
+}
diff --git a/sys/src/cmd/abaco/plumbing b/sys/src/cmd/abaco/plumbing
new file mode 100755
index 000000000..140bcbd49
--- /dev/null
+++ b/sys/src/cmd/abaco/plumbing
@@ -0,0 +1,10 @@
+# to update: cp /usr/$user/lib/plumbing /mnt/plumb/rules
+
+editor = acme
+
+include basic
+
+type is text
+data matches 'https?://[a-zA-Z0-9_@\-]+([.:][a-zA-Z0-9_@\-]+)*/?[a-zA-Z0-9_?,%#~&/\-+=]+([:.][a-zA-Z0-9_?,''%#~&/\-+=;]+)*'
+plumb to web
+plumb client window -dx 900 -dy 800 abaco
diff --git a/sys/src/cmd/abaco/rows.c b/sys/src/cmd/abaco/rows.c
new file mode 100755
index 000000000..9f27d9c7d
--- /dev/null
+++ b/sys/src/cmd/abaco/rows.c
@@ -0,0 +1,252 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <memdraw.h>
+#include <thread.h>
+#include <cursor.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include <frame.h>
+#include <plumb.h>
+#include <html.h>
+#include "dat.h"
+#include "fns.h"
+
+void
+rowinit(Row *row, Rectangle r)
+{
+ Rectangle r1;
+ Text *t;
+
+ draw(screen, r, display->white, nil, ZP);
+ row->r = r;
+ row->col = nil;
+ row->ncol = 0;
+ r1 = r;
+ r1.max.y = r1.min.y + font->height;
+ t = &row->tag;
+ textinit(t, screen, r1, font, tagcols);
+ t->what = Rowtag;
+ t->row = row;
+ t->w = nil;
+ t->col = nil;
+ r1.min.y = r1.max.y;
+ r1.max.y += Border;
+ draw(screen, r1, display->black, nil, ZP);
+ textinsert(t, 0, L"Newcol Google Exit ", 19);
+ textsetselect(t, t->rs.nr, t->rs.nr);
+}
+
+Column*
+rowadd(Row *row, Column *c, int x)
+{
+ Rectangle r, r1;
+ Column *d;
+ int i;
+
+ d = nil;
+ r = row->r;
+ r.min.y = row->tag.r.max.y+Border;
+ if(x<r.min.x && row->ncol>0){ /*steal 40% of last column by default */
+ d = row->col[row->ncol-1];
+ x = d->r.min.x + 3*Dx(d->r)/5;
+ }
+ /* look for column we'll land on */
+ for(i=0; i<row->ncol; i++){
+ d = row->col[i];
+ if(x < d->r.max.x)
+ break;
+ }
+ if(row->ncol > 0){
+ if(i < row->ncol)
+ i++; /* new column will go after d */
+ r = d->r;
+ if(Dx(r) < 100)
+ return nil;
+ draw(screen, r, display->white, nil, ZP);
+ r1 = r;
+ r1.max.x = min(x, r.max.x-50);
+ if(Dx(r1) < 50)
+ r1.max.x = r1.min.x+50;
+ colresize(d, r1);
+ r1.min.x = r1.max.x;
+ r1.max.x = r1.min.x+Border;
+ draw(screen, r1, display->black, nil, ZP);
+ r.min.x = r1.max.x;
+ }
+ if(c == nil){
+ c = emalloc(sizeof(Column));
+ colinit(c, r);
+ }else
+ colresize(c, r);
+ c->row = row;
+ c->tag.row = row;
+ row->col = realloc(row->col, (row->ncol+1)*sizeof(Column*));
+ memmove(row->col+i+1, row->col+i, (row->ncol-i)*sizeof(Column*));
+ row->col[i] = c;
+ row->ncol++;
+ clearmouse();
+ return c;
+}
+
+void
+rowresize(Row *row, Rectangle r)
+{
+ int i, dx, odx;
+ Rectangle r1, r2;
+ Column *c;
+
+ dx = Dx(r);
+ odx = Dx(row->r);
+ row->r = r;
+ r1 = r;
+ r1.max.y = r1.min.y + font->height;
+ textresize(&row->tag, screen, r1);
+ r1.min.y = r1.max.y;
+ r1.max.y += Border;
+ draw(screen, r1, display->black, nil, ZP);
+ r.min.y = r1.max.y;
+ r1 = r;
+ r1.max.x = r1.min.x;
+ for(i=0; i<row->ncol; i++){
+ c = row->col[i];
+ r1.min.x = r1.max.x;
+ if(i == row->ncol-1)
+ r1.max.x = r.max.x;
+ else
+ r1.max.x = r1.min.x+Dx(c->r)*dx/odx;
+ if(i > 0){
+ r2 = r1;
+ r2.max.x = r2.min.x+Border;
+ draw(screen, r2, display->black, nil, ZP);
+ r1.min.x = r2.max.x;
+ }
+ colresize(c, r1);
+ }
+}
+
+void
+rowdragcol(Row *row, Column *c, int)
+{
+ Rectangle r;
+ int i, b, x;
+ Point p, op;
+ Column *d;
+
+ clearmouse();
+ setcursor(mousectl, &boxcursor);
+ b = mouse->buttons;
+ op = mouse->xy;
+ while(mouse->buttons == b)
+ readmouse(mousectl);
+ setcursor(mousectl, nil);
+ if(mouse->buttons){
+ while(mouse->buttons)
+ readmouse(mousectl);
+ return;
+ }
+
+ for(i=0; i<row->ncol; i++)
+ if(row->col[i] == c)
+ goto Found;
+ error("can't find column");
+
+ Found:
+ if(i == 0)
+ return;
+ p = mouse->xy;
+ if((abs(p.x-op.x)<5 && abs(p.y-op.y)<5))
+ return;
+ if((i>0 && p.x<row->col[i-1]->r.min.x) || (i<row->ncol-1 && p.x>c->r.max.x)){
+ /* shuffle */
+ x = c->r.min.x;
+ rowclose(row, c, FALSE);
+ if(rowadd(row, c, p.x) == nil) /* whoops! */
+ if(rowadd(row, c, x) == nil) /* WHOOPS! */
+ if(rowadd(row, c, -1)==nil){ /* shit! */
+ rowclose(row, c, TRUE);
+ return;
+ }
+ colmousebut(c);
+ return;
+ }
+ d = row->col[i-1];
+ if(p.x < d->r.min.x+80+Scrollsize)
+ p.x = d->r.min.x+80+Scrollsize;
+ if(p.x > c->r.max.x-80-Scrollsize)
+ p.x = c->r.max.x-80-Scrollsize;
+ r = d->r;
+ r.max.x = c->r.max.x;
+ draw(screen, r, display->white, nil, ZP);
+ r.max.x = p.x;
+ colresize(d, r);
+ r = c->r;
+ r.min.x = p.x;
+ r.max.x = r.min.x;
+ r.max.x += Border;
+ draw(screen, r, display->black, nil, ZP);
+ r.min.x = r.max.x;
+ r.max.x = c->r.max.x;
+ colresize(c, r);
+ colmousebut(c);
+}
+
+void
+rowclose(Row *row, Column *c, int dofree)
+{
+ Rectangle r;
+ int i;
+
+ for(i=0; i<row->ncol; i++)
+ if(row->col[i] == c)
+ goto Found;
+ error("can't find column");
+ Found:
+ r = c->r;
+ if(dofree)
+ colcloseall(c);
+ memmove(row->col+i, row->col+i+1, (row->ncol-i)*sizeof(Column*));
+ row->ncol--;
+ row->col = realloc(row->col, row->ncol*sizeof(Column*));
+ if(row->ncol == 0){
+ draw(screen, r, display->white, nil, ZP);
+ return;
+ }
+ if(i == row->ncol){ /* extend last column right */
+ c = row->col[i-1];
+ r.min.x = c->r.min.x;
+ r.max.x = row->r.max.x;
+ }else{ /* extend next window left */
+ c = row->col[i];
+ r.max.x = c->r.max.x;
+ }
+ draw(screen, r, display->white, nil, ZP);
+ colresize(c, r);
+}
+
+Column*
+rowwhichcol(Row *row, Point p)
+{
+ int i;
+ Column *c;
+
+ for(i=0; i<row->ncol; i++){
+ c = row->col[i];
+ if(ptinrect(p, c->r))
+ return c;
+ }
+ return nil;
+}
+
+Text*
+rowwhich(Row *row, Point p, Rune r, int key)
+{
+ Column *c;
+
+ if(ptinrect(p, row->tag.all))
+ return &row->tag;
+ c = rowwhichcol(row, p);
+ if(c)
+ return colwhich(c, p, r, key);
+ return nil;
+}
diff --git a/sys/src/cmd/abaco/scrl.c b/sys/src/cmd/abaco/scrl.c
new file mode 100755
index 000000000..3b7072f97
--- /dev/null
+++ b/sys/src/cmd/abaco/scrl.c
@@ -0,0 +1,317 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <memdraw.h>
+#include <thread.h>
+#include <cursor.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include <frame.h>
+#include <plumb.h>
+#include <html.h>
+#include "dat.h"
+#include "fns.h"
+
+static Image *vscrtmp;
+static Image *hscrtmp;
+
+void
+scrlresize(void)
+{
+ freeimage(vscrtmp);
+ freeimage(hscrtmp);
+ vscrtmp = eallocimage(display, Rect(0, 0, 32, screen->r.max.y), screen->chan, 0, DNofill);
+ hscrtmp = eallocimage(display, Rect(0, 0, screen->r.max.x, 32), screen->chan, 0, DNofill);
+}
+
+static
+Rectangle
+scrpos(Rectangle r, uint p0, uint p1, uint tot)
+{
+ Rectangle q;
+ int h;
+
+ q = r;
+ h = q.max.y-q.min.y;
+ if(tot == 0)
+ return q;
+ if(tot > 1024*1024){
+ tot>>=10;
+ p0>>=10;
+ p1>>=10;
+ }
+ if(p0 > 0)
+ q.min.y += h*p0/tot;
+ if(p1 < tot)
+ q.max.y -= h*(tot-p1)/tot;
+ if(q.max.y < q.min.y+2){
+ if(q.min.y+2 <= r.max.y)
+ q.max.y = q.min.y+2;
+ else
+ q.min.y = q.max.y-2;
+ }
+ return q;
+}
+
+void
+textscrdraw(Text *t)
+{
+ Rectangle r, r1, r2;
+ Image *b;
+
+ if(vscrtmp == nil)
+ scrlresize();
+ r = t->scrollr;
+ b = vscrtmp;
+ r1 = r;
+ r1.min.x = 0;
+ r1.max.x = Dx(r);
+ r2 = scrpos(r1, t->org, t->org+t->nchars, t->rs.nr);
+ if(!eqrect(r2, t->lastsr)){
+ t->lastsr = r2;
+ draw(b, r1, t->cols[BORD], nil, ZP);
+ draw(b, r2, t->cols[BACK], nil, ZP);
+ r2.min.x = r2.max.x-1;
+ draw(b, r2, t->cols[BORD], nil, ZP);
+ draw(t->b, r, b, nil, Pt(0, r1.min.y));
+/*flushimage(display, 1);/*BUG?*/
+ }
+}
+
+void
+scrsleep(uint dt)
+{
+ Timer *timer;
+ static Alt alts[3];
+
+ timer = timerstart(dt);
+ alts[0].c = timer->c;
+ alts[0].v = nil;
+ alts[0].op = CHANRCV;
+ alts[1].c = mousectl->c;
+ alts[1].v = &mousectl->Mouse;
+ alts[1].op = CHANRCV;
+ alts[2].op = CHANEND;
+ for(;;)
+ switch(alt(alts)){
+ case 0:
+ timerstop(timer);
+ return;
+ case 1:
+ timercancel(timer);
+ return;
+ }
+}
+
+void
+textscroll(Text *t, int but)
+{
+ uint p0, oldp0;
+ Rectangle s;
+ int x, y, my, h, first;
+
+ s = insetrect(t->scrollr, 1);
+ h = s.max.y-s.min.y;
+ x = (s.min.x+s.max.x)/2;
+ oldp0 = ~0;
+ first = TRUE;
+ do{
+ flushimage(display, 1);
+ my = mouse->xy.y;
+ if(my < s.min.y)
+ my = s.min.y;
+ if(my >= s.max.y)
+ my = s.max.y;
+ if(!eqpt(mouse->xy, Pt(x, my))){
+ moveto(mousectl, Pt(x, my));
+ readmouse(mousectl); /* absorb event generated by moveto() */
+ }
+ if(but == 2){
+ y = my;
+ p0 = (vlong)t->rs.nr*(y-s.min.y)/h;
+ if(p0 >= t->q1)
+ p0 = textbacknl(t, p0, 2);
+ if(oldp0 != p0)
+ textsetorigin(t, p0, FALSE);
+ oldp0 = p0;
+ readmouse(mousectl);
+ continue;
+ }
+ if(but == 1)
+ p0 = textbacknl(t, t->org, (my-s.min.y)/t->font->height);
+ else
+ p0 = t->org+frcharofpt(t, Pt(s.max.x, my));
+ if(oldp0 != p0)
+ textsetorigin(t, p0, TRUE);
+ oldp0 = p0;
+ /* debounce */
+ if(first){
+ flushimage(display, 1);
+ sleep(200);
+ nbrecv(mousectl->c, &mousectl->Mouse);
+ first = FALSE;
+ }
+ scrsleep(80);
+ }while(mouse->buttons & (1<<(but-1)));
+ while(mouse->buttons)
+ readmouse(mousectl);
+}
+
+enum
+{
+ Scrbord = 1,
+};
+
+void
+pagescrldraw(Page *p)
+{
+ Rectangle r1;
+ int t, d, l, h;
+
+ if(vscrtmp == nil)
+ scrlresize();
+
+ r1 = Rect(0,0,Dx(p->hscrollr), Dy(p->hscrollr));
+ d = Dx(r1);
+ t = Dx(p->lay->r);
+ l = muldiv(p->pos.x, d, t);
+ h = muldiv(p->pos.x+d, d, t);
+ draw(hscrtmp, r1, tagcols[HIGH], nil, ZP);
+ r1.max.x = r1.min.x+h;
+ r1.min.x += l;
+ r1.min.y += Scrbord;
+ r1.max.y -= Scrbord;
+ draw(hscrtmp, r1, tagcols[BACK], nil, ZP);
+
+ r1 = Rect(0,0,Dx(p->vscrollr), Dy(p->vscrollr));
+ d = Dy(r1);
+ t = Dy(p->lay->r);
+ l = muldiv(p->pos.y, d, t);
+ h = muldiv(p->pos.y+d, d, t);
+ draw(vscrtmp, r1, tagcols[HIGH], nil, ZP);
+ r1.max.y = r1.min.y+h;
+ r1.min.y += l;
+ r1.max.x -= Scrbord;
+ r1.min.x += Scrbord;
+ draw(vscrtmp, r1, tagcols[BACK], nil, ZP);
+
+ draw(screen, p->vscrollr, vscrtmp, nil, vscrtmp->r.min);
+ draw(screen, p->hscrollr, hscrtmp, nil, hscrtmp->r.min);
+}
+
+void
+pagescroll(Page *p, int but, int horizontal)
+{
+ uint oldp0, p0;
+ Rectangle s;
+ Point mxy;
+ int i, m, om, first, d, size;
+ int smin, smax, ss, *pos;
+
+ if(horizontal){
+ s = insetrect(p->hscrollr, 1);
+ ss = s.max.x - s.min.x;
+ i = (s.min.y+s.max.y)/2;
+ d = Dx(p->r);
+ size = Dx(p->lay->r);
+ p0 = p->pos.x;
+ pos = &p->pos.x;
+ smin = s.min.x;
+ smax = s.max.x;
+ om = mouse->xy.x;
+ }else{
+ s = insetrect(p->vscrollr, 1);
+ ss = s.max.y-s.min.y;
+ i = (s.min.x+s.max.x)/2;
+ d = Dy(p->r);
+ size = Dy(p->lay->r);
+ p0 = p->pos.y;
+ pos = &p->pos.y;
+ smin = s.min.y;
+ smax = s.max.y;
+ om = mouse->xy.y;
+ }
+ oldp0 = ~0;
+ first = TRUE;
+ do{
+ flushimage(display, 1);
+ if(horizontal)
+ m = mouse->xy.x;
+ else
+ m = mouse->xy.y;
+
+ if(m > om)
+ m += (m-om)*Panspeed;
+ else if(m < om)
+ m -= (om-m)*Panspeed;
+
+ if(m < smin)
+ m = smin;
+ if(m > smax)
+ m = smax;
+
+ om = m;
+ if(horizontal)
+ mxy = Pt(m, i);
+ else
+ mxy = Pt(i, m);
+ if(!eqpt(mouse->xy, mxy)){
+ moveto(mousectl, mxy);
+ readmouse(mousectl); /* absorb event generated by moveto() */
+ }
+ if(but == 2){
+ p0 = muldiv(m-smin, size, ss);
+ p0 = max(p0, 0);
+ p0 = min(p0,size-d);
+ if(oldp0 != p0){
+ *pos = p0;
+ pageredraw(p);
+ }
+ oldp0 = p0;
+ readmouse(mousectl);
+ continue;
+ }
+ if(but == 1)
+ p0 -= (d/2);
+ else
+ p0 += d/2;
+ p0 = min(p0, size-d);
+ p0 = max(p0, 0);
+ if(oldp0 != p0){
+ *pos = p0;
+ pageredraw(p);
+ }
+ oldp0 = p0;
+ /* debounce */
+ if(first){
+ flushimage(display, 1);
+ sleep(200);
+ nbrecv(mousectl->c, &mousectl->Mouse);
+ first = FALSE;
+ }
+ scrsleep(80);
+ }while(mouse->buttons & (1<<(but-1)));
+ while(mouse->buttons)
+ readmouse(mousectl);
+}
+
+int
+pagescrollxy(Page *p, int x, int y)
+{
+ int scrled;
+
+ scrled = FALSE;
+ if(x != 0){
+ p->pos.x += x;
+ p->pos.x = max(p->pos.x, 0);
+ p->pos.x = min(p->pos.x, Dx(p->lay->r)-Dx(p->r));
+ scrled =TRUE;
+ }
+ if(y != 0){
+ p->pos.y += y;
+ p->pos.y = max(p->pos.y, 0);
+ p->pos.y = min(p->pos.y, Dy(p->lay->r)-Dy(p->r));
+ scrled =TRUE;
+ }
+ return scrled;
+}
diff --git a/sys/src/cmd/abaco/tabs.c b/sys/src/cmd/abaco/tabs.c
new file mode 100755
index 000000000..eda3f5132
--- /dev/null
+++ b/sys/src/cmd/abaco/tabs.c
@@ -0,0 +1,332 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <memdraw.h>
+#include <thread.h>
+#include <cursor.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include <frame.h>
+#include <plumb.h>
+#include <html.h>
+#include "dat.h"
+#include "fns.h"
+
+void
+drawtable(Box *b, Page *p, Image *im)
+{
+ Rectangle r, cr;
+ Tablecell *c;
+ Table *t;
+
+ t = ((Itable *)b->i)->table;
+ r = rectsubpt(b->r, p->pos);
+ draw(im, r, getcolor(t->background.color), nil, ZP);
+ if(t->border)
+ border(im, r, t->border, display->black, ZP);
+ for(c=t->cells; c!=nil; c=c->next){
+ cr = rectsubpt(c->lay->r, p->pos);
+ if(c->background.color != t->background.color)
+ draw(im, cr, getcolor(c->background.color), nil, ZP);
+ if(t->border)
+ border(im, cr, t->border, display->black, ZP);
+ laydraw(p, im, c->lay);
+ }
+}
+
+enum
+{
+ Tablemax = 2000,
+ Ttoplevel = 1<<0,
+
+};
+
+static
+void
+settable(Table *t)
+{
+ Tablecell *c;
+ Lay *lay;
+
+ for(c=t->cells; c!=nil; c=c->next){
+ lay = layitems(c->content, Rect(0,0,0,0), FALSE);
+ c->minw = Dx(lay->r);
+ layfree(lay);
+ if(dimenkind(c->wspec) == Dnone){
+ lay = layitems(c->content, Rect(0,0,Tablemax,0), FALSE);
+ c->maxw = Dx(lay->r);
+ layfree(lay);
+ }
+ }
+}
+
+void
+settables(Page *p)
+{
+ Table *t;
+ Item *i;
+
+ if(p->doc==nil)
+ return;
+ for(i=p->items; i!=nil; i=i->next)
+ if(i->tag == Itabletag)
+ ((Itable *)i)->table->flags |= Ttoplevel;
+
+ for(t=p->doc->tables; t!=nil; t=t->next)
+ settable(t);
+}
+
+static
+int
+cellwidth(Table *t, Tablecell *c, int sep)
+{
+ int w, i, n;
+
+ n = c->colspan;
+ if(n == 1)
+ return t->cols[c->col].width;
+ if(n == 0)
+ n = t->ncol - c->col;
+
+ w = t->cellspacing*(n-1) + n*sep;
+ for(i=c->col; i<c->col+n; i++)
+ w += t->cols[i].width;
+
+ return w;
+}
+
+static
+int
+cellheight(Table *t, Tablecell *c, int sep)
+{
+ int h, i, n;
+
+ n = c->rowspan;
+ if(n == 1)
+ return t->rows[c->row].height;
+ if(n == 0)
+ n = t->nrow - c->row;
+
+ h = t->cellspacing*(n-1) + n*sep;
+ for(i=c->row; i<c->row+n; i++)
+ h += t->rows[i].height;
+
+ return h;
+}
+
+static
+int
+getwidth(int *w, int n)
+{
+ int i, tot;
+
+ tot = 0;
+ for(i=0; i<n; i++)
+ tot += w[i];
+
+ return tot;
+}
+
+static
+void
+fixcols(Table *t, int *width, int sep, int domax)
+{
+ Tablecell *c;
+ int w, aw, i, d, n, rem;
+
+
+ for(c=t->cells; c!=nil; c=c->next){
+ if(c->colspan == 1)
+ continue;
+
+ n = c->colspan;
+ if(n == 0)
+ n = t->ncol - c->col;
+
+ w = domax ? c->maxw : c->minw;
+ w -= t->cellspacing*(n-1) + n*sep;
+
+ aw = 0;
+ for(i=c->col; i<c->col+n; i++)
+ aw += width[i];
+
+ rem = w-aw;
+ if(rem <= 0)
+ continue;
+
+ for(i=c->col; i<c->col+n; i++){
+ if(aw > 0){
+ d = width[i]*100/aw;
+ d = d*rem/100;
+ }else
+ d = rem/n;
+ width[i] += d;
+ }
+ }
+}
+
+static
+int
+tablewidth(Table *t, int tw, int sep)
+{
+ Tablecell *c;
+ int i, w, tmin, tmax, d;
+ int *maxw, *minw;
+ int totw;
+
+ maxw = emalloc(sizeof(int)*t->ncol);
+ minw = emalloc(sizeof(int)*t->ncol);
+ for(c=t->cells; c!=nil; c=c->next){
+ if(dimenkind(c->wspec) != Dnone){
+ d = c->minw;
+ c->minw = c->maxw = max(dimwidth(c->wspec, tw), c->minw);
+ c->minw = d;
+ }
+ if(c->colspan != 1)
+ continue;
+ maxw[c->col] = max(maxw[c->col], c->maxw);
+ minw[c->col] = max(minw[c->col], c->minw);
+ }
+ totw = 0;
+ fixcols(t, maxw, sep, TRUE);
+ tmax = getwidth(maxw, t->ncol);
+ if(tmax <= tw){
+ d = 0;
+ if(tw>tmax && dimenkind(t->width)!=Dnone && t->availw!=Tablemax)
+ d = (tw-tmax)/t->ncol;
+ for(i=0; i<t->ncol; i++){
+ t->cols[i].width = maxw[i] + d;
+ totw += t->cols[i].width;
+ }
+ }else{
+ fixcols(t, minw, sep, FALSE);
+ tmin = getwidth(minw, t->ncol);
+ w = tw - tmin;
+ d = tmax - tmin;
+ for(i=0; i<t->ncol; i++){
+ if(w<=0 || d<=0)
+ t->cols[i].width = minw[i];
+ else
+ t->cols[i].width = minw[i] + (maxw[i] - minw[i])*w/d;
+ totw += t->cols[i].width;
+ }
+ }
+ free(minw);
+ free(maxw);
+
+ return totw;
+}
+
+static
+void
+fixrows(Table *t, int sep)
+{
+ Tablecell *c;
+ Lay *lay;
+ int h, ah, i, d, n, rem;
+
+ for(c=t->cells; c!=nil; c=c->next){
+ if(c->rowspan == 1)
+ continue;
+ n = c->rowspan;
+ if(n==0 || c->row+n>t->nrow)
+ n = t->nrow - c->row;
+
+ lay = layitems(c->content, Rect(0,0,cellwidth(t, c, sep),0), FALSE);
+ h = max(Dy(lay->r), c->hspec);
+ layfree(lay);
+ h -= t->cellspacing*(n-1) + n*sep;
+ ah = 0;
+ for(i=c->row; i<c->row+n; i++)
+ ah += t->rows[i].height;
+
+ rem = h-ah;
+ if(rem <= 0)
+ continue;
+
+ for(i=c->row; i<c->row+n; i++){
+ if(ah > 0){
+ d = t->rows[i].height*100/ah;
+ d = d*rem/100;
+ }else
+ d = rem/n;
+
+ t->rows[i].height += d;
+ }
+ }
+}
+
+static
+int
+tableheight(Table *t, int sep)
+{
+ Tablecell *c;
+ Lay *lay;
+ int i, h, toth;
+
+ for(i=0; i<t->nrow; i++){
+ h = 0;
+ for(c=t->rows[i].cells; c!=nil; c=c->nextinrow){
+ if(c->rowspan != 1)
+ continue;
+ lay = layitems(c->content, Rect(0, 0, cellwidth(t, c, sep), 0), FALSE);
+ h = max(h, max(Dy(lay->r), c->hspec));
+ layfree(lay);
+ }
+ t->rows[i].height = h;
+ }
+ fixrows(t, sep);
+ toth = 0;
+ for(i=0; i<t->nrow; i++)
+ toth += t->rows[i].height;
+
+ return toth;
+}
+
+void
+tablesize(Table *t, int availw)
+{
+ int w, sep, hsep, vsep;
+
+ t->availw = availw;
+ sep = 2*(t->border+t->cellpadding);
+ hsep = t->cellspacing*(t->ncol+1) + 2*t->border + t->ncol*sep;
+ vsep = t->cellspacing*(t->nrow+1) + 2*t->border + t->nrow*sep;
+ w = dimwidth(t->width, availw);
+ w -= hsep;
+ w = w>0 ? w : 0;
+ t->totw = tablewidth(t, w, sep);
+ t->totw += hsep;
+ t->toth = tableheight(t, sep);
+ t->toth += vsep;
+}
+
+void
+laytable(Itable *it, Rectangle r)
+{
+ Rectangle cr;
+ Tablecell *c;
+ Table *t;
+ int x, y, h, w;
+ int sep, i;
+
+ t = it->table;
+
+ sep = (t->cellpadding+t->border) * 2;
+ r = insetrect(r, t->cellspacing+t->border);
+ for(c=t->cells; c!=nil; c=c->next){
+ w = cellwidth(t, c, sep);
+ h = cellheight(t, c, sep);
+ x = r.min.x;
+ if(c->col > 0)
+ for(i=0; i<c->col; i++)
+ x += t->cols[i].width + sep + t->cellspacing;
+ y = r.min.y;
+ if(c->row > 0)
+ for(i=0;i <c->row; i++)
+ y += t->rows[i].height + sep + t->cellspacing;
+ cr = Rect(x, y, x+w+sep, y+h+sep);
+ c->lay = layitems(c->content, insetrect(cr, sep/2), TRUE);
+ c->lay->r = cr;
+ }
+}
diff --git a/sys/src/cmd/abaco/tcs.h b/sys/src/cmd/abaco/tcs.h
new file mode 100755
index 000000000..d0533e300
--- /dev/null
+++ b/sys/src/cmd/abaco/tcs.h
@@ -0,0 +1,167 @@
+"iso_8859-1:1987", "8859-1",
+"iso-ir-100", "8859-1",
+"iso_8859-1", "8859-1",
+"iso-8859-1", "8859-1",
+"latin1", "8859-1",
+"l1", "8859-1",
+"ibm819", "8859-1",
+"cp819", "8859-1",
+"csisolatin1", "8859-1",
+"iso_8859-2:1987", "8859-2",
+"iso-ir-101", "8859-2",
+"iso_8859-2", "8859-2",
+"iso-8859-2", "8859-2",
+"latin2", "8859-2",
+"l2", "8859-2",
+"csisolatin2", "8859-2",
+"iso_8859-3:1988", "8859-3",
+"iso-ir-109", "8859-3",
+"iso_8859-3", "8859-3",
+"iso-8859-3", "8859-3",
+"latin3", "8859-3",
+"l3", "8859-3",
+"csisolatin3", "8859-3",
+"iso_8859-4:1988", "8859-4",
+"iso-ir-110", "8859-4",
+"iso_8859-4", "8859-4",
+"iso-8859-4", "8859-4",
+"latin4", "8859-4",
+"l4", "8859-4",
+"csisolatin4", "8859-4",
+"iso_8859-5:1988", "8859-5",
+"iso-ir-144", "8859-5",
+"iso_8859-5", "8859-5",
+"iso-8859-5", "8859-5",
+"cyrillic", "8859-5",
+"csisolatincyrillic", "8859-5",
+"iso_8859-6:1987", "8859-6",
+"iso-ir-127", "8859-6",
+"iso_8859-6", "8859-6",
+"iso-8859-6", "8859-6",
+"ecma-114", "8859-6",
+"asmo-708", "8859-6",
+"arabic", "8859-6",
+"csisolatinarabic", "8859-6",
+"iso_8859-7:1987", "8859-7",
+"iso-ir-126", "8859-7",
+"iso_8859-7", "8859-7",
+"iso-8859-7", "8859-7",
+"elot_928", "8859-7",
+"ecma-118", "8859-7",
+"greek", "8859-7",
+"greek8", "8859-7",
+"csisolatingreek", "8859-7",
+"iso_8859-8:1988", "8859-8",
+"iso-ir-138", "8859-8",
+"iso_8859-8", "8859-8",
+"iso-8859-8", "8859-8",
+"hebrew", "8859-8",
+"csisolatinhebrew", "8859-8",
+"iso_8859-9:1989", "8859-9",
+"iso-ir-148", "8859-9",
+"iso_8859-9", "8859-9",
+"iso-8859-9", "8859-9",
+"latin5", "8859-9",
+"l5", "8859-9",
+"csisolatin5", "8859-9",
+"iso-8859-15", "8859-15",
+"iso_8859-15", "8859-15",
+"latin-9", "8859-15",
+"ansi_x3.4-1968", "ascii",
+"iso-ir-6", "ascii",
+"ansi_x3.4-1986", "ascii",
+"iso_646.irv:1991", "ascii",
+"ascii", "ascii",
+"iso646-us", "ascii",
+"us-ascii", "ascii",
+"us", "ascii",
+"ibm367", "ascii",
+"cp367", "ascii",
+"csascii", "ascii",
+"big5", "big5",
+"csbig5", "big5",
+"ibm437", "ibm437",
+"cp437", "ibm437",
+"437", "ibm437",
+"cspc8codepage437", "ibm437",
+"ibm850", "ibm850",
+"cp850", "ibm850",
+"850", "ibm850",
+"cspc850multilingual", "ibm850",
+"ibm852", "ibm852",
+"cp852", "ibm852",
+"852", "ibm852",
+"cspcp852", "ibm852",
+"ibm855", "ibm855",
+"cp855", "ibm855",
+"855", "ibm855",
+"csibm855", "ibm855",
+"ibm857", "ibm857",
+"cp857", "ibm857",
+"857", "ibm857",
+"csibm857", "ibm857",
+"ibm862", "ibm862",
+"cp862", "ibm862",
+"862", "ibm862",
+"cspc862latinhebrew", "ibm862",
+"ibm866", "ibm866",
+"cp866", "ibm866",
+"866", "ibm866",
+"csibm866", "ibm866",
+"windows-1250", "windows-1250",
+"windows-1251", "windows-1251",
+"windows-1252", "windows-1252",
+"windows-1253", "windows-1253",
+"windows-1254", "windows-1254",
+"windows-1255", "windows-1255",
+"windows-1256", "windows-1256",
+"windows-1257", "windows-1257",
+"windows-1258", "windows-1258",
+"ks_c_5601-1987", "euc-k",
+"iso-ir-149", "euc-k",
+"ks_c_5601-1989", "euc-k",
+"ksc_5601", "euc-k",
+"korean", "euc-k",
+"csksc56011987", "euc-k",
+"euc-kr", "euc-k",
+"cseuckr", "euc-k",
+"gb2312", "gb",
+"csgb2312", "gb",
+"gb_2312-80", "gb",
+"iso-ir-58", "gb",
+"chinese", "gb",
+"csiso58gb231280", "gb",
+"iso-2022-jp", "jis-kanji",
+"csiso2022jp", "jis-kanji",
+"koi8-r", "koi8",
+"cskoi8r", "koi8",
+"macintosh", "macrom",
+"mac", "macrom",
+"csmacintosh", "macrom",
+"ibm865", "msdos2",
+"cp865", "msdos2",
+"865", "msdos2",
+"csibm865", "msdos2",
+"shift_jis", "ms-kanji",
+"ms_kanji", "ms-kanji",
+"csshiftjis", "ms-kanji",
+"sen_850200_b", "sf1",
+"iso-ir-10", "sf1",
+"fi", "sf1",
+"iso646-fi", "sf1",
+"iso646-se", "sf1",
+"se", "sf1",
+"csiso10swedish", "sf1",
+"sen_850200_c", "sf2",
+"iso-ir-11", "sf2",
+"iso646-se2", "sf2",
+"se2", "sf2",
+"csiso11swedishfornames", "sf2",
+"tis-620", "tis",
+"extended_unix_code_packed_format_for_japanese", "ujis",
+"cseucpkdfmtjapanese", "ujis",
+"euc-jp", "ujis",
+"iso-10646-utf-1", "utf1",
+"csiso10646utf1", "utf1",
+"viscii", "viscii",
+"csviscii", "viscii",
diff --git a/sys/src/cmd/abaco/tcs.txt b/sys/src/cmd/abaco/tcs.txt
new file mode 100755
index 000000000..8aa98345a
--- /dev/null
+++ b/sys/src/cmd/abaco/tcs.txt
@@ -0,0 +1,58 @@
+iso_8859-1 8859-1
+iso_8859-2 8859-2
+iso_8859-3 8859-3
+iso_8859-4 8859-4
+iso_8859-5 8859-5
+iso_8859-6 8859-6
+iso_8859-7 8859-7
+iso_8859-8 8859-8
+iso_8859-9 8859-9
+iso_8859-10 8859-10
+iso_8859-15 8859-15
+ascii ascii
+ atari
+ av
+big5 big5
+ibm437 ibm437
+ ibm720
+ ibm737
+ibm735 ibm775
+ibm850 ibm850
+ibm852 ibm852
+ibm855 ibm855
+ibm857 ibm857
+ ibm858
+ibm862 ibm862
+ibm866 ibm866
+ ibm874
+windows-1250 windows-1250
+windows-1251 windows-1251
+windows-1252 windows-1252
+windows-1253 windows-1253
+windows-1254 windows-1254
+windows-1255 windows-1255
+windows-1256 windows-1256
+windows-1257 windows-1257
+windows-1258 windows-1258
+ ebcdic
+korean euc-k
+euc-kr euc-k
+gb2312 gb
+gb_2312-80 gb
+iso-2022-jp jis-kanji
+koi8-r koi8
+macintosh macrom
+ibm865 msdos2
+shift_jis ms-kanji
+ next
+ ov
+se sf1
+se2 sf2
+tis-620 tis
+ ucode
+euc-jp ujis
+utf16 unicode
+iso-10646-utf-1 utf1
+viscii-1 viet1
+viscii-2 viet2
+viscii viscii
diff --git a/sys/src/cmd/abaco/text.c b/sys/src/cmd/abaco/text.c
new file mode 100755
index 000000000..8c145a154
--- /dev/null
+++ b/sys/src/cmd/abaco/text.c
@@ -0,0 +1,880 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <memdraw.h>
+#include <thread.h>
+#include <cursor.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include <frame.h>
+#include <plumb.h>
+#include <html.h>
+#include "dat.h"
+#include "fns.h"
+
+Image *tagcols[NCOL];
+Image *textcols[NCOL];
+
+
+void
+textinit(Text *t, Image *b, Rectangle r, Font *f, Image *cols[NCOL])
+{
+ t->all = r;
+ t->scrollr = r;
+ t->scrollr.max.x = r.min.x+Scrollsize;
+ t->lastsr = ZR;
+ r.min.x += Scrollsize+Scrollgap;
+ t->rs.nr = 0;
+ memmove(t->Frame.cols, cols, sizeof t->Frame.cols);
+ textredraw(t, r, f, b);
+}
+
+void
+textredraw(Text *t, Rectangle r, Font *f, Image *b)
+{
+ Rectangle r1;
+
+ frinit(t, r, f, b, t->Frame.cols);
+ r1 = t->r;
+ r1.min.x -= Scrollsize+Scrollgap; /* back fill to scroll bar */
+ draw(t->b, r1, t->cols[BACK], nil, ZP);
+ t->maxtab = Maxtab*stringwidth(f, "0");
+ textfill(t);
+ textsetselect(t, t->q0, t->q1);
+}
+
+int
+textresize(Text *t, Image *b, Rectangle r)
+{
+ if(Dy(r) > 0)
+ r.max.y -= Dy(r)%t->font->height;
+ else
+ r.max.y = r.min.y;
+
+ t->all = r;
+ t->scrollr = r;
+ t->scrollr.max.x = r.min.x+Scrollsize;
+ t->lastsr = ZR;
+ r.min.x += Scrollsize+Scrollgap;
+ frclear(t, 0);
+ textredraw(t, r, t->font, b);
+ if(t->what == Textarea)
+ textscrdraw(t);
+ return r.max.y;
+}
+
+void
+textclose(Text *t)
+{
+ closerunestr(&t->rs);
+ frclear(t, 1);
+}
+
+void
+textinsert(Text *t, uint q0, Rune *r, uint n)
+{
+ if(n == 0)
+ return;
+
+ t->rs.r = runerealloc(t->rs.r, t->rs.nr+n);
+ runemove(t->rs.r+q0+n, t->rs.r+q0, t->rs.nr-q0);
+ runemove(t->rs.r+q0, r, n);
+ t->rs.nr += n;
+ if(q0 < t->q1)
+ t->q1 += n;
+ if(q0 < t->q0)
+ t->q0 += n;
+ if(q0 < t->org)
+ t->org += n;
+ else if(q0 <= t->org+t->nchars)
+ frinsert(t, r, r+n, q0-t->org);
+}
+
+void
+textfill(Text *t)
+{
+ Rune *rp;
+ int i, n, m, nl;
+
+ if(t->lastlinefull)
+ return;
+ rp = runemalloc(BUFSIZE*8);
+ do{
+ n = t->rs.nr-(t->org+t->nchars);
+ if(n == 0)
+ break;
+ if(n > 2000) /* educated guess at reasonable amount */
+ n = 2000;
+ runemove(rp, t->rs.r+(t->org+t->nchars), n);
+ /*
+ * it's expensive to frinsert more than we need, so
+ * count newlines.
+ */
+ nl = t->maxlines-t->nlines;
+ m = 0;
+ for(i=0; i<n; ){
+ if(rp[i++] == '\n'){
+ m++;
+ if(m >= nl)
+ break;
+ }
+ }
+ frinsert(t, rp, rp+i, t->nchars);
+ }while(t->lastlinefull == FALSE);
+ free(rp);
+}
+
+void
+textdelete(Text *t, uint q0, uint q1)
+{
+ uint n, p0, p1;
+
+ n = q1-q0;
+ if(n == 0)
+ return;
+
+ runemove(t->rs.r+q0, t->rs.r+q1, t->rs.nr-q1);
+ t->rs.nr -= n;
+ if(q0 < t->q0)
+ t->q0 -= min(n, t->q0-q0);
+ if(q0 < t->q1)
+ t->q1 -= min(n, t->q1-q0);
+ if(q1 <= t->org)
+ t->org -= n;
+ else if(q0 < t->org+t->nchars){
+ p1 = q1 - t->org;
+ if(p1 > t->nchars)
+ p1 = t->nchars;
+ if(q0 < t->org){
+ t->org = q0;
+ p0 = 0;
+ }else
+ p0 = q0 - t->org;
+ frdelete(t, p0, p1);
+ textfill(t);
+ }
+ t->rs.r[t->rs.nr] = L'\0';
+}
+
+int
+textbswidth(Text *t, Rune c)
+{
+ uint q, eq;
+ Rune r;
+ int skipping;
+
+ /* there is known to be at least one character to erase */
+ if(c == 0x08) /* ^H: erase character */
+ return 1;
+ q = t->q0;
+ skipping = TRUE;
+ while(q > 0){
+ r = t->rs.r[q-1];
+ if(r == '\n'){ /* eat at most one more character */
+ if(q == t->q0) /* eat the newline */
+ --q;
+ break;
+ }
+ if(c == 0x17){
+ eq = isalnum(r);
+ if(eq && skipping) /* found one; stop skipping */
+ skipping = FALSE;
+ else if(!eq && !skipping)
+ break;
+ }
+ --q;
+ }
+ return t->q0-q;
+}
+
+void
+texttype(Text *t, Rune r)
+{
+ uint q0, q1;
+ int nb, n;
+ int nr;
+ Rune *rp;
+
+ if(t->what!=Textarea && r=='\n'){
+ if(t->what==Urltag)
+ get(t, t, XXX, XXX, nil, 0);
+ return;
+ }
+ if(t->what==Tag && (r==Kscrollonedown || r==Kscrolloneup))
+ return;
+
+ nr = 1;
+ rp = &r;
+ switch(r){
+ case Kleft:
+ if(t->q0 > 0)
+ textshow(t, t->q0-1, t->q0-1, TRUE);
+ return;
+ case Kright:
+ if(t->q1 < t->rs.nr)
+ textshow(t, t->q1+1, t->q1+1, TRUE);
+ return;
+ case Kdown:
+ n = t->maxlines/3;
+ goto case_Down;
+ case Kscrollonedown:
+ n = mousescrollsize(t->maxlines);
+ if(n <= 0)
+ n = 1;
+ goto case_Down;
+ case Kpgdown:
+ n = 2*t->maxlines/3;
+ case_Down:
+ q0 = t->org+frcharofpt(t, Pt(t->r.min.x, t->r.min.y+n*t->font->height));
+ textsetorigin(t, q0, TRUE);
+ return;
+ case Kup:
+ n = t->maxlines/3;
+ goto case_Up;
+ case Kscrolloneup:
+ n = mousescrollsize(t->maxlines);
+ goto case_Up;
+ case Kpgup:
+ n = 2*t->maxlines/3;
+ case_Up:
+ q0 = textbacknl(t, t->org, n);
+ textsetorigin(t, q0, TRUE);
+ return;
+ case Khome:
+ textshow(t, 0, 0, FALSE);
+ return;
+ case Kend:
+ textshow(t, t->rs.nr, t->rs.nr, FALSE);
+ return;
+ case 0x01: /* ^A: beginning of line */
+ /* go to where ^U would erase, if not already at BOL */
+ nb = 0;
+ if(t->q0>0 && t->rs.r[t->q0-1]!='\n')
+ nb = textbswidth(t, 0x15);
+ textshow(t, t->q0-nb, t->q0-nb, TRUE);
+ return;
+ case 0x05: /* ^E: end of line */
+ q0 = t->q0;
+ while(q0<t->rs.nr && t->rs.r[q0]!='\n')
+ q0++;
+ textshow(t, q0, q0, TRUE);
+ return;
+ }
+ if(t->q1 > t->q0)
+ cut(t, t, TRUE, TRUE, nil, 0);
+
+ textshow(t, t->q0, t->q0, TRUE);
+ switch(r){
+ case 0x08: /* ^H: erase character */
+ case 0x15: /* ^U: erase line */
+ case 0x17: /* ^W: erase word */
+ if(t->q0 == 0) /* nothing to erase */
+ return;
+ nb = textbswidth(t, r);
+ q1 = t->q0;
+ q0 = q1-nb;
+ /* if selection is at beginning of window, avoid deleting invisible text */
+ if(q0 < t->org){
+ q0 = t->org;
+ nb = q1-q0;
+ }
+ if(nb > 0){
+ textdelete(t, q0, q0+nb);
+ textsetselect(t, q0, q0);
+ }
+ return;
+ }
+ /* otherwise ordinary character; just insert */
+ textinsert(t, t->q0, &r, 1);
+ if(rp != &r)
+ free(rp);
+ textsetselect(t, t->q0+nr, t->q0+nr);
+ if(t->what == Textarea)
+ textscrdraw(t);
+}
+
+static Text *clicktext;
+static uint clickmsec;
+static Text *selecttext;
+static uint selectq;
+
+/*
+ * called from frame library
+ */
+void
+framescroll(Frame *f, int dl)
+{
+ if(f != &selecttext->Frame)
+ error("frameselect not right frame");
+ textframescroll(selecttext, dl);
+}
+
+void
+textframescroll(Text *t, int dl)
+{
+ uint q0;
+
+ if(dl == 0){
+ scrsleep(100);
+ return;
+ }
+ if(dl < 0){
+ q0 = textbacknl(t, t->org, -dl);
+ if(selectq > t->org+t->p0)
+ textsetselect(t, t->org+t->p0, selectq);
+ else
+ textsetselect(t, selectq, t->org+t->p0);
+ }else{
+ if(t->org+t->nchars == t->rs.nr)
+ return;
+ q0 = t->org+frcharofpt(t, Pt(t->r.min.x, t->r.min.y+dl*t->font->height));
+ if(selectq > t->org+t->p1)
+ textsetselect(t, t->org+t->p1, selectq);
+ else
+ textsetselect(t, selectq, t->org+t->p1);
+ }
+ textsetorigin(t, q0, TRUE);
+}
+
+void
+textselect(Text *t)
+{
+ uint q0, q1;
+ int b, x, y;
+ int state;
+
+ selecttext = t;
+ /*
+ * To have double-clicking and chording, we double-click
+ * immediately if it might make sense.
+ */
+ b = mouse->buttons;
+ q0 = t->q0;
+ q1 = t->q1;
+ selectq = t->org+frcharofpt(t, mouse->xy);
+ if(clicktext==t && mouse->msec-clickmsec<500)
+ if(q0==q1 && selectq==q0){
+ textdoubleclick(t, &q0, &q1);
+ textsetselect(t, q0, q1);
+ flushimage(display, 1);
+ x = mouse->xy.x;
+ y = mouse->xy.y;
+ /* stay here until something interesting happens */
+ do
+ readmouse(mousectl);
+ while(mouse->buttons==b && abs(mouse->xy.x-x)<3 && abs(mouse->xy.y-y)<3);
+ mouse->xy.x = x; /* in case we're calling frselect */
+ mouse->xy.y = y;
+ q0 = t->q0; /* may have changed */
+ q1 = t->q1;
+ selectq = q0;
+ }
+ if(mouse->buttons == b){
+ t->Frame.scroll = framescroll;
+ frselect(t, mousectl);
+ /* horrible botch: while asleep, may have lost selection altogether */
+ if(selectq > t->rs.nr)
+ selectq = t->org + t->p0;
+ t->Frame.scroll = nil;
+ if(selectq < t->org)
+ q0 = selectq;
+ else
+ q0 = t->org + t->p0;
+ if(selectq > t->org+t->nchars)
+ q1 = selectq;
+ else
+ q1 = t->org+t->p1;
+ }
+ if(q0 == q1){
+ if(q0==t->q0 && clicktext==t && mouse->msec-clickmsec<500){
+ textdoubleclick(t, &q0, &q1);
+ clicktext = nil;
+ }else{
+ clicktext = t;
+ clickmsec = mouse->msec;
+ }
+ }else
+ clicktext = nil;
+ textsetselect(t, q0, q1);
+ flushimage(display, 1);
+ state = 0; /* undo when possible; +1 for cut, -1 for paste */
+ while(mouse->buttons){
+ mouse->msec = 0;
+ b = mouse->buttons;
+ if((b&1) && (b&6)){
+ if(b & 2){
+ if(state==-1 && t->what==Textarea){
+ textsetselect(t, q0, t->q0);
+ state = 0;
+ }else if(state != 1){
+ cut(t, t, TRUE, TRUE, nil, 0);
+ state = 1;
+ }
+ }else{
+ if(state==1 && t->what==Textarea){
+ textsetselect(t, q0, t->q1);
+ state = 0;
+ }else if(state != -1){
+ paste(t, t, TRUE, FALSE, nil, 0);
+ state = -1;
+ }
+ }
+ textscrdraw(t);
+ }
+ flushimage(display, 1);
+ while(mouse->buttons == b)
+ readmouse(mousectl);
+ clicktext = nil;
+ }
+}
+
+void
+textshow(Text *t, uint q0, uint q1, int doselect)
+{
+ int qe;
+ int nl;
+ uint q;
+
+ if(t->what != Textarea){
+ if(doselect)
+ textsetselect(t, q0, q1);
+ return;
+ }
+ if(doselect)
+ textsetselect(t, q0, q1);
+ qe = t->org+t->nchars;
+ if(t->org<=q0 && (q0<qe || (q0==qe && qe==t->rs.nr)))
+ textscrdraw(t);
+ else{
+ nl = t->maxlines/4;
+ q = textbacknl(t, q0, nl);
+ /* avoid going backwards if trying to go forwards - long lines! */
+ if(!(q0>t->org && q<t->org))
+ textsetorigin(t, q, TRUE);
+ while(q0 > t->org+t->nchars)
+ textsetorigin(t, t->org+1, FALSE);
+ }
+}
+
+static
+int
+region(int a, int b)
+{
+ if(a < b)
+ return -1;
+ if(a == b)
+ return 0;
+ return 1;
+}
+
+void
+selrestore(Frame *f, Point pt0, uint p0, uint p1)
+{
+ if(p1<=f->p0 || p0>=f->p1){
+ /* no overlap */
+ frdrawsel0(f, pt0, p0, p1, f->cols[BACK], f->cols[TEXT]);
+ return;
+ }
+ if(p0>=f->p0 && p1<=f->p1){
+ /* entirely inside */
+ frdrawsel0(f, pt0, p0, p1, f->cols[HIGH], f->cols[HTEXT]);
+ return;
+ }
+
+ /* they now are known to overlap */
+
+ /* before selection */
+ if(p0 < f->p0){
+ frdrawsel0(f, pt0, p0, f->p0, f->cols[BACK], f->cols[TEXT]);
+ p0 = f->p0;
+ pt0 = frptofchar(f, p0);
+ }
+ /* after selection */
+ if(p1 > f->p1){
+ frdrawsel0(f, frptofchar(f, f->p1), f->p1, p1, f->cols[BACK], f->cols[TEXT]);
+ p1 = f->p1;
+ }
+ /* inside selection */
+ frdrawsel0(f, pt0, p0, p1, f->cols[HIGH], f->cols[HTEXT]);
+}
+
+void
+textsetselect(Text *t, uint q0, uint q1)
+{
+ int p0, p1;
+
+ /* t->p0 and t->p1 are always right; t->q0 and t->q1 may be off */
+ t->q0 = q0;
+ t->q1 = q1;
+ /* compute desired p0,p1 from q0,q1 */
+ p0 = q0-t->org;
+ p1 = q1-t->org;
+ if(p0 < 0)
+ p0 = 0;
+ if(p1 < 0)
+ p1 = 0;
+ if(p0 > t->nchars)
+ p0 = t->nchars;
+ if(p1 > t->nchars)
+ p1 = t->nchars;
+ if(p0==t->p0 && p1==t->p1)
+ return;
+ /* screen disagrees with desired selection */
+ if(t->p1<=p0 || p1<=t->p0 || p0==p1 || t->p1==t->p0){
+ /* no overlap or too easy to bother trying */
+ frdrawsel(t, frptofchar(t, t->p0), t->p0, t->p1, 0);
+ frdrawsel(t, frptofchar(t, p0), p0, p1, 1);
+ goto Return;
+ }
+ /* overlap; avoid unnecessary painting */
+ if(p0 < t->p0){
+ /* extend selection backwards */
+ frdrawsel(t, frptofchar(t, p0), p0, t->p0, 1);
+ }else if(p0 > t->p0){
+ /* trim first part of selection */
+ frdrawsel(t, frptofchar(t, t->p0), t->p0, p0, 0);
+ }
+ if(p1 > t->p1){
+ /* extend selection forwards */
+ frdrawsel(t, frptofchar(t, t->p1), t->p1, p1, 1);
+ }else if(p1 < t->p1){
+ /* trim last part of selection */
+ frdrawsel(t, frptofchar(t, p1), p1, t->p1, 0);
+ }
+
+ Return:
+ t->p0 = p0;
+ t->p1 = p1;
+}
+
+
+/*
+ * Release the button in less than DELAY ms and it's considered a null selection
+ * if the mouse hardly moved, regardless of whether it crossed a char boundary.
+ */
+enum {
+ DELAY = 2,
+ MINMOVE = 4,
+};
+
+uint
+xselect(Frame *f, Mousectl *mc, Image *col, uint *p1p) /* when called, button is down */
+{
+ uint p0, p1, q, tmp;
+ ulong msec;
+ Point mp, pt0, pt1, qt;
+ int reg, b;
+
+ mp = mc->xy;
+ b = mc->buttons;
+ msec = mc->msec;
+
+ /* remove tick */
+ if(f->p0 == f->p1)
+ frtick(f, frptofchar(f, f->p0), 0);
+ p0 = p1 = frcharofpt(f, mp);
+ pt0 = frptofchar(f, p0);
+ pt1 = frptofchar(f, p1);
+ reg = 0;
+ frtick(f, pt0, 1);
+ do{
+ q = frcharofpt(f, mc->xy);
+ if(p1 != q){
+ if(p0 == p1)
+ frtick(f, pt0, 0);
+ if(reg != region(q, p0)){ /* crossed starting point; reset */
+ if(reg > 0)
+ selrestore(f, pt0, p0, p1);
+ else if(reg < 0)
+ selrestore(f, pt1, p1, p0);
+ p1 = p0;
+ pt1 = pt0;
+ reg = region(q, p0);
+ if(reg == 0)
+ frdrawsel0(f, pt0, p0, p1, col, display->white);
+ }
+ qt = frptofchar(f, q);
+ if(reg > 0){
+ if(q > p1)
+ frdrawsel0(f, pt1, p1, q, col, display->white);
+
+ else if(q < p1)
+ selrestore(f, qt, q, p1);
+ }else if(reg < 0){
+ if(q > p1)
+ selrestore(f, pt1, p1, q);
+ else
+ frdrawsel0(f, qt, q, p1, col, display->white);
+ }
+ p1 = q;
+ pt1 = qt;
+ }
+ if(p0 == p1)
+ frtick(f, pt0, 1);
+ flushimage(f->display, 1);
+ readmouse(mc);
+ }while(mc->buttons == b);
+ if(mc->msec-msec < DELAY && p0!=p1
+ && abs(mp.x-mc->xy.x)<MINMOVE
+ && abs(mp.y-mc->xy.y)<MINMOVE) {
+ if(reg > 0)
+ selrestore(f, pt0, p0, p1);
+ else if(reg < 0)
+ selrestore(f, pt1, p1, p0);
+ p1 = p0;
+ }
+ if(p1 < p0){
+ tmp = p0;
+ p0 = p1;
+ p1 = tmp;
+ }
+ pt0 = frptofchar(f, p0);
+ if(p0 == p1)
+ frtick(f, pt0, 0);
+ selrestore(f, pt0, p0, p1);
+ /* restore tick */
+ if(f->p0 == f->p1)
+ frtick(f, frptofchar(f, f->p0), 1);
+ flushimage(f->display, 1);
+ *p1p = p1;
+ return p0;
+}
+
+int
+textselect23(Text *t, uint *q0, uint *q1, Image *high, int mask)
+{
+ uint p0, p1;
+ int buts;
+
+ p0 = xselect(t, mousectl, high, &p1);
+ buts = mousectl->buttons;
+ if((buts & mask) == 0){
+ *q0 = p0+t->org;
+ *q1 = p1+t->org;
+ }
+
+ while(mousectl->buttons)
+ readmouse(mousectl);
+ return buts;
+}
+
+int
+textselect2(Text *t, uint *q0, uint *q1, Text **tp)
+{
+ int buts;
+
+ *tp = nil;
+ buts = textselect23(t, q0, q1, but2col, 4);
+ if(buts & 4)
+ return 0;
+ if(buts & 1){ /* pick up argument */
+ *tp = argtext;
+ return 1;
+ }
+ return 1;
+}
+
+int
+textselect3(Text *t, uint *q0, uint *q1)
+{
+ int h;
+
+ h = (textselect23(t, q0, q1, but3col, 1|2) == 0);
+ return h;
+}
+
+static Rune left1[] = { L'{', L'[', L'(', L'<', L'«', 0 };
+static Rune right1[] = { L'}', L']', L')', L'>', L'»', 0 };
+static Rune left2[] = { L'\n', 0 };
+static Rune left3[] = { L'\'', L'"', L'`', 0 };
+
+static
+Rune *left[] = {
+ left1,
+ left2,
+ left3,
+ nil
+};
+static
+Rune *right[] = {
+ right1,
+ left2,
+ left3,
+ nil
+};
+
+void
+textdoubleclick(Text *t, uint *q0, uint *q1)
+{
+ int c, i;
+ Rune *r, *l, *p;
+ uint q;
+
+ if(t->rs.nr == 0)
+ return;
+
+ for(i=0; left[i]!=nil; i++){
+ q = *q0;
+ l = left[i];
+ r = right[i];
+ /* try matching character to left, looking right */
+ if(q == 0)
+ c = '\n';
+ else
+ c = t->rs.r[q-1];
+ p = runestrchr(l, c);
+ if(p != nil){
+ if(textclickmatch(t, c, t->rs.r[p-l], 1, &q))
+ *q1 = q-(c!='\n');
+ return;
+ }
+ /* try matching character to right, looking left */
+ if(q == t->rs.nr)
+ c = '\n';
+ else
+ c = t->rs.r[q];
+ p = runestrchr(r, c);
+ if(p != nil){
+ if(textclickmatch(t, c, l[p-r], -1, &q)){
+ *q1 = *q0+(*q0<t->rs.nr && c=='\n');
+ *q0 = q;
+ if(c!='\n' || q!=0 || t->rs.r[0]=='\n')
+ (*q0)++;
+ }
+ return;
+ }
+ }
+ /* try filling out word to right */
+ while(*q1<t->rs.nr && isalnum(t->rs.r[*q1]))
+ (*q1)++;
+ /* try filling out word to left */
+ while(*q0>0 && isalnum(t->rs.r[*q0-1]))
+ (*q0)--;
+}
+
+int
+textclickmatch(Text *t, int cl, int cr, int dir, uint *q)
+{
+ Rune c;
+ int nest;
+
+ nest = 1;
+ for(;;){
+ if(dir > 0){
+ if(*q == t->rs.nr)
+ break;
+ c = t->rs.r[*q];
+ (*q)++;
+ }else{
+ if(*q == 0)
+ break;
+ (*q)--;
+ c = t->rs.r[*q];
+ }
+ if(c == cr){
+ if(--nest==0)
+ return 1;
+ }else if(c == cl)
+ nest++;
+ }
+ return cl=='\n' && nest==1;
+}
+
+uint
+textbacknl(Text *t, uint p, uint n)
+{
+ int i, j;
+
+ /* look for start of this line if n==0 */
+ if(n==0 && p>0 && t->rs.r[p-1]!='\n')
+ n = 1;
+ i = n;
+ while(i-->0 && p>0){
+ --p; /* it's at a newline now; back over it */
+ if(p == 0)
+ break;
+ /* at 128 chars, call it a line anyway */
+ for(j=128; --j>0 && p>0; p--)
+ if(t->rs.r[p-1]=='\n')
+ break;
+ }
+ return p;
+}
+
+void
+textsetorigin(Text *t, uint org, int exact)
+{
+ int i, a, fixup;
+ Rune *r;
+ uint n;
+
+ if(org>0 && !exact){
+ /* org is an estimate of the char posn; find a newline */
+ /* don't try harder than 256 chars */
+ for(i=0; i<256 && org<t->rs.nr; i++){
+ if(t->rs.r[org] == '\n'){
+ org++;
+ break;
+ }
+ org++;
+ }
+ }
+ a = org-t->org;
+ fixup = 0;
+ if(a>=0 && a<t->nchars){
+ frdelete(t, 0, a);
+ fixup = 1; /* frdelete can leave end of last line in wrong selection mode; it doesn't know what follows */
+ }else if(a<0 && -a<t->nchars){
+ n = t->org - org;
+ r = runemalloc(n);
+ runemove(r, t->rs.r+org, n);
+ frinsert(t, r, r+n, 0);
+ free(r);
+ }else
+ frdelete(t, 0, t->nchars);
+ t->org = org;
+ textfill(t);
+ textscrdraw(t);
+ textsetselect(t, t->q0, t->q1);
+ if(fixup && t->p1 > t->p0)
+ frdrawsel(t, frptofchar(t, t->p1-1), t->p1-1, t->p1, 1);
+}
+
+void
+textset(Text *t, Rune*r, int n)
+{
+ textdelete(t, 0, t->rs.nr);
+ textinsert(t, 0, r, n);
+ textsetselect(t, t->q0, t->q1);
+}
+
+void
+textmouse(Text *t, Point xy, int but)
+{
+ Text *argt;
+ uint q0, q1;
+
+ if(ptinrect(xy, t->scrollr)){
+ if(t->what == Columntag)
+ rowdragcol(&row, t->col, but);
+ else if(t->what == Tag)
+ coldragwin(t->col, t->w, but);
+ else if(t->what == Textarea)
+ textscroll(t, but);
+ if(t->col)
+ activecol = t->col;
+ return;
+ }
+ if(but == 1){
+ selpage = nil;
+ textselect(t);
+ argtext = t;
+ seltext = t;
+ }else if(but == 2){
+ if(textselect2(t, &q0, &q1, &argt))
+ execute(t, q0, q1, argt);
+ }else if(but == 3){
+ if(textselect3(t, &q0, &q1))
+ look3(t, q0, q1);
+ }
+}
diff --git a/sys/src/cmd/abaco/time.c b/sys/src/cmd/abaco/time.c
new file mode 100755
index 000000000..e92a99bb8
--- /dev/null
+++ b/sys/src/cmd/abaco/time.c
@@ -0,0 +1,119 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <memdraw.h>
+#include <thread.h>
+#include <cursor.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include <frame.h>
+#include <plumb.h>
+#include <html.h>
+#include "dat.h"
+#include "fns.h"
+
+static Channel* ctimer; /* chan(Timer*)[100] */
+static Timer *timer;
+
+static
+uint
+msec(void)
+{
+ return nsec()/1000000;
+}
+
+void
+timerstop(Timer *t)
+{
+ t->next = timer;
+ timer = t;
+}
+
+void
+timercancel(Timer *t)
+{
+ t->cancel = TRUE;
+}
+
+static
+void
+timerproc(void*)
+{
+ int i, nt, na, dt, del;
+ Timer **t, *x;
+ uint old, new;
+
+ threadsetname("timerproc");
+ rfork(RFFDG);
+ t = nil;
+ na = 0;
+ nt = 0;
+ old = msec();
+ for(;;){
+ sleep(1); /* will sleep minimum incr */
+ new = msec();
+ dt = new-old;
+ old = new;
+ if(dt < 0) /* timer wrapped; go around, losing a tick */
+ continue;
+ for(i=0; i<nt; i++){
+ x = t[i];
+ x->dt -= dt;
+ del = FALSE;
+ if(x->cancel){
+ timerstop(x);
+ del = TRUE;
+ }else if(x->dt <= 0){
+ /*
+ * avoid possible deadlock if client is
+ * now sending on ctimer
+ */
+ if(nbsendul(x->c, 0) > 0)
+ del = TRUE;
+ }
+ if(del){
+ memmove(&t[i], &t[i+1], (nt-i-1)*sizeof t[0]);
+ --nt;
+ --i;
+ }
+ }
+ if(nt == 0){
+ x = recvp(ctimer);
+ gotit:
+ if(nt == na){
+ na += 10;
+ t = erealloc(t, na*sizeof(Timer*));
+ }
+ t[nt++] = x;
+ old = msec();
+ }
+ if(nbrecv(ctimer, &x) > 0)
+ goto gotit;
+ }
+}
+
+void
+timerinit(void)
+{
+ ctimer = chancreate(sizeof(Timer*), 100);
+ proccreate(timerproc, nil, STACK);
+}
+
+Timer*
+timerstart(int dt)
+{
+ Timer *t;
+
+ t = timer;
+ if(t)
+ timer = timer->next;
+ else{
+ t = emalloc(sizeof(Timer));
+ t->c = chancreate(sizeof(int), 0);
+ }
+ t->next = nil;
+ t->dt = dt;
+ t->cancel = FALSE;
+ sendp(ctimer, t);
+ return t;
+}
diff --git a/sys/src/cmd/abaco/urls.c b/sys/src/cmd/abaco/urls.c
new file mode 100755
index 000000000..9386cb164
--- /dev/null
+++ b/sys/src/cmd/abaco/urls.c
@@ -0,0 +1,230 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <memdraw.h>
+#include <thread.h>
+#include <cursor.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include <frame.h>
+#include <plumb.h>
+#include <html.h>
+#include "dat.h"
+#include "fns.h"
+
+Url *
+urlalloc(Runestr *src, Runestr *post, int m)
+{
+ Url *u;
+
+ u = emalloc(sizeof(Url));
+ copyrunestr(&u->src, src);
+ if(m==HPost)
+ copyrunestr(&u->post, post);
+ u->method = m;
+ incref(u);
+ return u;
+}
+
+void
+urlfree(Url *u)
+{
+ if(u && decref(u)==0){
+ closerunestr(&u->src);
+ closerunestr(&u->act);
+ closerunestr(&u->post);
+ closerunestr(&u->ctype);
+ free(u);
+ }
+}
+
+Url *
+urldup(Url *a)
+{
+ Url *b;
+
+ b = emalloc(sizeof(Url));
+ b->method = a->method;
+ copyrunestr(&b->src, &a->src);
+ copyrunestr(&b->act, &a->act);
+ copyrunestr(&b->post, &a->post);
+ copyrunestr(&b->ctype, &a->ctype);
+ return b;
+}
+
+static
+Runestr
+getattr(int conn, char *s)
+{
+ char buf[BUFSIZE];
+ int fd, n;
+
+ snprint(buf, sizeof(buf), "%s/%d/%s", webmountpt, conn, s);
+ fd = open(buf, OREAD);
+ if(fd < 0)
+ error("can't open attr file");
+
+ n = read(fd, buf, sizeof(buf)-1);
+ if(n < 0)
+ error("can't read");
+
+ close(fd);
+ buf[n] = '\0';
+ return (Runestr){runesmprint("%s", buf), n};
+}
+
+int
+urlopen(Url *u)
+{
+ char buf[BUFSIZE];
+ int cfd, fd, conn, n;
+
+ snprint(buf, sizeof(buf), "%s/clone", webmountpt);
+ cfd = open(buf, ORDWR);
+ if(cfd < 0)
+ error("can't open clone file");
+
+ n = read(cfd, buf, sizeof(buf)-1);
+ if(n <= 0)
+ error("reading clone");
+
+ buf[n] = '\0';
+ conn = atoi(buf);
+
+ snprint(buf, sizeof(buf), "url %S", u->src.r);
+ if(write(cfd, buf, strlen(buf)) < 0){
+// fprint(2, "write: %s: %r\n", buf);
+ Err:
+ close(cfd);
+ return -1;
+ }
+ if(u->method==HPost && u->post.r != nil){
+ snprint(buf, sizeof(buf), "%s/%d/postbody", webmountpt, conn);
+ fd = open(buf, OWRITE);
+ if(fd < 0){
+// fprint(2, "urlopen: bad query: %s: %r\n", buf);
+ goto Err;
+ }
+ snprint(buf, sizeof(buf), "%S", u->post.r);
+ if(write(fd, buf, strlen(buf)) < 0)
+ fprint(2, "urlopen: bad query: %s: %r\n", buf);
+
+ close(fd);
+ }
+ snprint(buf, sizeof(buf), "%s/%d/body", webmountpt, conn);
+ fd = open(buf, OREAD);
+ if(fd < 0){
+// fprint(2, "open: %S: %r\n", u->src.r);
+ goto Err;
+ }
+ u->ctype = getattr(conn, "contenttype");
+ u->act = getattr(conn, "parsed/url");
+ if(u->act.nr == 0)
+ copyrunestr(&u->act, &u->src);
+ close(cfd);
+ return fd;
+}
+
+void
+urlcanon(Rune *name){
+ Rune *s, *t;
+ Rune **comp, **p, **q;
+ int rooted;
+
+ name = runestrchr(name, L'/')+2;
+ rooted=name[0]==L'/';
+ /*
+ * Break the name into a list of components
+ */
+ comp=emalloc(runestrlen(name)*sizeof(char *));
+ p=comp;
+ *p++=name;
+ for(s=name;;s++){
+ if(*s==L'/'){
+ *p++=s+1;
+ *s='\0';
+ }
+ else if(*s=='\0')
+ break;
+ }
+ *p=0;
+ /*
+ * go through the component list, deleting components that are empty (except
+ * the last component) or ., and any .. and its non-.. predecessor.
+ */
+ p=q=comp;
+ while(*p){
+ if(runestrcmp(*p, L"")==0 && p[1]!=0
+ || runestrcmp(*p, L".")==0)
+ p++;
+ else if(runestrcmp(*p, L"..")==0 && q!=comp && runestrcmp(q[-1], L"..")!=0){
+ --q;
+ p++;
+ }
+ else
+ *q++=*p++;
+ }
+ *q=0;
+ /*
+ * rebuild the path name
+ */
+ s=name;
+ if(rooted) *s++='/';
+ for(p=comp;*p;p++){
+ t=*p;
+ while(*t) *s++=*t++;
+ if(p[1]!=0) *s++='/';
+ }
+ *s='\0';
+ free(comp);
+}
+
+/* this is a HACK */
+Rune *
+urlcombine(Rune *b, Rune *u)
+{
+ Rune *p, *q, *sep, *s;
+ Rune endrune[] = { L'?', L'#' };
+ int i, restore;
+
+ if(u == nil)
+ error("urlcombine: u == nil");
+
+ if(validurl(u))
+ return erunestrdup(u);
+
+ if(b==nil || !validurl(b))
+ error("urlcombine: b==nil || !validurl(b)");
+
+ if(runestrncmp(u, L"//", 2) == 0){
+ q = runestrchr(b, L':');
+ return runesmprint("%.*S:%S", (int)(q-b), b, u);
+ }
+ p = runestrstr(b, L"://")+3;
+ sep = L"";
+ q = nil;
+ if(*u ==L'/')
+ q = runestrchr(p, L'/');
+ else if(*u==L'#' || *u==L'?'){
+ for(i=0; i<nelem(endrune); i++)
+ if(q = runestrchr(p, endrune[i]))
+ break;
+ }else{
+ sep = L"/";
+ restore = 0;
+ s = runestrchr(p, L'?');
+ if(s != nil){
+ *s = '\0';
+ restore = 1;
+ }
+ q = runestrrchr(p, L'/');
+ if(restore)
+ *s = L'?';
+ }
+ if(q == nil)
+ p = runesmprint("%S%S%S", b, sep, u);
+ else
+ p = runesmprint("%.*S%S%S", (int)(q-b), b, sep, u);
+ urlcanon(p);
+ return p;
+}
diff --git a/sys/src/cmd/abaco/util.c b/sys/src/cmd/abaco/util.c
new file mode 100755
index 000000000..cd7e7b3da
--- /dev/null
+++ b/sys/src/cmd/abaco/util.c
@@ -0,0 +1,1293 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <draw.h>
+#include <memdraw.h>
+#include <thread.h>
+#include <cursor.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include <frame.h>
+#include <plumb.h>
+#include <html.h>
+#include <regexp.h>
+#include "dat.h"
+#include "fns.h"
+
+static Point prevmouse;
+static Window *mousew;
+
+int
+min(int a, int b)
+{
+ if(a < b)
+ return a;
+ return b;
+}
+
+int
+max(int a, int b)
+{
+ if(a > b)
+ return a;
+ return b;
+}
+
+void
+cvttorunes(char *p, int n, Rune *r, int *nb, int *nr, int *nulls)
+{
+ uchar *q;
+ Rune *s;
+ int j, w;
+
+ /*
+ * Always guaranteed that n bytes may be interpreted
+ * without worrying about partial runes. This may mean
+ * reading up to UTFmax-1 more bytes than n; the caller
+ * knows this. If n is a firm limit, the caller should
+ * set p[n] = 0.
+ */
+ q = (uchar*)p;
+ s = r;
+ for(j=0; j<n; j+=w){
+ if(*q < Runeself){
+ w = 1;
+ *s = *q++;
+ }else{
+ w = chartorune(s, (char*)q);
+ q += w;
+ }
+ if(*s)
+ s++;
+ else if(nulls)
+ *nulls = TRUE;
+ }
+ *nb = (char*)q-p;
+ *nr = s-r;
+}
+
+void
+bytetorunestr(char *s, Runestr *rs)
+{
+ Rune *r;
+ int nb, nr;
+
+ nb = strlen(s);
+ r = runemalloc(nb+1);
+ cvttorunes(s, nb, r, &nb, &nr, nil);
+ r[nr] = '\0';
+ rs->nr = nr;
+ rs->r = r;
+}
+
+void
+error(char *s)
+{
+ fprint(2, "abaco: %s: %r\n", s);
+// abort();
+ threadexitsall(s);
+}
+
+void*
+emalloc(ulong n)
+{
+ void *p;
+
+ p = malloc(n);
+ if(p == nil)
+ error("malloc failed");
+ setmalloctag(p, getcallerpc(&n));
+ memset(p, 0, n);
+ return p;
+}
+
+void*
+erealloc(void *p, ulong n)
+{
+ p = realloc(p, n);
+ if(p == nil)
+ error("realloc failed");
+ setmalloctag(p, getcallerpc(&n));
+ return p;
+}
+
+Rune*
+erunestrdup(Rune *r)
+{
+ void *p;
+
+ if(r == nil)
+ return nil;
+ p = runestrdup(r);
+ if(p == nil)
+ error("runestrdup failed");
+ setmalloctag(p, getcallerpc(&r));
+ return p;
+}
+
+char*
+estrdup(char *s)
+{
+ char *t;
+
+ t = strdup(s);
+ if(t == nil)
+ error("strdup failed");
+ setmalloctag(t, getcallerpc(&s));
+ return t;
+}
+
+int
+runestreq(Runestr a, Runestr b)
+{
+ return runeeq(a.r, a.nr, b.r, b.nr);
+}
+
+int
+runeeq(Rune *s1, uint n1, Rune *s2, uint n2)
+{
+ if(n1 != n2)
+ return FALSE;
+ return memcmp(s1, s2, n1*sizeof(Rune)) == 0;
+}
+
+void
+closerunestr(Runestr *rs)
+{
+
+ rs->nr = 0;
+ if(rs->r)
+ free(rs->r);
+ rs->r = nil;
+}
+
+void
+copyrunestr(Runestr *a, Runestr *b)
+{
+ closerunestr(a);
+ a->r = runemalloc(b->nr+1);
+ runemove(a->r, b->r, b->nr);
+ a->r[b->nr] = 0;
+ a->nr = b->nr;
+}
+
+int
+isalnum(Rune c)
+{
+ /*
+ * Hard to get absolutely right. Use what we know about ASCII
+ * and assume anything above the Latin control characters is
+ * potentially an alphanumeric.
+ */
+ if(c <= ' ')
+ return FALSE;
+ if(0x7F<=c && c<=0xA0)
+ return FALSE;
+ if(utfrune("!\"#$%&'()*+,-./:;<=>?@[\\]^`{|}~", c))
+ return FALSE;
+ return TRUE;
+}
+
+Rune*
+skipbl(Rune *r, int n, int *np)
+{
+ while(n>0 && (*r==' ' || *r=='\t' || *r=='\n')){
+ --n;
+ r++;
+ }
+ *np = n;
+ return r;
+}
+
+Rune*
+findbl(Rune *r, int n, int *np)
+{
+ while(n>0 && *r!=' ' && *r!='\t' && *r!='\n'){
+ --n;
+ r++;
+ }
+ *np = n;
+ return r;
+}
+
+int
+istextfield(Item *i)
+{
+ Formfield *ff;
+
+ ff = ((Iformfield *)i)->formfield;
+ if(ff->ftype==Ftext || ff->ftype==Ftextarea || ff->ftype==Fpassword)
+ return TRUE;
+
+ return FALSE;
+}
+
+int
+forceitem(Item *i)
+{
+ if(i->state&IFwrap && i->tag!=Iruletag && i->tag!=Itabletag)
+ return FALSE;
+
+ return TRUE;
+}
+
+int
+dimwidth(Dimen d, int w)
+{
+ int s, k;
+
+ k = dimenkind(d);
+ if(k == Dnone)
+ return w;
+ s = dimenspec(d);
+ if(k == Dpixels)
+ w = s;
+ else if(k==Dpercent && s<100)
+ w = s*w/100;
+
+ return w;
+}
+
+void
+frdims(Dimen *d, int n, int t, int **ret)
+{
+ int totpix, totpcnt, totrel;
+ double spix, spcnt, relu, vd;
+ int tt, trest, totpixrel, minrelu, i;
+ int *x, *spec, *kind;
+
+ if(n == 1){
+ *ret = x = emalloc(sizeof(int));
+ x[0] = t;
+ return;
+ }
+ totpix = totpcnt = totrel = 0;
+ spec = emalloc(n*sizeof(int));
+ kind = emalloc(n*sizeof(int));
+ for(i=0; i<n; i++){
+ spec[i] = dimenspec(d[i]);
+ if(spec[i] < 0)
+ spec[i] = 0;
+ kind[i] = dimenkind(d[i]);
+ switch(kind[i]){
+ case Dpixels:
+ totpix += spec[i];
+ break;
+ case Dpercent:
+ totpcnt += spec[i];
+ break;
+ case Drelative:
+ totrel += spec[i];
+ break;
+ case Dnone:
+ totrel++;
+ break;
+ }
+ }
+ spix = spcnt = 1.0;
+ minrelu = 0;
+ if(totrel > 0)
+ minrelu = Scrollsize+Scrollgap;
+ relu = (double)minrelu;
+ tt = totpix + t*totpcnt/100 + totrel*minrelu;
+ if(tt < t){
+ if(totrel == 0){
+ if(totpcnt != 0)
+ spcnt = (double)((t-totpix)*100)/(double)(t*totpcnt);
+ else
+ spix = (double)t/(double)totpix;
+ }else
+ relu += (double)(t-tt)/(double)totrel;
+ }else{
+ totpixrel = totpix + totrel*minrelu;
+ if(totpixrel < t)
+ spcnt = (double)((t-totpixrel)*100)/(double)(t*totpcnt);
+ else{
+ trest = t - totrel*minrelu;
+ if(trest > 0)
+ spcnt = (double)trest/(double)(totpix + (t*totpcnt/100));
+ else{
+ spcnt = (double)t/(double)tt;
+ relu = 0.0;
+ }
+ spix = spcnt;
+ }
+ }
+ x = emalloc(n * sizeof(int));
+ tt = 0;
+ for(i=0; i<n-1; i++){
+ vd = (double)spec[i];
+ switch(kind[i]){
+ case Dpixels:
+ vd = vd*spix;
+ break;
+ case Dpercent:
+ vd = vd*(double)t*spcnt/100.0;
+ break;
+ case Drelative:
+ vd = vd*relu;
+ break;
+ case Dnone:
+ vd = relu;
+ break;
+ }
+ x[i] = (int)(vd+.5);
+ tt += x[i];
+ }
+ x[n - 1] = t - tt;
+ *ret = x;
+ free(spec);
+ free(kind);
+}
+
+Image *
+getbg(Page *p)
+{
+ Docinfo *d;
+ Cimage *ci;
+ Image *bg;
+
+ d = p->doc;
+ if(d->backgrounditem){
+ if(d->backgrounditem->aux){
+ ci = d->backgrounditem->aux;
+ if(ci->mi)
+ getimage(ci, d->backgrounditem->altrep);
+ bg = ci->i;
+ }else
+ bg = display->white;
+ }else
+ bg = getcolor(d->background.color);
+
+ return bg;
+}
+
+Rune *
+getbase(Page *p)
+{
+ if(p->doc)
+ return p->doc->base;
+ if(p->url->act.r)
+ return p->url->act.r;
+ return p->url->src.r;
+}
+
+Image *
+eallocimage(Display *d, Rectangle r, ulong chan, int repl, int col)
+{
+ Image *i;
+
+ i = allocimage(d, r, chan, repl, col);
+ if(i == nil)
+ error("allocimage failed");
+ return i;
+}
+
+void
+rect3d(Image *im, Rectangle r, int i, Image **c, Point sp)
+{
+ Point p[6];
+
+ if(i < 0){
+ r = insetrect(r, i);
+ sp = addpt(sp, Pt(i,i));
+ i = -i;
+ }
+ draw(im, Rect(r.min.x+i, r.min.y+i, r.max.x-i, r.max.y-i), c[2], nil, sp);
+ p[0] = r.min;
+ p[1] = Pt(r.min.x, r.max.y);
+ p[2] = Pt(r.min.x+i, r.max.y-i);
+ p[3] = Pt(r.min.x+i, r.min.y+i);
+ p[4] = Pt(r.max.x-i, r.min.y+i);
+ p[5] = Pt(r.max.x, r.min.y);
+ fillpoly(im, p, 6, 0, c[0], sp);
+ p[0] = r.max;
+ p[1] = Pt(r.min.x, r.max.y);
+ p[2] = Pt(r.min.x+i, r.max.y-i);
+ p[3] = Pt(r.max.x-i, r.max.y-i);
+ p[4] = Pt(r.max.x-i, r.min.y+i);
+ p[5] = Pt(r.max.x, r.min.y);
+ fillpoly(im, p, 6, 0, c[1], sp);
+}
+
+void
+ellipse3d(Image *im, Point p, int rad, int i, Image **c, Point sp)
+{
+ fillarc(im, p, rad, rad, c[0], sp, 45, 180);
+ fillarc(im, p, rad, rad, c[1], sp, 45, -180);
+ fillellipse(im, p, rad-i, rad-i, c[2], sp);
+}
+
+void
+colarray(Image **c, Image *c0, Image *c1, Image *c2, int checked)
+{
+ if(checked){
+ c[0] = c0;
+ c[1] = c1;
+ }else{
+ c[0] = c1;
+ c[1] = c0;
+ }
+ c[2] = c2;
+}
+
+static char *deffontpaths[] = {
+#include "fonts.h"
+};
+
+static char *fontpaths[NumFnt];
+static Font *fonts[NumFnt];
+
+void
+initfontpaths(void)
+{
+ Biobufhdr *bp;
+ char buf[128];
+ int i;
+
+ /* we don't care if getenv(2) fails */
+ snprint(buf, sizeof(buf)-1, "%s/lib/abaco.fonts", getenv("home"));
+ if((bp=Bopen(buf, OREAD)) == nil)
+ goto Default;
+
+ for(i=0; i<NumFnt; i++)
+ if((fontpaths[i]=Brdstr(bp, '\n', 1)) == nil)
+ goto Error;
+
+ Bterm(bp);
+ return;
+Error:
+ fprint(2, "abaco: not enough fontpaths in '%s'\n", buf);
+ Bterm(bp);
+ for(i--; i>=0; i--)
+ free(fontpaths[i]);
+Default:
+ for(i=0; i<NumFnt; i++)
+ fontpaths[i] = deffontpaths[i];
+}
+
+Font *
+getfont(int i)
+{
+ if(fonts[i] == nil){
+ fonts[i] = openfont(display, fontpaths[i]);
+ if(fonts[i] == nil)
+ error("can't open font file");
+ }
+ return fonts[i];
+}
+
+typedef struct Color Color;
+
+struct Color {
+ int rgb;
+ Image *i;
+ Color *next;
+};
+
+enum {
+ NHASH = 19,
+};
+
+static Color *colortab[NHASH];
+
+Image *
+getcolor(int rgb)
+{
+ Color *c;
+ int h;
+
+ if(rgb == 0xFFFFFF)
+ return display->white;
+ else if(rgb == 0x000000)
+ return display->black;
+
+ h = rgb%NHASH;
+ for(c=colortab[h]; c!=nil; c=c->next)
+ if(c->rgb == rgb){
+ flushimage(display, 0); /* BUG? */
+ return c->i;
+ }
+ c = emalloc(sizeof(Color));
+ c->i = eallocimage(display, Rect(0,0,1,1), screen->chan, 1, (rgb<<8)|0xFF);
+ c->rgb = rgb;
+ c->next = colortab[h];
+ colortab[h] = c;
+
+ return c->i;
+}
+
+int
+plumbrunestr(Runestr *rs, char *attr)
+{
+ Plumbmsg *m;
+ int i;
+
+ i = -1;
+ if(plumbsendfd >= 0){
+ m = emalloc(sizeof(Plumbmsg));
+ m->src = estrdup("abaco");
+ m->dst = nil;
+ m->wdir = estrdup("/tmp");
+ m->type = estrdup("text");
+ if(attr)
+ m->attr = plumbunpackattr(attr);
+ else
+ m->attr = nil;
+ m->data = smprint("%.*S", rs->nr, rs->r);
+ m->ndata = -1;
+ i = plumbsend(plumbsendfd, m);
+ plumbfree(m);
+ }
+ return i;
+}
+
+int
+hexdigit(int v)
+{
+ if(0<=v && v<=9)
+ return '0' + v;
+ else
+ return 'A' + v - 10;
+}
+
+static int
+inclass(char c, Rune* cl)
+{
+ int n, ans, negate, i;
+
+ n = runestrlen(cl);
+ if(n == 0)
+ return 0;
+ ans = 0;
+ negate = 0;
+ if(cl[0] == '^'){
+ negate = 1;
+ cl++;
+ n--;
+ }
+ for(i=0; i<n; i++){
+ if(cl[i]=='-' && i>0 && i<n-1){
+ if(c>=cl[i - 1] && c<=cl[i+1]){
+ ans = 1;
+ break;
+ }
+ i++;
+ }
+ else if(c == cl[i]){
+ ans = 1;
+ break;
+ }
+ }
+ if(negate)
+ ans = !ans;
+ return ans;
+}
+
+Rune*
+ucvt(Rune* s)
+{
+ Rune* u;
+ char *t;
+ int i, c, n, j, len;
+
+ t = smprint("%S", s);
+ n = strlen(t);
+ len = 0;
+ for(i=0; i<n; i++){
+ c = t[i];
+ if(inclass(c, L"- /$_@.!*'(),a-zA-Z0-9"))
+ len++;
+ else
+ len += 3;
+ }
+ u = runemalloc(len+1);
+ j = 0;
+
+ for(i=0; i<n; i++){
+ c = t[i];
+ if(inclass(c, L"-/$_@.!*'(),a-zA-Z0-9"))
+ u[j++] = c;
+ else if(c == ' ')
+ u[j++] = '+';
+ else {
+ u[j++] = '%';
+ u[j++] = hexdigit((c >> 4)&15);
+ u[j++] = hexdigit(c&15);
+ }
+ }
+ u[j] = 0;
+ free(t);
+ return u;
+}
+
+void
+reverseimages(Iimage **head)
+{
+ Iimage *r, *c, *n;
+
+ r = nil;
+ for(c=*head; c!=nil; c=n){
+ n = c->nextimage;
+ c->nextimage = r;
+ r = c;
+ }
+ *head = r;
+}
+
+char urlexpr[] = "^(https?|ftp|file|gopher|mailto|news|nntp|telnet|wais|"
+ "prospero)://([a-zA-Z0-9_@\\-]+([.:][a-zA-Z0-9_@\\-]+)*)";
+Reprog *urlprog;
+
+int
+validurl(Rune *r)
+{
+ Resub rs[10];
+
+ if(urlprog == nil){
+ urlprog = regcomp(urlexpr);
+ if(urlprog == nil)
+ error("regcomp");
+ }
+ memset(rs, 0, sizeof(rs));
+ if(rregexec(urlprog, r, rs, nelem(rs)) == 0)
+ return FALSE;
+ return TRUE;
+}
+
+void
+execproc(void *v)
+{
+ Channel *sync;
+ Exec *e;
+ int p[2], q[2];
+ char *cmd;
+
+ threadsetname("execproc");
+ e = v;
+ p[0] = e->p[0];
+ p[1] = e->p[1];
+ q[0] = e->q[0];
+ q[1] = e->q[1];
+ cmd = e->cmd;
+ sync = e->sync;
+ rfork(RFFDG);
+ free(e);
+ dup(p[0], 0);
+ close(p[0]);
+ close(p[1]);
+ if(q[0]){
+ dup(q[1], 1);
+ close(q[0]);
+ close(q[1]);
+ }
+ if(!procstderr)
+ close(2);
+ procexecl(sync, "/bin/rc", "rc", "-c", cmd, 0);
+ error("can't exec");
+}
+
+static void
+writeproc(void *v)
+{
+ Channel *sync;
+ void **a;
+ char *s;
+ long np;
+ int fd, i, n;
+
+ threadsetname("writeproc");
+ a = v;
+ sync = a[0];
+ fd = (int)a[1];
+ s = a[2];
+ np =(long)a[3];
+ free(a);
+
+ for(i=0; i<np; i+=n){
+ n = np-i;
+ if(n > BUFSIZE)
+ n = BUFSIZE;
+ if(write(fd, s+i, n) != n)
+ break;
+ }
+ close(fd);
+ sendul(sync, i);
+}
+
+struct {
+ char *mime;
+ char *tcs;
+}tcstab[] = {
+
+#include "tcs.h"
+
+ /* not generated by the script */
+ "euc_jp", "jis",
+ "euc_kr", "euc-k",
+ "windows-874", "tis",
+ nil, nil,
+};
+
+enum {
+ Winstart = 127,
+ Winend = 159
+};
+
+static int winchars[] = {
+ 8226, /* 8226 is a bullet */
+ 8226, 8226, 8218, 402, 8222, 8230, 8224, 8225,
+ 710, 8240, 352, 8249, 338, 8226, 8226, 8226,
+ 8226, 8216, 8217, 8220, 8221, 8226, 8211, 8212,
+ 732, 8482, 353, 8250, 339, 8226, 8226, 376
+};
+
+char *
+tcs(char *cs, char *s, long *np)
+{
+ Channel *sync;
+ Exec *e;
+ Rune r;
+ long i, n;
+ void **a;
+ uchar *us;
+ char buf[BUFSIZE], cmd[50];
+ char *t, *u;
+ int p[2], q[2];
+
+
+ if(s==nil || *s=='\0' || *np==0){
+ werrstr("tcs failed: no data");
+ return s;
+ }
+
+ if(cs == nil){
+ werrstr("tcs failed: no charset");
+ return s;
+ }
+
+ if(cistrncmp(cs, "utf-8", 5)==0 || cistrncmp(cs, "utf8", 4)==0)
+ return s;
+
+ for(i=0; tcstab[i].mime!=nil; i++)
+ if(cistrncmp(cs, tcstab[i].mime, strlen(tcstab[i].mime)) == 0)
+ break;
+
+ if(tcstab[i].mime == nil){
+ fprint(2, "abaco: charset: %s not supported\n", cs);
+ goto latin1;
+ }
+ if(cistrcmp(tcstab[i].tcs, "8859-1")==0 || cistrcmp(tcstab[i].tcs, "ascii")==0){
+latin1:
+ n = 0;
+ for(us=(uchar*)s; *us; us++)
+ n += runelen(*us);
+ n++;
+ t = emalloc(n);
+ for(us=(uchar*)s, u=t; *us; us++){
+ if(*us>=Winstart && *us<=Winend)
+ *u++ = winchars[*us-Winstart];
+ else{
+ r = *us;
+ u += runetochar(u, &r);
+ }
+ }
+ *u = 0;
+ free(s);
+ return t;
+ }
+
+ if(pipe(p)<0 || pipe(q)<0)
+ error("can't create pipe");
+
+ sync = chancreate(sizeof(ulong), 0);
+ if(sync == nil)
+ error("can't create channel");
+
+ snprint(cmd, sizeof cmd, "tcs -f %s", tcstab[i].tcs);
+ e = emalloc(sizeof(Exec));
+ e->p[0] = p[0];
+ e->p[1] = p[1];
+ e->q[0] = q[0];
+ e->q[1] = q[1];
+ e->cmd = cmd;
+ e->sync = sync;
+ proccreate(execproc, e, STACK);
+ recvul(sync);
+ chanfree(sync);
+ close(p[0]);
+ close(q[1]);
+
+ /* in case tcs fails */
+ t = s;
+ sync = chancreate(sizeof(ulong), 0);
+ if(sync == nil)
+ error("can't create channel");
+
+ a = emalloc(4*sizeof(void *));
+ a[0] = sync;
+ a[1] = (void *)p[1];
+ a[2] = s;
+ a[3] = (void *)*np;
+ proccreate(writeproc, a, STACK);
+
+ s = nil;
+ while((n = read(q[0], buf, sizeof(buf))) > 0){
+ s = erealloc(s, i+n+1);
+ memmove(s+i, buf, n);
+ i += n;
+ s[i] = '\0';
+ }
+ n = recvul(sync);
+ if(n != *np)
+ fprint(2, "tcs: did not write %ld; wrote %uld\n", *np, n);
+
+ *np = i;
+ chanfree(sync);
+ close(q[0]);
+
+ if(s == nil){
+ fprint(2, "tcs failed: can't convert charset=%s to %s\n", cs, tcstab[i].tcs);
+ return t;
+ }
+ free(t);
+
+ return s;
+}
+
+static
+int
+isspace(char c)
+{
+ return c==' ' || c== '\t' || c=='\r' || c=='\n';
+}
+
+static
+int
+findctype(char *b, int l, char *keyword, char *s)
+{
+ char *p, *e;
+ int i;
+
+ p = cistrstr(s, keyword);
+ if(!p)
+ return -1;
+ p += strlen(keyword);
+ while(*p && isspace(*p))
+ p++;
+ if(*p != '=')
+ return -1;
+ p++;
+ while(*p && isspace(*p))
+ p++;
+ if(!*p)
+ return -1;
+ if(*p == '"'){
+ p++;
+ e = strchr(p, '"');
+ if(!e)
+ return -1;
+ }else
+ for(e = p; *e < 127 && *e > ' ' ; e++)
+ ;
+ i = e-p;
+ if(i < 1)
+ return -1;
+ snprint(b, l, "%.*s", i, p);
+ return 0;
+}
+
+static
+int
+finddocctype(char *b, int l, char *s)
+{
+ char *p, *e;
+
+ p = cistrstr(s, "<meta");
+ if(!p)
+ return -1;
+ p += 5;
+ e = strchr(s, '>');
+ if(!e)
+ return -1;
+ snprint(b, l, "%.*s", (int)(e-p), p);
+ return 0;
+}
+
+static
+int
+findxmltype(char *b, int l, char *s)
+{
+ char *p, *e;
+
+ p = cistrstr(s, "<?xml ");
+ if(!p)
+ return -1;
+
+ p += 6;
+ e = strstr(p, "?>");
+ if(!e)
+ return -1;
+ snprint(b, l, "%.*s", (int)(e-p), p);
+
+ return 0;
+}
+
+/*
+ * servers can lie about lie about the charset,
+ * so we use the charset based on the priority.
+ */
+char *
+convert(Runestr ctype, char *s, long *np)
+{
+ char t[25], buf[256];
+
+ *t = '\0';
+ if(ctype.nr){
+ snprint(buf, sizeof(buf), "%.*S", ctype.nr, ctype.r);
+ findctype(t, sizeof(t), "charset", buf);
+ }
+ if(findxmltype(buf, sizeof(buf), s)==0)
+ findctype(t, sizeof(t), "encoding", buf);
+ if(finddocctype(buf, sizeof(buf), s) == 0)
+ findctype(t, sizeof(t), "charset", buf);
+
+ if(*t == '\0')
+ strcpy(t, charset);
+ return tcs(t, s, np);
+}
+
+int
+xtofchar(Rune *s, Font *f, long p)
+{
+ Rune *r;
+ int q;
+
+ if(p == 0)
+ return 0;
+
+ q = 0;
+ for(r=s; *r!=L'\0'; r++){
+ p -= runestringnwidth(f, r, 1);
+ if(p < 0)
+ break;
+ q++;
+ }
+ return q;
+}
+
+int
+istextsel(Page *p, Rectangle r, int *q0, int *q1, Rune *s, Font *f)
+{
+ int topinr, botinr;
+
+ *q0 = *q1 = 0;
+ topinr= ptinrect(p->top, r);
+ if(topinr || (r.min.y>p->top.y && r.max.y<p->bot.y))
+ p->selecting = TRUE;
+ botinr = ptinrect(p->bot, r);
+ if(botinr || r.min.y>p->bot.y)
+ p->selecting = FALSE;
+
+ if(topinr || botinr){
+ if(topinr)
+ *q0 = xtofchar(s, f, p->top.x-r.min.x);
+ if(botinr)
+ *q1 = xtofchar(s, f, p->bot.x-r.min.x);
+ if(*q0!=0 || *q1!=0)
+ return TRUE;
+ }
+ return p->selecting;
+}
+
+Point
+getpt(Page *p, Point xy)
+{
+ xy.x = xy.x-p->r.min.x+p->pos.x;
+ xy.y = xy.y-p->r.min.y+p->pos.y;
+
+ return xy;
+}
+
+void
+getimage(Cimage *ci, Rune *altr)
+{
+ Rectangle r;
+ Memimage *mi;
+ Image *i, *i2;
+ char buf[128];
+ uchar *bits;
+ int nbits;
+
+ mi = ci->mi;
+ if(mi == nil){
+ snprint(buf, sizeof(buf), "[%S]", altr ? altr : L"IMG");
+ r.min = Pt(0, 0);
+ r.max.x = 2*Space + stringwidth(font, buf);
+ r.max.y = 2*Space + font->height;
+ ci->i = eallocimage(display, r, GREY1, 1, DBlack);
+ r.min.x += Space;
+ r.min.y += Space;
+ string(ci->i, r.min, display->white, ZP, font, buf);
+ return;
+ }
+ nbits = bytesperline(mi->r, mi->depth)*Dy(mi->r);
+ bits = emalloc(nbits);
+ unloadmemimage(mi, mi->r, bits, nbits);
+/*
+ /* get rid of alpha channel from transparent gif * /
+
+ if(mi->depth == 16){
+ for(y=1; y<nbits; y+=2)
+ bits[y>>1] = bits[y];
+ }
+*/
+ i = eallocimage(display, mi->r, mi->chan, 0, DNofill);
+ loadimage(i, i->r, bits, nbits);
+ i2 = eallocimage(display, i->r, RGB24, 1, DNofill);
+ draw(i2, i2->r, display->black, nil, ZP);
+ draw(i2, i2->r, i, nil, i->r.min);
+ free(bits);
+ freememimage(mi);
+ freeimage(i);
+ ci->i = i2;
+ ci->mi = nil;
+}
+
+static
+void
+fixtext1(Item **list)
+{
+ Itext *text, *ntext;
+ Item *it, *prev;
+ Rune *s, *s1, *s2;
+ int n;
+
+ if(*list == nil)
+ return;
+
+ prev = nil;
+ for(it=*list; it!=nil; it=prev->next){
+ if(it->tag!=Itexttag || forceitem(it))
+ goto Continue;
+
+ text = (Itext *)it;
+ s = text->s;
+ while(*s && isspacerune(*s))
+ s++;
+ if(!*s){
+ if(prev == nil)
+ prev = *list = it->next;
+ else
+ prev->next = it->next;
+
+ it->next = nil;
+ freeitems(it);
+ if(prev == nil)
+ return;
+ continue;
+ }
+ n = 0;
+ while(s[n] && !isspacerune(s[n]))
+ n++;
+
+ if(!s[n])
+ goto Continue;
+
+ s1 = runemalloc(n+1);
+ s1 = runemove(s1, s, n);
+ s1[n] = L'\0';
+ s += n;
+
+ while(*s && isspacerune(*s))
+ s++;
+
+ if(*s){
+ n = runestrlen(s);
+ s2 = runemalloc(n+1);
+ runemove(s2, s, n);
+ s2[n] = L'\0';
+ ntext = emalloc(sizeof(Itext));
+ ntext->s = s2;
+ ntext->ascent = text->ascent;
+ ntext->anchorid = text->anchorid;
+ ntext->state = text->state&~(IFbrk|IFbrksp|IFnobrk|IFcleft|IFcright);
+ ntext->tag = text->tag;
+ ntext->fnt = text->fnt;
+ ntext->fg = text->fg;
+ ntext->ul = text->ul;
+ ntext->next = (Item *)text->next;
+ text->next = (Item *)ntext;
+ }
+ free(text->s);
+ text->s = s1;
+ Continue:
+ prev = it;
+ }
+}
+
+void
+fixtext(Page *p)
+{
+ Tablecell *c;
+ Table *t;
+
+ fixtext1(&p->items);
+ for(t=p->doc->tables; t!=nil; t=t->next)
+ for(c=t->cells; c!=nil; c=c->next)
+ fixtext1(&c->content);
+}
+
+typedef struct Refresh Refresh;
+
+struct Refresh
+{
+ Page *p;
+ Refresh *next;
+};
+
+static Refresh *refreshs = nil;
+static QLock refreshlock;
+
+void
+addrefresh(Page *p, char *fmt, ...)
+{
+ Refresh *r;
+ Rune *s;
+ va_list arg;
+
+ if(p->aborting)
+ return;
+
+ va_start(arg, fmt);
+ s = runevsmprint(fmt, arg);
+ va_end(arg);
+ if(s == nil)
+ error("runevsmprint failed");
+
+ if(p->status){
+ free(p->status);
+ p->status = nil;
+ }
+ p->status = s;
+ qlock(&refreshlock);
+ for(r=refreshs; r!=nil; r=r->next)
+ if(r->p == p)
+ goto Return;
+
+ incref(p->w); /* flushrefresh will decref */
+ r = emalloc(sizeof(Refresh));
+ r->p = p;
+ r->next = refreshs;
+ refreshs = r;
+
+ Return:
+ nbsendp(crefresh, nil);
+ qunlock(&refreshlock);
+}
+
+/* called while row is locked */
+void
+flushrefresh(void)
+{
+ Refresh *r, *next;
+ Page *p;
+
+ qlock(&refreshlock);
+ for(r=refreshs; r!=nil; r=next){
+ p = r->p;
+ if(p->changed==TRUE && p->aborting==FALSE){
+ p->changed = FALSE;
+ if(p->parent==nil || p->loading==FALSE)
+ pagerender(p);
+ if(!p->refresh.t)
+ pagesetrefresh(p);
+ }
+ if(p->status){
+ winsetstatus(p->w, p->status);
+ free(p->status);
+ p->status = nil;
+ }
+ winseturl(p->w);
+ winsettag(p->w);
+ decref(p->w);
+ next = r->next;
+ free(r);
+ }
+ refreshs = nil;
+ qunlock(&refreshlock);
+}
+
+void
+savemouse(Window *w)
+{
+ prevmouse = mouse->xy;
+ mousew = w;
+}
+
+void
+restoremouse(Window *w)
+{
+ if(mousew!=nil && mousew==w)
+ moveto(mousectl, prevmouse);
+ mousew = nil;
+}
+
+void
+clearmouse()
+{
+ mousew = nil;
+}
+
+/*
+ * Heuristic city.
+ */
+Window*
+makenewwindow(Page *p)
+{
+ Column *c;
+ Window *w, *bigw, *emptyw;
+ Page *emptyp;
+ int i, y, el;
+
+ if(activecol)
+ c = activecol;
+ else if(selpage && selpage->col)
+ c = selpage->col;
+ else if(p && p->col)
+ c = p->col;
+ else{
+ if(row.ncol==0 && rowadd(&row, nil, -1)==nil)
+ error("can't make column");
+ c = row.col[row.ncol-1];
+ }
+ activecol = c;
+ if(p==nil || p->w==nil || c->nw==0)
+ return coladd(c, nil, nil, -1);
+
+ /* find biggest window and biggest blank spot */
+ emptyw = c->w[0];
+ bigw = emptyw;
+ for(i=1; i<c->nw; i++){
+ w = c->w[i];
+ /* use >= to choose one near bottom of screen */
+ if(Dy(w->page.all) >= Dy(bigw->page.all))
+ bigw = w;
+ if(w->page.lay==nil && Dy(w->page.all) >= Dy(emptyw->page.all))
+ emptyw = w;
+ }
+ emptyp = &emptyw->page;
+ el = Dy(emptyp->all);
+ /* if empty space is big, use it */
+ if(el>15 || (el>3 && el>(Dy(bigw->page.all)-1)/2))
+ y = emptyp->all.max.y;
+ else{
+ /* if this window is in column and isn't much smaller, split it */
+ if(p->col==c && Dy(p->w->r)>2*Dy(bigw->r)/3)
+ bigw = p->w;
+ y = (bigw->r.min.y + bigw->r.max.y)/2;
+ }
+ w = coladd(c, nil, nil, y);
+ colgrow(w->col, w, 1);
+ return w;
+}
diff --git a/sys/src/cmd/abaco/wind.c b/sys/src/cmd/abaco/wind.c
new file mode 100755
index 000000000..fd108e75f
--- /dev/null
+++ b/sys/src/cmd/abaco/wind.c
@@ -0,0 +1,383 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <memdraw.h>
+#include <thread.h>
+#include <cursor.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include <frame.h>
+#include <plumb.h>
+#include <html.h>
+#include "dat.h"
+#include "fns.h"
+
+void
+wininit(Window *w, Window *, Rectangle r)
+{
+ Rectangle r1, br;
+
+ incref(w);
+ w->r = r;
+ w->tag.w = w;
+ w->url.w = w;
+ w->page.w = w;
+ w->status.w = w;
+ r1 = r;
+ r1.max.y = r1.min.y + font->height;
+ textinit(&w->tag, screen, r1, font, tagcols);
+ w->tag.what = Tag;
+ r1.min.y = r1.max.y++;
+ draw(screen, r1, tagcols[BORD], nil, ZP);
+ br.min = w->tag.scrollr.min;
+ br.max.x = br.min.x + Dx(button->r);
+ br.max.y = br.min.y + Dy(button->r);
+ draw(screen, br, button, nil, button->r.min);
+ r1.min.y = r1.max.y;
+ r1.max.y += font->height;
+ textinit(&w->url, screen, r1, font, tagcols);
+ w->url.
+ w->url.what = Urltag;
+ r1.min.y = r1.max.y++;
+ draw(screen, r1, tagcols[BORD], nil, ZP);
+ r1.min.y = r1.max.y;
+ r1.max.y = r.max.y - font->height - 1;
+ w->page.all = r1;
+ w->page.b = screen;
+ draw(screen, r1, display->white, nil, ZP);
+ r1.min.y = r1.max.y++;
+ draw(screen, r1, tagcols[BORD], nil, ZP);
+ r1.min.y = r1.max.y;
+ r1.max.y += font->height;
+ textinit(&w->status, screen, r1, font, tagcols);
+ w->status.what = Statustag;
+}
+
+int
+winresize(Window *w, Rectangle r, int safe)
+{
+ Rectangle r1, br;
+
+ w->r = r;
+ r1 = r;
+ r1.max.y = r1.min.y + font->height;
+ if(!safe || !eqrect(w->tag.r, r1)){
+ textresize(&w->tag, screen, r1);
+ br.min = w->tag.scrollr.min;
+ br.max.x = r1.min.x + Dx(button->r);
+ br.max.y = r1.min.y + Dy(button->r);
+ draw(screen, br, button, nil, button->r.min);
+ r1.min.y = r1.max.y++;
+ draw(screen, r1, tagcols[BORD], nil, ZP);
+ r1.min.y = r1.max.y;
+ r1.max.y += font->height;
+ textresize(&w->url, screen, r1);
+ r1.min.y = r1.max.y++;
+ draw(screen, r1, tagcols[BORD], nil, ZP);
+ }
+ r1.min.y = r1.max.y;
+ r1.max.y = r.max.y - font->height - 1;
+ w->page.b = screen;
+ if(!safe || !eqrect(w->page.all, r1)){
+ if(Dy(r1) <= 0){
+ w->page.all = ZR;
+ pagerender(&w->page);
+ w->r = r;
+ w->r.max.y = r1.min.y;
+ return w->r.max.y;
+ }
+ draw(screen, r1, display->white, nil, ZP);
+ w->page.all = r1;
+ pagerender(&w->page);
+ r1.min.y = r1.max.y++;
+ draw(screen, r1, tagcols[BORD], nil, ZP);
+ r1.min.y = r1.max.y;
+ r1.max.y = r.max.y;
+ textresize(&w->status, screen, r1);
+ }
+ return w->r.max.y;
+}
+
+void
+winclose1(Window *w)
+{
+ int i;
+
+ if(decref(w) == 0){
+ textclose(&w->tag);
+ textclose(&w->url);
+ textclose(&w->status);
+ if(w->history.url){
+ for(i=0; i<w->history.nurl; i++)
+ urlfree(w->history.url[i]);
+ free(w->history.url);
+ }
+ free(w);
+ }
+}
+
+void
+winclose(Window *w)
+{
+ pageclose(&w->page);
+ winclose1(w);
+}
+
+void
+winlock(Window *w, int owner)
+{
+ incref(w);
+ qlock(w);
+ w->owner = owner;
+}
+
+void
+winunlock(Window *w)
+{
+ w->owner = 0;
+ qunlock(w);
+ winclose1(w);
+}
+
+void
+winsettag1(Window *w)
+{
+ int i, j, k, n, bar;
+ Rune *new, *r;
+ Image *b;
+ uint q0, q1;
+ Rectangle br;
+ Runestr old;
+
+ memset(&old, 0, sizeof(Runestr));
+ copyrunestr(&old, &w->tag.rs);
+ for(i=0; i<w->tag.rs.nr; i++)
+ if(old.r[i]==' ' || old.r[i]=='\t')
+ break;
+
+ if(runestreq(old, w->page.title) == FALSE){
+ textdelete(&w->tag, 0, i);
+ textinsert(&w->tag, 0, w->page.title.r, w->page.title.nr);
+ closerunestr(&old);
+ copyrunestr(&old, &w->tag.rs);
+ }
+ new = runemalloc(w->page.title.nr+100);
+ i = 0;
+ runemove(new+i, L" Del Snarf", 10);
+ i += 10;
+ if(w->history.nurl){
+ if(w->history.cid > 0){
+ runemove(new+i, L" Back", 5);
+ i += 5;
+ }
+ if(w->history.cid < w->history.nurl-1){
+ runemove(new+i, L" Next", 5);
+ i += 5;
+ }
+ if(w->page.loading){
+ runemove(new+i, L" Stop", 5);
+ i += 5;
+ }
+ }
+ runemove(new+i, L" Get", 4);
+ i += 4;
+ runemove(new+i, L" | ", 3);
+ i += 3;
+ runemove(new+i, w->page.title.r, w->page.title.nr);
+ i += w->page.title.nr;
+/*
+ r = runestrchr(old.r, '|');
+ r = nil;
+ if(r)
+ k = r-old.r+1;
+ else{
+ k = w->tag.rs.nr;
+ if(w->page.url){
+ runemove(new+i, L" Look ", 6);
+ i += 6;
+ }
+ }
+*/
+ k = w->tag.rs.nr;
+ if(runeeq(new, i, old.r, k) == FALSE){
+ n = k;
+ if(n > i)
+ n = i;
+ for(j=0; j<n; j++)
+ if(old.r[j] != new[j])
+ break;
+ q0 = w->tag.q0;
+ q1 = w->tag.q1;
+ textdelete(&w->tag, j, k);
+ textinsert(&w->tag, j, new+j, i-j);
+ /* try to preserve user selection */
+ r = runestrchr(old.r, '|');
+ if(r){
+ bar = r-old.r;
+ if(q0 > bar){
+ bar = (runestrchr(new, '|')-new)-bar;
+ w->tag.q0 = q0+bar;
+ w->tag.q1 = q1+bar;
+ }
+ }
+ }
+ closerunestr(&old);
+ free(new);
+ n = w->tag.rs.nr;
+ if(w->tag.q0 > n)
+ w->tag.q0 = n;
+ if(w->tag.q1 > n)
+ w->tag.q1 = n;
+ textsetselect(&w->tag, w->tag.q0, w->tag.q1);
+ b = button;
+ br.min = w->tag.scrollr.min;
+ br.max.x = br.min.x + Dx(b->r);
+ br.max.y = br.min.y + Dy(b->r);
+ draw(screen, br, b, nil, b->r.min);
+}
+
+
+void
+winsettag(Window *w)
+{
+ if(w->col && w->col->safe)
+ winsettag1(w);
+}
+
+void
+winseturl(Window *w)
+{
+ if(w->page.url && runestreq(w->url.rs, w->page.url->act)==FALSE)
+ textset(&w->url, w->page.url->act.r, w->page.url->act.nr);
+}
+
+void
+winsetstatus(Window *w, Rune *r)
+{
+ if(w->col && w->col->safe)
+ textset(&w->status, r, runestrlen(r));
+}
+
+void
+winaddhist(Window *w, Url *u)
+{
+ Url **url;
+ int cid, n, i;
+
+ url = w->history.url;
+ n = w->history.nurl;
+ cid = w->history.cid;
+ if(cid < n-1){
+ for(i=cid+1; i<n; i++)
+ urlfree(url[i]);
+ n = cid+1;
+ }
+ w->history.url = erealloc(w->history.url, ++n*sizeof(Url *));
+ w->history.url[n-1] = u;
+ w->history.cid = u->id = n-1;
+ w->history.nurl = n;
+ incref(u);
+}
+
+void
+wingohist(Window *w, int isnext)
+{
+ Page *p;
+ int n, id;
+
+ n = w->history.nurl;
+ p = &w->page;
+ if(!p->url)
+ return;
+
+ id = p->url->id;
+
+ if(isnext)
+ id++;
+ else
+ id--;
+
+ if(n==0 || id<0 || id==n)
+ return;
+
+ incref(w->history.url[id]);
+ pageload(p, w->history.url[id], FALSE);
+ w->history.cid = id;
+}
+
+Text *
+wintext(Window *w, Point xy)
+{
+ w->inpage = FALSE;
+ if(ptinrect(xy, w->tag.all))
+ return &w->tag;
+ if(ptinrect(xy, w->url.all))
+ return &w->url;
+ if(ptinrect(xy, w->status.all))
+ return &w->status;
+ if(ptinrect(xy, w->page.all))
+ w->inpage = TRUE;
+
+ return nil;
+}
+
+Text *
+wintype(Window *w, Point xy, Rune r)
+{
+ Text *t;
+
+ t = wintext(w, xy);
+ if(t && !ptinrect(xy, t->scrollr))
+ return t;
+ if(w->inpage)
+ pagetype(&w->page, r, xy);
+
+ return nil;
+}
+
+Text *
+winmouse(Window *w, Point xy, int but)
+{
+ Text *t;
+
+ t = wintext(w, xy);
+ if(t)
+ return t;
+ if(w->inpage)
+ pagemouse(&w->page, xy, but);
+
+ return nil;
+}
+
+void
+winmousebut(Window *w)
+{
+ moveto(mousectl, divpt(addpt(w->tag.scrollr.min, w->tag.scrollr.max), 2));
+}
+
+int
+winclean(Window *, int)
+{
+ return TRUE;
+}
+
+void
+windebug(Window *w)
+{
+ Page *p;
+ int i;
+
+ p = &w->page;
+ fprint(2, "title:\t%S\n", p->title.r);
+ fprint(2, "url:\t%.*S\n",w->url.rs.nr, w->url.rs.r);
+ fprint(2, "aborting:\t%s\n", istrue(p->aborting));
+ fprint(2, "changed:\t%s\n", istrue(p->changed));
+ fprint(2, "loading:\t%s\n", istrue(p->loading));
+ fprint(2, "status:\t%S\n", p->status);
+ fprint(2, "HISTORY:\n");
+ for(i=0; i<w->history.nurl; i++)
+ fprint(2, "url[%d]: %S\n", i, w->history.url[i]->act.r);
+
+ if(p->kidinfo)
+ fprint(2, "name: %S\n", p->kidinfo->name);
+}