-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmwParameterDialog.c
More file actions
312 lines (270 loc) · 10.3 KB
/
Copy pathmwParameterDialog.c
File metadata and controls
312 lines (270 loc) · 10.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
/*****
* mwParameterDialog.c
*
* See mwParameterDialog.h.
*
* REQUIRES a DLOG/DITL resource pair - a resource, not something
* this file can create - following the same conventions mwZoom.c
* and mwMenus.c already document their own dialogs with:
*
* "Parameter Entry", ID 131 (kParameterDialogID). Suggested
* size around 340x220 px - roomy enough for a two-line prompt
* and three label/field rows with clear spacing; exact bounds
* aren't load-bearing anywhere in this file. DITL 131, in
* EXACTLY this item order - unlike mwZoom.c's confirmation
* dialogs, most of these items have no title text of their own
* to find them by at runtime, so item number is the only
* handle this code has on them:
*
* 1. StaticText, prompt - two or three lines tall, close to
* the dialog's own width. Placeholder content only; every
* call overwrites it via SetIText().
* 2. StaticText, field 1's label - placeholder content only.
* 3. EditText, field 1's value.
* 4. StaticText, field 2's label - placeholder content only.
* 5. EditText, field 2's value.
* 6. StaticText, field 3's label - placeholder content only.
* 7. EditText, field 3's value.
* 8. Button, titled exactly "OK".
* 9. Button, titled exactly "Cancel".
*
* Item 1 needs to be tall enough for two or three lines of
* text (SetIText() doesn't reflow a StaticText item's own
* bounds to fit longer content - whatever's set has to already
* fit the rect ResEdit gives it). Items 2-7 are laid out as
* three label/field rows, evenly spaced - which rows are
* actually visible for a given call depends on fieldCount (see
* mwParameterDialog.h); unused trailing rows are hidden, not
* removed, so leave real vertical gaps between rows 1, 2, and 3
* rather than crowding them, since a 1-field call still uses
* the same dialog height with rows 2 and 3 simply blank.
*
* Buttons 8/9 are found by their own title text
* (ButtonItemWithTitle() below) exactly the way mwZoom.c's own
* confirmation dialogs are - the lesson that item, once real
* testing caught ResEdit's own item order not matching what a
* comment like this one specified, applies here as much as it
* did there.
*
* DLOG: references DITL 131, procID dBoxProc, goAway off,
* "initially visible" doesn't matter - ShowParameterDialog()
* forces it either way, matching every other dialog in this
* project.
*
*****/
#include "mwParameterDialog.h"
#include "mwZoom.h" /* CentreDialogOverMainWindow() */
#ifndef _Quickdraw_
#include <Quickdraw.h>
#endif
#ifndef _Dialogs_
#include <Dialogs.h>
#endif
#define kParameterDialogID 131
#define kPromptItem 1
#define kField1LabelItem 2
#define kField1EditItem 3
#define kFieldItemStride 2 /* field N's label/edit items are kField1LabelItem/kField1EditItem + (N-1)*kFieldItemStride */
#define kOKButtonTitle "\pOK"
#define kCancelButtonTitle "\pCancel"
/* CToPascalString()/PascalToCString()
Plain length-prefixed <-> NUL-terminated conversion, capped at 255/
63 characters respectively (Str255's own limit; ParameterField.text's).
Small, and duplicated in spirit from mwInfo.c's AppendCString() and
mwMenus.c's own BuildPascalString() rather than shared - each is a
handful of lines, and this project already tolerates that
(AppendCString() itself exists because Str255 has no equivalent
built in) rather than introducing a shared string-utilities file for
three call sites. Worth consolidating if a fourth one ever shows up. */
static void CToPascalString(const char *src, Str255 dst) {
short i = 0;
while (src[i] != '\0' && i < 255) {
dst[i + 1] = src[i];
i++;
}
dst[0] = i;
}
static void PascalToCString(const Str255 src, char *dst, short dstSize) {
short i;
short length = src[0];
if (length > dstSize - 1)
length = dstSize - 1;
for (i = 0; i < length; i++)
dst[i] = src[i + 1];
dst[length] = '\0';
}
/* PascalStringsEqual()
A plain byte-by-byte Pascal string comparison - duplicated from
mwZoom.c's own helper of the same name and same reasoning: avoiding
the Script Manager's EqualString() for a comparison this simple, so
as not to add a dependency on a call this project has never actually
linked before. */
static Boolean PascalStringsEqual(const unsigned char *a, const unsigned char *b) {
short length = a[0];
short i;
if (length != b[0])
return false;
for (i = 1; i <= length; i++)
if (a[i] != b[i])
return false;
return true;
}
/* The two items ButtonItemWithTitle() (below) searches - items 8 and
9 as specified, but named for what real testing elsewhere in this
project already warned not to trust blindly: a "guess" at where
they'll actually be, confirmed by title text before being relied on
for anything, never assumed correct on its own. */
#define kOKItemGuess 8
#define kCancelItemGuess 9
/* ButtonItemWithTitle()
Which of this dialog's two buttons has the given title - see this
file's own top comment for why title text, not item number, is what
finds OK/Cancel specifically (items 2-7 have no title of their own
to search for the same way, so they're still addressed by number).
GetCTitle(), not GetIText(): a button is a Control Manager item, and
mwZoom.c's own DialogItemTitleIs() already established GetCTitle()
as this project's way of reading one's title back, rather than
introducing a second, untested way to do the same thing here.
Returns 0 if neither matches, which no caller here should ever
actually see given the resource is meant to have exactly one button
titled "OK" and one titled "Cancel". */
static short ButtonItemWithTitle(DialogPtr dialog, ConstStr255Param title) {
short item;
for (item = kOKItemGuess; item <= kCancelItemGuess; item++) {
short itemType;
Handle itemHandle;
Rect itemRect;
Str255 itemTitle;
GetDItem(dialog, item, &itemType, &itemHandle, &itemRect);
if ((itemType & 0x7F) != 4) /* 4 = ctrlItem (a button), masking off the 128 = itemDisable bit - same test ShowAboutBox() (mwMenus.c) uses to find its own button */
continue;
GetCTitle((ControlHandle) itemHandle, itemTitle);
if (PascalStringsEqual(itemTitle, title))
return item;
}
return 0;
}
/* ShowParameterDialog()
See mwParameterDialog.h. */
Boolean ShowParameterDialog(const char *prompt, ParameterField *fields, short fieldCount) {
DialogPtr dialog;
Handle dlogResource;
GrafPtr savedPort;
short itemType;
Handle itemHandle;
Rect itemRect;
short itemHit;
short okItem, cancelItem;
short i;
Boolean result;
Str255 converted;
GetPort(&savedPort);
dlogResource = GetResource('DLOG', kParameterDialogID);
if (dlogResource == NULL || ResError() != noErr) {
SysBeep(1);
return false;
}
dialog = GetNewDialog(kParameterDialogID, NULL, (WindowPtr) -1L);
if (dialog == NULL) {
SysBeep(1);
return false;
}
HideWindow(dialog);
SetPort(dialog);
CToPascalString(prompt, converted);
GetDItem(dialog, kPromptItem, &itemType, &itemHandle, &itemRect);
SetIText(itemHandle, converted);
for (i = 0; i < kMaxParameterFields; i++) {
short labelItem = kField1LabelItem + i * kFieldItemStride;
short fieldItem = kField1EditItem + i * kFieldItemStride;
if (i < fieldCount) {
CToPascalString(fields[i].label, converted);
GetDItem(dialog, labelItem, &itemType, &itemHandle, &itemRect);
SetIText(itemHandle, converted);
if (fields[i].kind == kParameterFieldInteger)
NumToString(fields[i].value, converted);
else
CToPascalString(fields[i].text, converted);
GetDItem(dialog, fieldItem, &itemType, &itemHandle, &itemRect);
SetIText(itemHandle, converted);
} else {
HideDItem(dialog, labelItem);
HideDItem(dialog, fieldItem);
}
}
okItem = ButtonItemWithTitle(dialog, (ConstStr255Param) kOKButtonTitle);
cancelItem = ButtonItemWithTitle(dialog, (ConstStr255Param) kCancelButtonTitle);
/* Falls back to the specified item numbers if title-matching found
neither - a misbuilt resource (wrong title text on a button)
would otherwise leave okItem/cancelItem both 0, and since
nothing a person can actually click ever produces itemHit==0,
the modal loop below would hang waiting for a click that can
never satisfy its own exit condition. Preferring the title match
when it succeeds, same as mwZoom.c's own dialogs, still protects
against ResEdit's item order not matching this file's own
numbered spec; this only guards the case where it doesn't match
*either* way. */
if (okItem == 0)
okItem = kOKItemGuess;
if (cancelItem == 0)
cancelItem = kCancelItemGuess;
CentreDialogOverMainWindow(dialog);
ShowWindow(dialog);
SelectWindow(dialog);
SelIText(dialog, kField1EditItem, 0, 32767);
for (;;) {
ModalDialog(NULL, &itemHit);
if (itemHit == cancelItem) {
result = false;
break;
}
if (itemHit == okItem) {
Boolean allValid = true;
short firstBadField = -1;
/* Validate every active field before committing any of
them, so a bad second field can't leave the first
field's result half-applied if this attempt turns out
invalid overall. */
for (i = 0; i < fieldCount; i++) {
short fieldItem = kField1EditItem + i * kFieldItemStride;
Str255 entered;
GetDItem(dialog, fieldItem, &itemType, &itemHandle, &itemRect);
GetIText(itemHandle, entered);
if (fields[i].kind == kParameterFieldInteger) {
long parsed;
/* StringToNum() has no way to report "not a
number at all" - empty or non-numeric text
parses as 0, same as a genuine "0" would - so
the range check below is what actually catches
both a bad number and a missing one, not this
call itself. */
StringToNum(entered, &parsed);
if (parsed < fields[i].minimum || parsed > fields[i].maximum) {
allValid = false;
if (firstBadField < 0)
firstBadField = i;
} else {
fields[i].value = parsed;
}
} else {
if (entered[0] == 0) {
allValid = false;
if (firstBadField < 0)
firstBadField = i;
} else {
PascalToCString(entered, fields[i].text, sizeof(fields[i].text));
}
}
}
if (allValid) {
result = true;
break;
}
SysBeep(1);
SelIText(dialog, kField1EditItem + firstBadField * kFieldItemStride, 0, 32767);
}
}
DisposeDialog(dialog);
SetPort(savedPort);
return result;
}