/* * Copyright (c) 2021 joshua stein * * 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; * * [..] * te = TEStylNew(&bounds, &bounds); * TEAutoView(true, te); * TETabEnable(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 #include #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 { cpos++; } } if (startdraw != i) 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; cpos++; 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 } }