jcs
/amend
/amendments
/13
tetab: Add standalone tab handling module for TextEdit controls
This uses TE's TECustomHook functionality to hook into width
calculation, drawing, and hit testing to expand tabs into a
configurable number of spaces (defaulting to 4). This requires
using the new styled TextEdit control available in System 6+.
jcs made amendment 13 over 3 years ago
--- tetab.c Thu Oct 21 17:10:02 2021
+++ tetab.c Thu Oct 21 17:10:02 2021
@@ -0,0 +1,243 @@
+/*
+ * Copyright (c) 2021 joshua stein <jcs@jcs.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * Implements tab spacing in TextEdit controls with a fixed-width font.
+ * Only works on styled TextEdit controls (created with TEStylNew) and
+ * assumes that functions get called with a text buffer that starts at the
+ * beginning of the line.
+ *
+ * Usage is per-TextEdit control, so these hooks need to be established
+ * on all controls where tabs should be expanded:
+ *
+ * TEHandle te;
+ * ProcPtr teproc;
+ *
+ * [..]
+ * your_te = TEStylNew(&bounds, &bounds);
+ * TEAutoView(true, te);
+ * teproc = StripAddress(TETabDrawHook);
+ * TECustomHook(intDrawHook, &teproc, te);
+ * teproc = StripAddress(TETabWidthHook);
+ * TECustomHook(intWidthHook, &teproc, te);
+ * teproc = StripAddress(TETabHitTestHook);
+ * TECustomHook(intHitTestHook, &teproc, te);
+ *
+ * The tab width is set to 4 by default in the TETabWidth global.
+ *
+ * Thanks to Ari Halberstadt for their 1989 Usenet post on
+ * comp.sys.mac.programmer with HitTestHook assembly.
+ */
+
+#include <Script.h>
+#include <SetUpA4.h>
+
+#include "tetab.h"
+
+pascal void TETabDrawHook(void);
+pascal void TETabWidthHook(void);
+pascal void TETabHitTestHook(void);
+
+short TETabWidth = 4;
+
+void
+TETabEnable(TEHandle te)
+{
+ ProcPtr teproc;
+
+ TEAutoView(true, te);
+ teproc = StripAddress(TETabWidthHook);
+ TECustomHook(intWidthHook, &teproc, te);
+ teproc = StripAddress(TETabDrawHook);
+ TECustomHook(intDrawHook, &teproc, te);
+ teproc = StripAddress(TETabHitTestHook);
+ TECustomHook(intHitTestHook, &teproc, te);
+}
+
+pascal void
+TETabDrawHook(void)
+{
+ /* entry registers, in order */
+ short offset;
+ short length;
+ Ptr text;
+
+ /* locals */
+ short i, cwidth, cpos, space, startdraw;
+ char c;
+
+ asm {
+ movem.l d3-d7/a0-a3, -(a7)
+ move.w d0, offset
+ move.w d1, length
+ move.l a0, text
+ }
+ SetUpA4();
+
+ cwidth = CharWidth(' ');
+
+ /*
+ * This startdraw stuff is to try to draw long blocks of non-tab text
+ * with one call to DrawText() instead of DrawChar()ing every character
+ * one at a time, for speed.
+ */
+ for (i = 0, cpos = 0, startdraw = 0; i < length; i++) {
+ c = text[offset + i];
+
+ if (c == '\t') {
+ if (i - startdraw)
+ DrawText(text, startdraw, i - startdraw);
+
+ space = (TETabWidth - (cpos % TETabWidth));
+ if (!space)
+ space = TETabWidth;
+ cpos += space;
+ Move(cwidth * space, 0);
+ startdraw = i + 1;
+ } else {
+ //DrawChar(c);
+ cpos++;
+ }
+ }
+
+ if (startdraw < i - 1)
+ DrawText(text, startdraw, i - startdraw);
+
+ /* return nothing */
+
+ /* restore registers */
+ RestoreA4();
+ asm {
+ movem.l (a7)+, d3-d7/a0-a3
+ }
+}
+
+pascal void
+TETabWidthHook(void)
+{
+ /* entry registers, in order */
+ short length;
+ short offset;
+ Ptr text;
+
+ /* exit */
+ short width;
+
+ short i, cwidth, cpos, space;
+ char c;
+
+ asm {
+ movem.l d3-d7/a0-a3, -(a7)
+ move.w d0, length
+ move.w d1, offset
+ move.l a0, text
+ }
+ SetUpA4();
+
+ cwidth = CharWidth(' ');
+
+ for (i = 0, cpos = 0; i < length; i++) {
+ c = text[offset + i];
+
+ if (c == '\t') {
+ space = (TETabWidth - (cpos % TETabWidth));
+ if (!space)
+ space = TETabWidth;
+ cpos += space;
+ } else {
+ cpos++;
+ }
+ }
+
+ width = cwidth * cpos;
+
+ /* return length */
+ asm {
+ move.w width, d1
+ }
+
+ /* restore registers */
+ RestoreA4();
+ asm {
+ movem.l (a7)+, d3-d7/a0-a3
+ }
+}
+
+pascal void
+TETabHitTestHook(void)
+{
+ /* entry registers, in order */
+ short length;
+ short poffset;
+ Ptr text;
+
+ /* exit */
+ short width;
+ short offset = 0;
+ short leftside = 0;
+
+ /* locals */
+ short i, cwidth, cpos, wpos, space, found;
+ char c;
+
+ asm {
+ movem.l d3-d7/a0-a3, -(a7)
+ move.w d0, length
+ move.w d1, poffset
+ move.l a0, text
+ }
+ SetUpA4();
+
+ cwidth = CharWidth(' ');
+
+ for (i = 0, wpos = 0, cpos = 0, found = 0; i < length; i++) {
+ c = text[i];
+
+ if (c == '\t') {
+ space = (TETabWidth - (wpos % TETabWidth));
+ if (!space)
+ space = TETabWidth;
+ wpos += space;
+ } else if (c == '\r')
+ break;
+ else
+ wpos++;
+
+ cpos++;
+
+ if (poffset <= (wpos * cwidth)) {
+ found = 1;
+ break;
+ }
+ }
+
+ /* put found in hi word */
+ width = (wpos * cwidth) | (found << 8);
+ offset = cpos;
+ leftside = (poffset < (width - (cwidth / 2)));
+
+ asm {
+ move.w width, d0
+ move.w offset, d1
+ move.w leftside, d2
+ }
+
+ /* restore registers */
+ RestoreA4();
+ asm {
+ movem.l (a7)+, d3-d7/a0-a3
+ }
+}
--- tetab.h Thu Oct 21 14:04:42 2021
+++ tetab.h Thu Oct 21 14:04:42 2021
@@ -0,0 +1,18 @@
+/*
+ * Copyright (c) 2021 joshua stein <jcs@jcs.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+void TETabEnable(TEHandle te);
+extern short TETabWidth;