Download
jcs
/amend
/tetab.c
(View History)
jcs tetab: Tweak selection hit testing, move over half a char width | Latest amendment: 59 on 2022-02-03 |
1 | /* |
2 | * Copyright (c) 2021 joshua stein <jcs@jcs.org> |
3 | * |
4 | * Permission to use, copy, modify, and distribute this software for any |
5 | * purpose with or without fee is hereby granted, provided that the above |
6 | * copyright notice and this permission notice appear in all copies. |
7 | * |
8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR |
11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN |
13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF |
14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
15 | */ |
16 | |
17 | /* |
18 | * Implements tab spacing in TextEdit controls with a fixed-width font. |
19 | * Only works on styled TextEdit controls (created with TEStylNew) and |
20 | * assumes that functions get called with a text buffer that starts at the |
21 | * beginning of the line. |
22 | * |
23 | * Usage is per-TextEdit control, so these hooks need to be established |
24 | * on all controls where tabs should be expanded: |
25 | * |
26 | * TEHandle te; |
27 | * ProcPtr teproc; |
28 | * |
29 | * [..] |
30 | * te = TEStylNew(&bounds, &bounds); |
31 | * TEAutoView(true, te); |
32 | * TETabEnable(te); |
33 | * |
34 | * The tab width is set to 4 by default in the TETabWidth global. |
35 | * |
36 | * Thanks to Ari Halberstadt for their 1989 Usenet post on |
37 | * comp.sys.mac.programmer with HitTestHook assembly. |
38 | */ |
39 | |
40 | #include <Script.h> |
41 | #include <SetUpA4.h> |
42 | |
43 | #include "tetab.h" |
44 | |
45 | pascal void TETabDrawHook(void); |
46 | pascal void TETabWidthHook(void); |
47 | pascal void TETabHitTestHook(void); |
48 | |
49 | short TETabWidth = 4; |
50 | |
51 | void |
52 | TETabEnable(TEHandle te) |
53 | { |
54 | ProcPtr teproc; |
55 | |
56 | TEAutoView(true, te); |
57 | teproc = StripAddress(TETabWidthHook); |
58 | TECustomHook(intWidthHook, &teproc, te); |
59 | teproc = StripAddress(TETabDrawHook); |
60 | TECustomHook(intDrawHook, &teproc, te); |
61 | teproc = StripAddress(TETabHitTestHook); |
62 | TECustomHook(intHitTestHook, &teproc, te); |
63 | } |
64 | |
65 | pascal void |
66 | TETabDrawHook(void) |
67 | { |
68 | /* entry registers, in order */ |
69 | short offset; |
70 | short length; |
71 | Ptr text; |
72 | |
73 | /* locals */ |
74 | short i, cwidth, cpos, space, startdraw; |
75 | char c; |
76 | |
77 | asm { |
78 | movem.l d3-d7/a0-a3, -(a7) |
79 | move.w d0, offset |
80 | move.w d1, length |
81 | move.l a0, text |
82 | } |
83 | SetUpA4(); |
84 | |
85 | cwidth = CharWidth(' '); |
86 | |
87 | /* |
88 | * This startdraw stuff is to try to draw long blocks of non-tab text |
89 | * with one call to DrawText() instead of DrawChar()ing every character |
90 | * one at a time, for speed. |
91 | */ |
92 | for (i = 0, cpos = 0, startdraw = 0; i < length; i++) { |
93 | c = text[offset + i]; |
94 | |
95 | if (c == '\t') { |
96 | if (i - startdraw) |
97 | DrawText(text, startdraw, i - startdraw); |
98 | |
99 | space = (TETabWidth - (cpos % TETabWidth)); |
100 | if (!space) |
101 | space = TETabWidth; |
102 | cpos += space; |
103 | Move(cwidth * space, 0); |
104 | startdraw = i + 1; |
105 | } else { |
106 | cpos++; |
107 | } |
108 | } |
109 | |
110 | if (startdraw != i) |
111 | DrawText(text, startdraw, i - startdraw); |
112 | |
113 | /* return nothing */ |
114 | |
115 | /* restore registers */ |
116 | RestoreA4(); |
117 | asm { |
118 | movem.l (a7)+, d3-d7/a0-a3 |
119 | } |
120 | } |
121 | |
122 | pascal void |
123 | TETabWidthHook(void) |
124 | { |
125 | /* entry registers, in order */ |
126 | short length; |
127 | short offset; |
128 | Ptr text; |
129 | |
130 | /* exit */ |
131 | short width; |
132 | |
133 | short i, cwidth, cpos, space; |
134 | char c; |
135 | |
136 | asm { |
137 | movem.l d3-d7/a0-a3, -(a7) |
138 | move.w d0, length |
139 | move.w d1, offset |
140 | move.l a0, text |
141 | } |
142 | SetUpA4(); |
143 | |
144 | cwidth = CharWidth(' '); |
145 | |
146 | for (i = 0, cpos = 0; i < length; i++) { |
147 | c = text[offset + i]; |
148 | |
149 | if (c == '\t') { |
150 | space = (TETabWidth - (cpos % TETabWidth)); |
151 | if (!space) |
152 | space = TETabWidth; |
153 | cpos += space; |
154 | } else { |
155 | cpos++; |
156 | } |
157 | } |
158 | |
159 | width = cwidth * cpos; |
160 | |
161 | /* return length */ |
162 | asm { |
163 | move.w width, d1 |
164 | } |
165 | |
166 | /* restore registers */ |
167 | RestoreA4(); |
168 | asm { |
169 | movem.l (a7)+, d3-d7/a0-a3 |
170 | } |
171 | } |
172 | |
173 | pascal void |
174 | TETabHitTestHook(void) |
175 | { |
176 | /* entry registers, in order */ |
177 | short length; |
178 | short poffset; |
179 | Ptr text; |
180 | |
181 | /* exit */ |
182 | short width; |
183 | short offset = 0; |
184 | short leftside = 0; |
185 | |
186 | /* locals */ |
187 | short i, cwidth, cpos, wpos, space, found; |
188 | char c; |
189 | |
190 | asm { |
191 | movem.l d3-d7/a0-a3, -(a7) |
192 | move.w d0, length |
193 | move.w d1, poffset |
194 | move.l a0, text |
195 | } |
196 | SetUpA4(); |
197 | |
198 | cwidth = CharWidth(' '); |
199 | |
200 | for (i = 0, wpos = 0, cpos = 0, found = 0; i < length; i++) { |
201 | c = text[i]; |
202 | |
203 | if (c == '\t') { |
204 | space = (TETabWidth - (wpos % TETabWidth)); |
205 | if (!space) |
206 | space = TETabWidth; |
207 | wpos += space; |
208 | } else if (c == '\r') |
209 | break; |
210 | else |
211 | wpos++; |
212 | |
213 | cpos++; |
214 | |
215 | if (poffset <= (wpos * cwidth) - (cwidth / 2)) { |
216 | found = 1; |
217 | break; |
218 | } |
219 | } |
220 | |
221 | /* put found in hi word */ |
222 | width = (wpos * cwidth) | (found << 8); |
223 | offset = cpos; |
224 | leftside = (poffset < (width - (cwidth / 2))); |
225 | |
226 | asm { |
227 | move.w width, d0 |
228 | move.w offset, d1 |
229 | move.w leftside, d2 |
230 | } |
231 | |
232 | /* restore registers */ |
233 | RestoreA4(); |
234 | asm { |
235 | movem.l (a7)+, d3-d7/a0-a3 |
236 | } |
237 | } |