1 /* Ui.cpp
2  *
3  * Copyright (C) 1992-2021 Paul Boersma
4  *
5  * This code is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or (at
8  * your option) any later version.
9  *
10  * This code is distributed in the hope that it will be useful, but
11  * WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13  * See the GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this work. If not, see <http://www.gnu.org/licenses/>.
17  */
18 
19 #include "../kar/longchar.h"
20 #include "machine.h"
21 #include "GuiP.h"
22 #include "Collection.h"
23 #include "UiP.h"
24 #include "Editor.h"
25 #include "Graphics.h"   // colours
26 #include "NUM2.h"   // get elements of ranges
27 
28 #include "enums_getText.h"
29 #include "Ui_enums.h"
30 #include "enums_getValue.h"
31 #include "Ui_enums.h"
32 
33 kUi_realMatrixFormat theRealMatrixFormat;
34 kUi_stringArrayFormat theStringArrayFormat;
35 
Ui_prefs()36 void Ui_prefs () {
37 	Preferences_addEnum (U"Ui.realMatrixFormat", & theRealMatrixFormat, kUi_realMatrixFormat, (int) kUi_realMatrixFormat::DEFAULT);
38 	Preferences_addEnum (U"Ui.stringArrayFormat", & theStringArrayFormat, kUi_stringArrayFormat, (int) kUi_stringArrayFormat::DEFAULT);
39 }
40 
formatNumericMatrix(constMAT cells,kUi_realMatrixFormat format)41 static conststring32 formatNumericMatrix (constMAT cells, kUi_realMatrixFormat format) {
42 	static MelderString buffer;
43 	MelderString_empty (& buffer);
44 	switch (format) {
45 		case kUi_realMatrixFormat::ONE_ROW_PER_LINE_: {
46 			for (integer irow = 1; irow <= cells.nrow; irow ++) {
47 				for (integer icol = 1; icol <= cells.ncol; icol ++) {
48 					MelderString_append (& buffer, cells [irow] [icol]);
49 					if (icol < cells.ncol)
50 						MelderString_appendCharacter (& buffer, U' ');
51 				}
52 				if (irow < cells.nrow)
53 					MelderString_appendCharacter (& buffer, U'\n');
54 			}
55 		} break; case kUi_realMatrixFormat::FORMULA_: {
56 			if (NUMisEmpty (cells)) {
57 				MelderString_append (& buffer, U"zero## (0, 0)");
58 			} else {
59 				MelderString_append (& buffer, U"{ ");
60 				for (integer irow = 1; irow <= cells.nrow; irow ++) {
61 					MelderString_append (& buffer, U"{ ");
62 					for (integer icol = 1; icol <= cells.ncol; icol ++) {
63 						MelderString_append (& buffer, cells [irow] [icol]);
64 						if (icol < cells.ncol)
65 							MelderString_append (& buffer, U", ");
66 					}
67 					MelderString_append (& buffer, U" }");
68 					if (irow < cells.nrow)
69 						MelderString_append (& buffer, U", ");
70 				}
71 				MelderString_append (& buffer, U" }");
72 			}
73 		} break; case kUi_realMatrixFormat::UNDEFINED: {
74 			Melder_fatal (U"Unknown numeric matrix format.");
75 		}
76 	}
77 	return buffer.string;
78 }
79 
MelderString_appendQuoted(MelderString * buffer,conststring32 string)80 static void MelderString_appendQuoted (MelderString *buffer, conststring32 string) {
81 	MelderString_appendCharacter (buffer, U'\"');
82 	for (const char32 *p = & string [0]; *p != U'\0'; p ++)
83 		if (*p == U'\"')
84 			MelderString_append (buffer, U"\"\"");
85 		else
86 			MelderString_appendCharacter (buffer, *p);
87 	MelderString_appendCharacter (buffer, U'\"');
88 }
89 
formatStringArray(constSTRVEC strings,kUi_stringArrayFormat format)90 static conststring32 formatStringArray (constSTRVEC strings, kUi_stringArrayFormat format) {
91 	static MelderString buffer;
92 	MelderString_empty (& buffer);
93 	switch (format) {
94 		case kUi_stringArrayFormat::WHITESPACE_SEPARATED_: {
95 			for (integer i = 1; i <= strings.size; i ++) {
96 				if (str32chr (strings [i], U'\"') || Melder_findHorizontalOrVerticalSpace (strings [i]))
97 					MelderString_appendQuoted (& buffer, strings [i]);
98 				else
99 					MelderString_append (& buffer, strings [i]);
100 				if (i < strings.size)
101 					MelderString_appendCharacter (& buffer, U' ');
102 			}
103 		} break; case kUi_stringArrayFormat::COMMA_SEPARATED_: {
104 			for (integer i = 1; i <= strings.size; i ++) {
105 				if (str32chr (strings [i], U'\"') || str32chr (strings [i], U','))
106 					MelderString_appendQuoted (& buffer, strings [i]);
107 				else
108 					MelderString_append (& buffer, strings [i]);
109 				if (i < strings.size)
110 					MelderString_appendCharacter (& buffer, U',');
111 			}
112 		} break; case kUi_stringArrayFormat::SEMICOLON_SEPARATED_: {
113 			for (integer i = 1; i <= strings.size; i ++) {
114 				if (str32chr (strings [i], U'\"') || str32chr (strings [i], U';'))
115 					MelderString_appendQuoted (& buffer, strings [i]);
116 				else
117 					MelderString_append (& buffer, strings [i]);
118 				if (i < strings.size)
119 					MelderString_appendCharacter (& buffer, U';');
120 			}
121 		} break; case kUi_stringArrayFormat::PIPE_SEPARATED_: {
122 			for (integer i = 1; i <= strings.size; i ++) {
123 				if (str32chr (strings [i], U'\"') || str32chr (strings [i], U'|'))
124 					MelderString_appendQuoted (& buffer, strings [i]);
125 				else
126 					MelderString_append (& buffer, strings [i]);
127 				if (i < strings.size)
128 					MelderString_appendCharacter (& buffer, U'|');
129 			}
130 		} break; case kUi_stringArrayFormat::ONE_PER_LINE_: {
131 			for (integer i = 1; i <= strings.size; i ++) {
132 				MelderString_append (& buffer, strings [i]);
133 				if (i < strings.size)
134 					MelderString_appendCharacter (& buffer, U'\n');
135 			}
136 		} break; case kUi_stringArrayFormat::FORMULA_: {
137 			if (NUMisEmpty (strings)) {
138 				MelderString_append (& buffer, U"empty$# (0)");
139 			} else {
140 				MelderString_append (& buffer, U"{ ");
141 				for (integer i = 1; i <= strings.size; i ++) {
142 					MelderString_appendQuoted (& buffer, strings [i]);
143 					if (i < strings.size)
144 						MelderString_append (& buffer, U", ");
145 				}
146 				MelderString_append (& buffer, U" }");
147 			}
148 		} break; case kUi_stringArrayFormat::UNDEFINED: {
149 			Melder_fatal (U"Unknown string array format.");
150 		}
151 	}
152 	return buffer.string;
153 }
154 
155 /***** class UiField: the things that have values in an UiForm dialog *****/
156 
157 Thing_implement (UiField, Thing, 0);
158 
v_destroy()159 void structUiField :: v_destroy () noexcept {
160 	our UiField_Parent :: v_destroy ();
161 }
162 
UiField_create(_kUiField_type type,conststring32 nameOrNull)163 static autoUiField UiField_create (_kUiField_type type, conststring32 nameOrNull) {
164 	autoUiField me = Thing_new (UiField);
165 	my type = type;
166 	my formLabel = Melder_dup (nameOrNull);
167 	if (nameOrNull) {
168 		char32 shortName [1+100], *p;
169 		str32ncpy (shortName, nameOrNull, 100);
170 		shortName [100] = U'\0';
171 		/*
172 			Strip parentheses and colon off parameter name.
173 		*/
174 		if (!! (p = (char32 *) str32chr (shortName, U'('))) {
175 			*p = U'\0';
176 			if (p - shortName > 0 && p [-1] == U' ')
177 				p [-1] = U'\0';
178 		}
179 		p = shortName;
180 		if (*p != U'\0' && p [str32len (p) - 1] == U':')
181 			p [str32len (p) - 1] = U'\0';
182 		Thing_setName (me.get(), shortName);
183 	}
184 	return me;
185 }
186 
187 /***** class UiOption: radio buttons and menu options *****/
188 
189 Thing_implement (UiOption, Thing, 0);
190 
UiOption_create(conststring32 label)191 static autoUiOption UiOption_create (conststring32 label) {
192 	autoUiOption me = Thing_new (UiOption);
193 	Thing_setName (me.get(), label);
194 	return me;
195 }
196 
UiRadio_addButton(UiField me,conststring32 label)197 UiOption UiRadio_addButton (UiField me, conststring32 label) {
198 	if (! me)
199 		return nullptr;
200 	Melder_assert (my type == _kUiField_type::RADIO_ || my type == _kUiField_type::OPTIONMENU_);
201 	autoUiOption thee = UiOption_create (label);
202 	return my options. addItem_move (thee.move());
203 }
204 
UiOptionMenu_addButton(UiField me,conststring32 label)205 UiOption UiOptionMenu_addButton (UiField me, conststring32 label) {
206 	if (! me)
207 		return nullptr;
208 	Melder_assert (my type == _kUiField_type::RADIO_ || my type == _kUiField_type::OPTIONMENU_);
209 	autoUiOption thee = UiOption_create (label);
210 	return my options. addItem_move (thee.move());
211 }
212 
213 /***** Things to do with UiField objects. *****/
214 
UiField_setDefault(UiField me)215 static void UiField_setDefault (UiField me) {
216 	switch (my type) {
217 		case _kUiField_type::LABEL_:
218 		{
219 			// do nothing
220 		}
221 		break;
222 		case _kUiField_type::REAL_:
223 		case _kUiField_type::REAL_OR_UNDEFINED_:
224 		case _kUiField_type::POSITIVE_:
225 		case _kUiField_type::INTEGER_:
226 		case _kUiField_type::NATURAL_:
227 		case _kUiField_type::WORD_:
228 		case _kUiField_type::SENTENCE_:
229 		case _kUiField_type::COLOUR_:
230 		case _kUiField_type::CHANNEL_:
231 		case _kUiField_type::TEXT_:
232 		case _kUiField_type::FORMULA_:
233 		case _kUiField_type::INFILE_:
234 		case _kUiField_type::OUTFILE_:
235 		case _kUiField_type::FOLDER_:
236 		{
237 			GuiText_setString (my text, my stringDefaultValue.get());
238 		}
239 		break;
240 		case _kUiField_type::REALVECTOR_:
241 		case _kUiField_type::POSITIVEVECTOR_:
242 		{
243 			GuiOptionMenu_setValue (my optionMenu, (int) my realVectorDefaultFormat);
244 			GuiText_setString (my text, my stringDefaultValue.get());
245 		}
246 		break;
247 		case _kUiField_type::INTEGERVECTOR_:
248 		case _kUiField_type::NATURALVECTOR_:
249 		{
250 			GuiOptionMenu_setValue (my optionMenu, (int) my integerVectorDefaultFormat);
251 			GuiText_setString (my text, my stringDefaultValue.get());
252 		}
253 		break;
254 		case _kUiField_type::REALMATRIX_:
255 		{
256 			theRealMatrixFormat = (kUi_realMatrixFormat) GuiOptionMenu_getValue (my optionMenu);
257 			GuiText_setString (my text, formatNumericMatrix (my numericMatrixDefaultValue.get(), theRealMatrixFormat));
258 		}
259 		break;
260 		case _kUiField_type::STRINGARRAY_:
261 		{
262 			theStringArrayFormat = (kUi_stringArrayFormat) GuiOptionMenu_getValue (my optionMenu);
263 			GuiText_setString (my text, formatStringArray (my stringArrayDefaultValue.get(), theStringArrayFormat));
264 		}
265 		break;
266 		case _kUiField_type::BOOLEAN_:
267 		{
268 			GuiCheckButton_setValue (my checkButton, my integerDefaultValue);
269 		}
270 		break;
271 		case _kUiField_type::RADIO_:
272 		{
273 			for (int i = 1; i <= my options.size; i ++) {
274 				if (i == my integerDefaultValue) {
275 					UiOption b = my options.at [i];
276 					GuiRadioButton_set (b -> radioButton);
277 				}
278 			}
279 		}
280 		break;
281 		case _kUiField_type::OPTIONMENU_:
282 		{
283 			GuiOptionMenu_setValue (my optionMenu, my integerDefaultValue);
284 		}
285 		break;
286 		case _kUiField_type::LIST_:
287 		{
288 			GuiList_selectItem (my list, my integerDefaultValue);
289 		}
290 	}
291 }
292 
UiField_widgetToValue(UiField me)293 static void UiField_widgetToValue (UiField me) {
294 	switch (my type)
295 	{
296 		case _kUiField_type::LABEL_:
297 		{
298 			// do nothing
299 		}
300 		break;
301 		case _kUiField_type::REAL_:
302 		case _kUiField_type::REAL_OR_UNDEFINED_:
303 		case _kUiField_type::POSITIVE_:
304 		{
305 			autostring32 text = GuiText_getString (my text);   // the text as typed by the user
306 			Interpreter_numericExpression (nullptr, text.get(), & my realValue);
307 			if (isundef (my realValue) && my type != _kUiField_type::REAL_OR_UNDEFINED_)
308 				Melder_throw (U"“", my name.get(), U"” has the value \"undefined\".");
309 			if (my type == _kUiField_type::POSITIVE_ && my realValue <= 0.0)
310 				Melder_throw (U"“", my name.get(), U"” should be greater than 0.0.");
311 			if (my realVariable)
312 				*my realVariable = my realValue;
313 		}
314 		break;
315 		case _kUiField_type::INTEGER_:
316 		case _kUiField_type::NATURAL_:
317 		case _kUiField_type::CHANNEL_:
318 		{
319 			autostring32 text = GuiText_getString (my text);
320 			if (my type == _kUiField_type::CHANNEL_ && (str32equ (text.get(), U"Left") || str32equ (text.get(), U"Mono"))) {
321 				my integerValue = 1;
322 			} else if (my type == _kUiField_type::CHANNEL_ && (str32equ (text.get(), U"Right") || str32equ (text.get(), U"Stereo"))) {
323 				my integerValue = 2;
324 			} else {
325 				double realValue;
326 				Interpreter_numericExpression (nullptr, text.get(), & realValue);
327 				my integerValue = Melder_iround (realValue);
328 				Melder_require (my integerValue == realValue,
329 					U"“", my name.get(), U"” should be a whole number.");
330 			}
331 			if (my type == _kUiField_type::NATURAL_) {
332 				Melder_require (my integerValue >= 1,
333 					U"“", my name.get(), U"” should be a positive whole number.");
334 			}
335 			if (my type == _kUiField_type::CHANNEL_) {
336 				Melder_require (my integerValue >= 0,
337 					U"“", my name.get(), U"” should be a positive whole number or zero.");
338 			}
339 			if (my integerVariable)
340 				*my integerVariable = my integerValue;
341 		}
342 		break;
343 		case _kUiField_type::WORD_:
344 		{
345 			my stringValue = GuiText_getString (my text);
346 			Melder_require (*Melder_findEndOfInk (my stringValue.get()) == U'\0',
347 				U"“", my name.get(), U"” should be a single ink-word and cannot contain a space.");
348 			if (my stringVariable)
349 				*my stringVariable = my stringValue.get();
350 		}
351 		break;
352 		case _kUiField_type::SENTENCE_:
353 		case _kUiField_type::TEXT_:
354 		case _kUiField_type::FORMULA_:
355 		case _kUiField_type::INFILE_:
356 		case _kUiField_type::OUTFILE_:
357 		case _kUiField_type::FOLDER_:
358 		{
359 			my stringValue = GuiText_getString (my text);
360 			if (my stringVariable)
361 				*my stringVariable = my stringValue.get();
362 		}
363 		break;
364 		case _kUiField_type::REALVECTOR_:
365 		case _kUiField_type::POSITIVEVECTOR_:
366 		{
367 			autostring32 stringValue = GuiText_getString (my text);
368 			kUi_realVectorFormat format = (kUi_realVectorFormat) GuiOptionMenu_getValue (my optionMenu);
369 			switch (format) {
370 				case kUi_realVectorFormat::WHITESPACE_SEPARATED_: {
371 					my realVectorValue = splitByWhitespace_VEC (stringValue.get());
372 				} break; case kUi_realVectorFormat::FORMULA_: {
373 					VEC result;
374 					bool ownedByInterpreter;
375 					Interpreter_numericVectorExpression (nullptr, stringValue.get(), & result, & ownedByInterpreter);
376 					if (ownedByInterpreter) {
377 						my realVectorValue. adoptFromAmbiguousOwner (result);
378 					} else {
379 						my realVectorValue = copy_VEC (result);
380 					}
381 				} break; case kUi_realVectorFormat::UNDEFINED: {
382 					Melder_fatal (U"Unknown real vector format.");
383 				}
384 			}
385 			if (my type == _kUiField_type::POSITIVEVECTOR_)
386 				for (integer i = 1; i <= my realVectorValue.size; i ++)
387 					if (my realVectorValue [i] <= 0.0)
388 						Melder_throw (U"Element ", i, U" of vector “", my name.get(), U"” is ", my realVectorValue [i], U" but should be greater than 0.0.");
389 			if (my realVectorVariable)
390 				*my realVectorVariable = my realVectorValue.get();
391 		}
392 		break;
393 		case _kUiField_type::INTEGERVECTOR_:
394 		case _kUiField_type::NATURALVECTOR_:
395 		{
396 			autostring32 stringValue = GuiText_getString (my text);
397 			kUi_integerVectorFormat format = (kUi_integerVectorFormat) GuiOptionMenu_getValue (my optionMenu);
398 			switch (format) {
399 				case kUi_integerVectorFormat::WHITESPACE_SEPARATED_: {
400 					my integerVectorValue = iround_INTVEC (splitByWhitespace_VEC (stringValue.get()).get());
401 				} break; case kUi_integerVectorFormat::RANGES_: {
402 					my integerVectorValue = splitByWhitespaceWithRanges_INTVEC (stringValue.get());
403 				} break; case kUi_integerVectorFormat::FORMULA_: {
404 					VEC result;
405 					bool ownedByInterpreter;
406 					Interpreter_numericVectorExpression (nullptr, stringValue.get(), & result, & ownedByInterpreter);
407 					my integerVectorValue = raw_INTVEC (result.size);
408 					for (integer i = 1; i <= result.size; i ++) {
409 						my integerVectorValue [i] = Melder_iround (result [i]);
410 						Melder_require (my integerVectorValue [i] == result [i],
411 							U"Element ", i, U" of vector “", my name.get(), U"” is ", result [i], U" but should be a whole number.");
412 					}
413 				} break; case kUi_integerVectorFormat::UNDEFINED: {
414 					Melder_fatal (U"Unknown integer vector format.");
415 				}
416 			}
417 			if (my type == _kUiField_type::NATURALVECTOR_)
418 				for (integer i = 1; i <= my integerVectorValue.size; i ++)
419 					if (my integerVectorValue [i] <= 0)
420 						Melder_throw (U"Element ", i, U" of vector “", my name.get(), U"” is ", my integerVectorValue [i], U" but should be greater than 0.");
421 			if (my integerVectorVariable)
422 				*my integerVectorVariable = my integerVectorValue.get();
423 		}
424 		break;
425 		case _kUiField_type::REALMATRIX_:
426 		{
427 			autostring32 stringValue = GuiText_getString (my text);
428 			MAT result;
429 			bool ownedByInterpreter;
430 			Interpreter_numericMatrixExpression (nullptr, stringValue.get(), & result, & ownedByInterpreter);
431 			if (ownedByInterpreter) {
432 				my numericMatrixValue. adoptFromAmbiguousOwner (result);
433 			} else {
434 				my numericMatrixValue = copy_MAT (result);
435 			}
436 			if (my numericMatrixVariable)
437 				*my numericMatrixVariable = my numericMatrixValue.get();
438 		}
439 		break;
440 		case _kUiField_type::STRINGARRAY_:
441 		{
442 			autostring32 stringValue = GuiText_getString (my text);
443 			theStringArrayFormat = (kUi_stringArrayFormat) GuiOptionMenu_getValue (my optionMenu);
444 			switch (theStringArrayFormat) {
445 				case kUi_stringArrayFormat::WHITESPACE_SEPARATED_: {
446 					my stringArrayValue = splitByWhitespace_STRVEC (stringValue.get());
447 				} break; case kUi_stringArrayFormat::COMMA_SEPARATED_: {
448 					my stringArrayValue = splitBy_STRVEC (stringValue.get(), U",");
449 				} break; case kUi_stringArrayFormat::SEMICOLON_SEPARATED_: {
450 					my stringArrayValue = splitBy_STRVEC (stringValue.get(), U";");
451 				} break; case kUi_stringArrayFormat::PIPE_SEPARATED_: {
452 					my stringArrayValue = splitBy_STRVEC (stringValue.get(), U"|");
453 				} break; case kUi_stringArrayFormat::ONE_PER_LINE_: {
454 					my stringArrayValue = splitBy_STRVEC (stringValue.get(), U"\n");
455 				} break; case kUi_stringArrayFormat::FORMULA_: {
456 					if (stringValue [0] == U'\0') {
457 						my stringArrayValue = autoSTRVEC();   // interpret the empty string as zero elements, as for all other formats
458 					} else {
459 						STRVEC result;
460 						bool ownedByInterpreter;
461 						Interpreter_stringArrayExpression (nullptr, stringValue.get(), & result, & ownedByInterpreter);
462 						if (ownedByInterpreter) {
463 							my stringArrayValue. adoptFromAmbiguousOwner (result);
464 						} else {
465 							my stringArrayValue = copy_STRVEC (result);
466 						}
467 					}
468 				} break; case kUi_stringArrayFormat::UNDEFINED: {
469 					Melder_fatal (U"Unknown string array format.");
470 				}
471 			}
472 			if (my stringArrayVariable)
473 				*my stringArrayVariable = my stringArrayValue.get();
474 		}
475 		break;
476 		case _kUiField_type::BOOLEAN_:
477 		{
478 			my integerValue = GuiCheckButton_getValue (my checkButton);
479 			if (my boolVariable)
480 				*my boolVariable = my integerValue;
481 		}
482 		break;
483 		case _kUiField_type::RADIO_:
484 		{
485 			my integerValue = 0;
486 			for (int i = 1; i <= my options.size; i ++) {
487 				UiOption b = my options.at [i];
488 				if (GuiRadioButton_getValue (b -> radioButton))
489 					my integerValue = i;
490 			}
491 			if (my integerValue == 0)
492 				Melder_throw (U"No option chosen for “", my name.get(), U"”.");
493 			if (my intVariable)
494 				*my intVariable = int (my integerValue) - my subtract;
495 			if (my stringVariable)
496 				*my stringVariable = my options.at [my integerValue] -> name.get();
497 		}
498 		break;
499 		case _kUiField_type::OPTIONMENU_:
500 		{
501 			my integerValue = GuiOptionMenu_getValue (my optionMenu);
502 			if (my integerValue == 0)
503 				Melder_throw (U"No option chosen for “", my name.get(), U"”.");
504 			if (my intVariable)
505 				*my intVariable = int (my integerValue) - my subtract;
506 			if (my stringVariable)
507 				*my stringVariable = my options.at [my integerValue] -> name.get();
508 		}
509 		break;
510 		case _kUiField_type::LIST_:
511 		{
512 			autoINTVEC selected = GuiList_getSelectedPositions (my list);
513 			if (selected.size == 0) {
514 				Melder_warning (U"No items selected.");
515 				my integerValue = 1;
516 			} else {
517 				if (selected.size > 1)
518 					Melder_warning (U"More than one item selected.");
519 				my integerValue = selected [1];
520 			}
521 			if (my integerVariable)
522 				*my integerVariable = my integerValue;
523 			if (my stringVariable)
524 				*my stringVariable = (char32 *) my strings [my integerValue];
525 		}
526 		break;
527 		case _kUiField_type::COLOUR_:
528 		{
529 			autostring32 string = GuiText_getString (my text);
530 			MelderColour colour = MelderColour_fromColourNameOrRGBString (string.get());
531 			if (colour.valid()) {
532 				my colourValue = colour;
533 			} else {
534 				double greyValue;
535 				Interpreter_numericExpression (nullptr, string.get(), & greyValue);
536 				my colourValue = MelderColour (greyValue);
537 			}
538 			if (my colourVariable)
539 				*my colourVariable = my colourValue;
540 		}
541 	}
542 }
543 
544 /***** History mechanism. *****/
545 
546 static MelderString theHistory;
UiHistory_write(conststring32 string)547 void UiHistory_write (conststring32 string) {
548 	MelderString_append (& theHistory, string);
549 }
UiHistory_write_expandQuotes(conststring32 string)550 void UiHistory_write_expandQuotes (conststring32 string) {
551 	if (! string)
552 		return;
553 	for (const char32 *p = & string [0]; *p != U'\0'; p ++) {
554 		if (*p == U'\"')
555 			MelderString_append (& theHistory, U"\"\"");
556 		else
557 			MelderString_appendCharacter (& theHistory, *p);
558 	}
559 }
UiHistory_write_colonize(conststring32 string)560 void UiHistory_write_colonize (conststring32 string) {
561 	if (! string)
562 		return;
563 	for (const char32 *p = & string [0]; *p != U'\0'; p ++) {
564 		if (*p == U'.' && p [1] == U'.' && p [2] == U'.') {
565 			MelderString_append (& theHistory, U":");
566 			p += 2;
567 		} else {
568 			MelderString_appendCharacter (& theHistory, *p);
569 		}
570 	}
571 }
UiHistory_get()572 char32 *UiHistory_get () {
573 	return theHistory.string;
574 }
UiHistory_clear()575 void UiHistory_clear () {
576 	MelderString_empty (& theHistory);
577 }
578 
579 /***** class UiForm: dialog windows *****/
580 
581 Thing_implement (UiForm, Thing, 0);
582 
583 bool (*theAllowExecutionHookHint) (void *closure) = nullptr;
584 void *theAllowExecutionClosureHint = nullptr;
585 
Ui_setAllowExecutionHook(bool (* allowExecutionHook)(void * closure),void * allowExecutionClosure)586 void Ui_setAllowExecutionHook (bool (*allowExecutionHook) (void *closure), void *allowExecutionClosure) {
587 	theAllowExecutionHookHint = allowExecutionHook;
588 	theAllowExecutionClosureHint = allowExecutionClosure;
589 }
590 
v_destroy()591 void structUiForm :: v_destroy () noexcept {
592 	if (our d_dialogForm) {
593 		trace (U"form <<", our d_dialogForm -> name.get(), U">>, invoking-button title <<", our invokingButtonTitle.get(), U">>");
594 		GuiObject_destroy (our d_dialogForm -> d_widget);   // BUG: make sure this destroys the shell
595 	}
596 	our UiForm_Parent :: v_destroy ();
597 }
598 
gui_button_cb_revert(UiForm me,GuiButtonEvent)599 static void gui_button_cb_revert (UiForm me, GuiButtonEvent /* event */) {
600 	for (int ifield = 1; ifield <= my numberOfFields; ifield ++)
601 		UiField_setDefault (my field [ifield].get());
602 }
603 
gui_dialog_cb_close(UiForm me)604 static void gui_dialog_cb_close (UiForm me) {
605 	if (my cancelCallback)
606 		my cancelCallback (me, my buttonClosure);
607 	GuiThing_hide (my d_dialogForm);
608 	if (my destroyWhenUnmanaged)
609 		forget (me);
610 }
gui_button_cb_cancel(UiForm me,GuiButtonEvent)611 static void gui_button_cb_cancel (UiForm me, GuiButtonEvent /* event */) {
612 	if (my cancelCallback)
613 		my cancelCallback (me, my buttonClosure);
614 	GuiThing_hide (my d_dialogForm);
615 	if (my destroyWhenUnmanaged)
616 		forget (me);
617 }
618 
UiForm_okOrApply(UiForm me,GuiButton button,int hide)619 static void UiForm_okOrApply (UiForm me, GuiButton button, int hide) {
620 	if (my allowExecutionHook && ! my allowExecutionHook (my allowExecutionClosure)) {
621 		Melder_flushError (U"Cannot execute command window “", my name.get(), U"”.");
622 		return;
623 	}
624 	try {
625 		for (int ifield = 1; ifield <= my numberOfFields; ifield ++)
626 			UiField_widgetToValue (my field [ifield].get());
627 	} catch (MelderError) {
628 		Melder_flushError (U"Please correct command window “", my name.get(), U"” or cancel.");
629 		return;
630 	}
631 	if (my okButton)     GuiThing_setSensitive (my okButton,     false);
632 	if (my applyButton)  GuiThing_setSensitive (my applyButton,  false);
633 	if (my cancelButton) GuiThing_setSensitive (my cancelButton, false);
634 	if (my revertButton) GuiThing_setSensitive (my revertButton, false);
635 	if (my helpButton)   GuiThing_setSensitive (my helpButton,   false);
636 	for (int i = 1; i <= my numberOfContinueButtons; i ++)
637 		if (my continueButtons [i])
638 			GuiThing_setSensitive (my continueButtons [i], false);
639 
640 	#if defined (_WIN32)
641 		GdiFlush ();
642 	#endif
643 	if (my isPauseForm) {
644 		for (int i = 1; i <= my numberOfContinueButtons; i ++)
645 			if (button == my continueButtons [i])
646 				my clickedContinueButton = i;
647 	}
648 	/*
649 		Keep the gate for error handling.
650 	*/
651 	try {
652 		my okCallback (me, 0, nullptr, nullptr, nullptr, nullptr, false, my buttonClosure);
653 		/*
654 			Write everything to history. Before destruction!
655 		*/
656 		if (! my isPauseForm) {
657 			UiHistory_write (U"\n");
658 			UiHistory_write_colonize (my invokingButtonTitle.get());
659 			int size = my numberOfFields;
660 			while (size >= 1 && my field [size] -> type == _kUiField_type::LABEL_)
661 				size --;   // ignore trailing fields without a value
662 			int next = 0;
663 			for (int ifield = 1; ifield <= size; ifield ++) {
664 				UiField field = my field [ifield].get();
665 				switch (field -> type)
666 				{
667 					case _kUiField_type::LABEL_:
668 					{
669 						// do nothing
670 					}
671 					break;
672 					case _kUiField_type::REAL_:
673 					case _kUiField_type::REAL_OR_UNDEFINED_:
674 					case _kUiField_type::POSITIVE_:
675 					{
676 						UiHistory_write (next -- ? U", " : U" ");
677 						UiHistory_write (Melder_double (field -> realValue));
678 					}
679 					break;
680 					case _kUiField_type::INTEGER_:
681 					case _kUiField_type::NATURAL_:
682 					case _kUiField_type::CHANNEL_:
683 					{
684 						UiHistory_write (next -- ? U", " : U" ");
685 						UiHistory_write (Melder_integer (field -> integerValue));
686 					}
687 					break;
688 					case _kUiField_type::WORD_:
689 					case _kUiField_type::SENTENCE_:
690 					case _kUiField_type::TEXT_:
691 					case _kUiField_type::FORMULA_:
692 					case _kUiField_type::INFILE_:
693 					case _kUiField_type::OUTFILE_:
694 					case _kUiField_type::FOLDER_:
695 					{
696 						UiHistory_write (next -- ? U", \"" : U" \"");
697 						UiHistory_write_expandQuotes (field -> stringValue.get());
698 						UiHistory_write (U"\"");
699 					}
700 					break;
701 					case _kUiField_type:: REALVECTOR_:
702 					case _kUiField_type:: POSITIVEVECTOR_:
703 					{
704 						if (NUMisEmpty (field -> realVectorValue.get())) {
705 							UiHistory_write (next -- ? U", zero# (0)" : U" zero# (0)");
706 						} else {
707 							UiHistory_write (next -- ? U", { " : U" { ");
708 							for (integer i = 1; i <= field -> realVectorValue.size; i ++) {
709 								UiHistory_write (Melder_double (field -> realVectorValue [i]));
710 								UiHistory_write (i == field -> realVectorValue.size ? U" }" : U", ");
711 							}
712 						}
713 					} break;
714 					case _kUiField_type:: INTEGERVECTOR_:
715 					case _kUiField_type:: NATURALVECTOR_:
716 					{
717 						if (NUMisEmpty (field -> integerVectorValue.get())) {
718 							UiHistory_write (next -- ? U", zero# (0)" : U" zero# (0)");
719 						} else {
720 							UiHistory_write (next -- ? U", { " : U" { ");
721 							for (integer i = 1; i <= field -> integerVectorValue.size; i ++) {
722 								UiHistory_write (Melder_integer (field -> integerVectorValue [i]));
723 								UiHistory_write (i == field -> integerVectorValue.size ? U" }" : U", ");
724 							}
725 						}
726 					} break;
727 					case _kUiField_type:: REALMATRIX_:
728 					{
729 						if (NUMisEmpty (field -> numericMatrixValue.get())) {
730 							UiHistory_write (next -- ? U", zero## (0, 0)" : U" zero## (0, 0)");
731 						} else {
732 							UiHistory_write (next -- ? U", { " : U" { ");
733 							for (integer irow = 1; irow <= field -> numericMatrixValue.nrow; irow ++) {
734 								UiHistory_write (U"{ ");
735 								for (integer icol = 1; icol <= field -> numericMatrixValue.ncol; icol ++) {
736 									UiHistory_write (Melder_double (field -> numericMatrixValue [irow] [icol]));
737 									UiHistory_write (icol == field -> numericMatrixValue.ncol ? U" }" : U", ");
738 								}
739 								UiHistory_write (irow == field -> numericMatrixValue.nrow ? U" }" : U", ");
740 							}
741 						}
742 					} break;
743 					case _kUiField_type:: STRINGARRAY_:
744 					{
745 						if (NUMisEmpty (field -> stringArrayValue.get())) {
746 							UiHistory_write_expandQuotes (next -- ? U", empty$# (0)" : U" empty$# (0)");
747 						} else {
748 							UiHistory_write (next -- ? U", { " : U" { ");
749 							for (integer i = 1; i <= field -> stringArrayValue.size; i ++) {
750 								UiHistory_write (U"\"");
751 								UiHistory_write_expandQuotes (field -> stringArrayValue [i].get());
752 								UiHistory_write (U"\"");
753 								UiHistory_write (i == field -> stringArrayValue.size ? U" }" : U", ");
754 							}
755 						}
756 					} break;
757 					case _kUiField_type::BOOLEAN_:
758 					{
759 						UiHistory_write (field -> integerValue ? (next -- ? U", \"yes\"" : U" \"yes\"") : (next -- ? U", \"no\"" : U" \"no\""));
760 					}
761 					break;
762 					case _kUiField_type::RADIO_:
763 					case _kUiField_type::OPTIONMENU_:
764 					{
765 						UiOption b = field -> options.at [field -> integerValue];
766 						UiHistory_write (next -- ? U", \"" : U" \"");
767 						UiHistory_write_expandQuotes (b -> name.get());
768 						UiHistory_write (U"\"");
769 					}
770 					break;
771 					case _kUiField_type::LIST_:
772 					{
773 						UiHistory_write (next -- ? U", \"" : U" \"");
774 						UiHistory_write_expandQuotes (field -> strings [field -> integerValue]);
775 						UiHistory_write (U"\"");
776 					}
777 					break;
778 					case _kUiField_type::COLOUR_:
779 					{
780 						conststring32 colourString = MelderColour_namePrettyOrNull (field -> colourValue);
781 						if (colourString) {
782 							UiHistory_write (next -- ? U", \"" : U" \"");
783 							UiHistory_write (colourString);
784 							UiHistory_write (U"\"");
785 						} else if (field -> colourValue. isGrey()) {
786 							UiHistory_write (next -- ? U", " : U" ");
787 							UiHistory_write (Melder_double (field -> colourValue. red));
788 						} else {
789 							colourString = MelderColour_nameRGB (field -> colourValue);
790 							UiHistory_write (next -- ? U", " : U" ");
791 							UiHistory_write (colourString);
792 						}
793 					}
794 				}
795 			}
796 		}
797 		if (hide) {
798 			GuiThing_hide (my d_dialogForm);
799 			if (my destroyWhenUnmanaged) {
800 				forget (me);
801 				return;
802 			}
803 		}
804 	} catch (MelderError) {
805 		/*
806 			If a solution has already been suggested, or the "error" was actually a conscious user action, do not add anything more.
807 		*/
808 		if (! str32str (Melder_getError (), U"Please ") && ! str32str (Melder_getError (), U"You could ") &&
809 			! str32str (Melder_getError (), U"You interrupted ") && ! str32str (Melder_getError (), U"Interrupted!"))
810 		{
811 			/*
812 				Otherwise, show a generic message.
813 			*/
814 			if (str32str (Melder_getError (), U"Selection changed!")) {
815 				Melder_appendError (U"Please change the selection in the object list, or click Cancel in the command window “",
816 					my name.get(), U"”.");
817 			} else {
818 				Melder_appendError (U"Please change something in the command window “",
819 					my name.get(), U"”, or click Cancel in that window.");
820 			}
821 		}
822 		Melder_flushError ();
823 	}
824 	if (my okButton)     GuiThing_setSensitive (my okButton,     true);
825 	if (my applyButton)  GuiThing_setSensitive (my applyButton,  true);
826 	if (my cancelButton) GuiThing_setSensitive (my cancelButton, true);
827 	if (my revertButton) GuiThing_setSensitive (my revertButton, true);
828 	if (my helpButton)   GuiThing_setSensitive (my helpButton,   true);
829 	for (int i = 1; i <= my numberOfContinueButtons; i ++)
830 		if (my continueButtons [i])
831 			GuiThing_setSensitive (my continueButtons [i], true);
832 }
833 
gui_button_cb_ok(UiForm me,GuiButtonEvent event)834 static void gui_button_cb_ok (UiForm me, GuiButtonEvent event) {
835 	UiForm_okOrApply (me, event -> button, true);
836 }
837 
gui_dialog_cb_default(UiForm me)838 static void gui_dialog_cb_default (UiForm me) {
839 	UiForm_okOrApply (me,
840 		my defaultContinueButton >= 1 && my defaultContinueButton <= my numberOfContinueButtons ? my continueButtons [my defaultContinueButton] : nullptr,
841 		true
842 	);
843 }
844 
gui_button_cb_apply(UiForm me,GuiButtonEvent event)845 static void gui_button_cb_apply (UiForm me, GuiButtonEvent event) {
846 	UiForm_okOrApply (me, event -> button, false);
847 }
848 
gui_button_cb_help(UiForm me,GuiButtonEvent)849 static void gui_button_cb_help (UiForm me, GuiButtonEvent /* event */) {
850 	Melder_help (my helpTitle.get());
851 }
852 
gui_button_cb_browseInfile(UiField me,GuiButtonEvent)853 static void gui_button_cb_browseInfile (UiField me, GuiButtonEvent /* event */) {
854 	autoStringSet chosenFilePath = GuiFileSelect_getInfileNames (nullptr, U"Open file", false);
855 	if (chosenFilePath->size != 0)
856 		GuiText_setString (my text, chosenFilePath->at [1] -> string.get());
857 }
gui_button_cb_browseOutfile(UiField me,GuiButtonEvent)858 static void gui_button_cb_browseOutfile (UiField me, GuiButtonEvent /* event */) {
859 	autostring32 chosenFilePath = GuiFileSelect_getOutfileName (nullptr, U"Save file", U"");
860 	if (chosenFilePath)
861 		GuiText_setString (my text, chosenFilePath.get());
862 }
gui_button_cb_browseFolder(UiField me,GuiButtonEvent)863 static void gui_button_cb_browseFolder (UiField me, GuiButtonEvent /* event */) {
864 	autostring32 chosenFolderPath = GuiFileSelect_getFolderName (nullptr, U"Choose folder");
865 	if (chosenFolderPath)
866 		GuiText_setString (my text, chosenFolderPath.get());
867 }
868 
UiForm_create(GuiWindow parent,conststring32 title,UiCallback okCallback,void * buttonClosure,conststring32 invokingButtonTitle,conststring32 helpTitle)869 autoUiForm UiForm_create (GuiWindow parent, conststring32 title,
870 	UiCallback okCallback, void *buttonClosure,
871 	conststring32 invokingButtonTitle, conststring32 helpTitle)
872 {
873 	autoUiForm me = Thing_new (UiForm);
874 	my d_dialogParent = parent;
875 	Thing_setName (me.get(), title);
876 	my okCallback = okCallback;
877 	my buttonClosure = buttonClosure;
878 	my invokingButtonTitle = Melder_dup (invokingButtonTitle);
879 	my helpTitle = Melder_dup (helpTitle);
880 	return me;
881 }
882 
UiForm_setPauseForm(UiForm me,int numberOfContinueButtons,int defaultContinueButton,int cancelContinueButton,conststring32 continue1,conststring32 continue2,conststring32 continue3,conststring32 continue4,conststring32 continue5,conststring32 continue6,conststring32 continue7,conststring32 continue8,conststring32 continue9,conststring32 continue10,void (* cancelCallback)(UiForm dia,void * closure))883 void UiForm_setPauseForm (UiForm me,
884 	int numberOfContinueButtons, int defaultContinueButton, int cancelContinueButton,
885 	conststring32 continue1, conststring32 continue2, conststring32 continue3,
886 	conststring32 continue4, conststring32 continue5, conststring32 continue6,
887 	conststring32 continue7, conststring32 continue8, conststring32 continue9,
888 	conststring32 continue10,
889 	void (*cancelCallback) (UiForm dia, void *closure))
890 {
891 	my isPauseForm = true;
892 	my numberOfContinueButtons = numberOfContinueButtons;
893 	my defaultContinueButton = defaultContinueButton;
894 	my cancelContinueButton = cancelContinueButton;
895 	my continueTexts [1] = continue1;
896 	my continueTexts [2] = continue2;
897 	my continueTexts [3] = continue3;
898 	my continueTexts [4] = continue4;
899 	my continueTexts [5] = continue5;
900 	my continueTexts [6] = continue6;
901 	my continueTexts [7] = continue7;
902 	my continueTexts [8] = continue8;
903 	my continueTexts [9] = continue9;
904 	my continueTexts [10] = continue10;
905 	my cancelCallback = cancelCallback;
906 }
907 
commonOkCallback(UiForm,integer,Stackel,conststring32,Interpreter interpreter,conststring32,bool,void * closure)908 static void commonOkCallback (UiForm /* dia */, integer /* narg */, Stackel /* args */, conststring32 /* sendingString */,
909 	Interpreter interpreter, conststring32 /* invokingButtonTitle */, bool /* modified */, void *closure)
910 {
911 	EditorCommand cmd = (EditorCommand) closure;
912 	cmd -> commandCallback (cmd -> d_editor, cmd, cmd -> d_uiform.get(), 0, nullptr, nullptr, interpreter);
913 }
914 
UiForm_createE(EditorCommand cmd,conststring32 title,conststring32 invokingButtonTitle,conststring32 helpTitle)915 autoUiForm UiForm_createE (EditorCommand cmd, conststring32 title, conststring32 invokingButtonTitle, conststring32 helpTitle) {
916 	Editor editor = cmd -> d_editor;
917 	autoUiForm dia (UiForm_create (editor -> windowForm, title, commonOkCallback, cmd, invokingButtonTitle, helpTitle));
918 	dia -> command = cmd;
919 	return dia;
920 }
921 
UiForm_addField(UiForm me,_kUiField_type type,conststring32 label)922 static UiField UiForm_addField (UiForm me, _kUiField_type type, conststring32 label) {
923 	if (my numberOfFields == MAXIMUM_NUMBER_OF_FIELDS)
924 		Melder_throw (U"Cannot have more than ", MAXIMUM_NUMBER_OF_FIELDS, U"fields in a form.");
925 	my field [++ my numberOfFields] = UiField_create (type, label);
926 	return my field [my numberOfFields].get();
927 }
928 
UiForm_addReal(UiForm me,double * variable,conststring32 variableName,conststring32 label,conststring32 defaultValue)929 UiField UiForm_addReal (UiForm me, double *variable, conststring32 variableName, conststring32 label, conststring32 defaultValue) {
930 	UiField thee = UiForm_addField (me, _kUiField_type::REAL_, label);
931 	thy stringDefaultValue = Melder_dup (defaultValue);
932 	thy realVariable = variable;
933 	thy variableName = variableName;
934 	return thee;
935 }
936 
UiForm_addRealOrUndefined(UiForm me,double * variable,conststring32 variableName,conststring32 label,conststring32 defaultValue)937 UiField UiForm_addRealOrUndefined (UiForm me, double *variable, conststring32 variableName, conststring32 label, conststring32 defaultValue) {
938 	UiField thee = UiForm_addField (me, _kUiField_type::REAL_OR_UNDEFINED_, label);
939 	thy stringDefaultValue = Melder_dup (defaultValue);
940 	thy realVariable = variable;
941 	thy variableName = variableName;
942 	return thee;
943 }
944 
UiForm_addPositive(UiForm me,double * variable,conststring32 variableName,conststring32 label,conststring32 defaultValue)945 UiField UiForm_addPositive (UiForm me, double *variable, conststring32 variableName, conststring32 label, conststring32 defaultValue) {
946 	UiField thee = UiForm_addField (me, _kUiField_type::POSITIVE_, label);
947 	thy stringDefaultValue = Melder_dup (defaultValue);
948 	thy realVariable = variable;
949 	thy variableName = variableName;
950 	return thee;
951 }
952 
UiForm_addInteger(UiForm me,integer * variable,conststring32 variableName,conststring32 label,conststring32 defaultValue)953 UiField UiForm_addInteger (UiForm me, integer *variable, conststring32 variableName, conststring32 label, conststring32 defaultValue) {
954 	UiField thee = UiForm_addField (me, _kUiField_type::INTEGER_, label);
955 	thy stringDefaultValue = Melder_dup (defaultValue);
956 	thy integerVariable = variable;
957 	thy variableName = variableName;
958 	return thee;
959 }
960 
UiForm_addNatural(UiForm me,integer * variable,conststring32 variableName,conststring32 label,conststring32 defaultValue)961 UiField UiForm_addNatural (UiForm me, integer *variable, conststring32 variableName, conststring32 label, conststring32 defaultValue) {
962 	UiField thee = UiForm_addField (me, _kUiField_type::NATURAL_, label);
963 	thy stringDefaultValue = Melder_dup (defaultValue);
964 	thy integerVariable = variable;
965 	thy variableName = variableName;
966 	return thee;
967 }
968 
UiForm_addWord(UiForm me,conststring32 * variable,conststring32 variableName,conststring32 label,conststring32 defaultValue)969 UiField UiForm_addWord (UiForm me, conststring32 *variable, conststring32 variableName, conststring32 label, conststring32 defaultValue) {
970 	UiField thee = UiForm_addField (me, _kUiField_type::WORD_, label);
971 	thy stringDefaultValue = Melder_dup (defaultValue);
972 	thy stringVariable = variable;
973 	thy variableName = variableName;
974 	return thee;
975 }
976 
UiForm_addSentence(UiForm me,conststring32 * variable,conststring32 variableName,conststring32 label,conststring32 defaultValue)977 UiField UiForm_addSentence (UiForm me, conststring32 *variable, conststring32 variableName, conststring32 label, conststring32 defaultValue) {
978 	UiField thee = UiForm_addField (me, _kUiField_type::SENTENCE_, label);
979 	thy stringDefaultValue = Melder_dup (defaultValue);
980 	thy stringVariable = variable;
981 	thy variableName = variableName;
982 	return thee;
983 }
984 
UiForm_addLabel(UiForm me,conststring32 * variable,conststring32 label)985 UiField UiForm_addLabel (UiForm me, conststring32 *variable, conststring32 label) {
986 	UiField thee = UiForm_addField (me, _kUiField_type::LABEL_, U"");   // this field gets no name; so that the user can give it any title
987 	thy stringVariable = variable;
988 	thy stringValue = Melder_dup (label);
989 	return thee;
990 }
991 
UiForm_addBoolean(UiForm me,bool * variable,conststring32 variableName,conststring32 label,bool defaultValue)992 UiField UiForm_addBoolean (UiForm me, bool *variable, conststring32 variableName, conststring32 label, bool defaultValue) {
993 	UiField thee = UiForm_addField (me, _kUiField_type::BOOLEAN_, label);
994 	thy integerDefaultValue = defaultValue;
995 	thy boolVariable = variable;
996 	thy variableName = variableName;
997 	return thee;
998 }
999 
UiForm_addText(UiForm me,conststring32 * variable,conststring32 variableName,conststring32 label,conststring32 defaultValue,integer numberOfLines)1000 UiField UiForm_addText (UiForm me, conststring32 *variable, conststring32 variableName, conststring32 label, conststring32 defaultValue, integer numberOfLines) {
1001 	UiField thee = UiForm_addField (me, _kUiField_type::TEXT_, label);
1002 	thy stringDefaultValue = Melder_dup (defaultValue);
1003 	thy stringVariable = variable;
1004 	thy variableName = variableName;
1005 	thy numberOfLines = Melder_clipped (1_integer, numberOfLines, 33_integer);
1006 	return thee;
1007 }
1008 
UiForm_addFormula(UiForm me,conststring32 * variable,conststring32 variableName,conststring32 label,conststring32 defaultValue)1009 UiField UiForm_addFormula (UiForm me, conststring32 *variable, conststring32 variableName, conststring32 label, conststring32 defaultValue) {
1010 	UiField thee = UiForm_addField (me, _kUiField_type::FORMULA_, label);
1011 	thy stringDefaultValue = Melder_dup (defaultValue);
1012 	thy stringVariable = variable;
1013 	thy variableName = variableName;
1014 	thy numberOfLines = 5;
1015 	return thee;
1016 }
1017 
UiForm_addInfile(UiForm me,conststring32 * variable,conststring32 variableName,conststring32 label,conststring32 defaultValue)1018 UiField UiForm_addInfile (UiForm me, conststring32 *variable, conststring32 variableName, conststring32 label, conststring32 defaultValue) {
1019 	UiField thee = UiForm_addField (me, _kUiField_type::INFILE_, label);
1020 	thy stringDefaultValue = Melder_dup (defaultValue);
1021 	thy stringVariable = variable;
1022 	thy variableName = variableName;
1023 	thy numberOfLines = 3;
1024 	return thee;
1025 }
1026 
UiForm_addOutfile(UiForm me,conststring32 * variable,conststring32 variableName,conststring32 label,conststring32 defaultValue)1027 UiField UiForm_addOutfile (UiForm me, conststring32 *variable, conststring32 variableName, conststring32 label, conststring32 defaultValue) {
1028 	UiField thee = UiForm_addField (me, _kUiField_type::OUTFILE_, label);
1029 	thy stringDefaultValue = Melder_dup (defaultValue);
1030 	thy stringVariable = variable;
1031 	thy variableName = variableName;
1032 	thy numberOfLines = 3;
1033 	return thee;
1034 }
1035 
UiForm_addFolder(UiForm me,conststring32 * variable,conststring32 variableName,conststring32 label,conststring32 defaultValue)1036 UiField UiForm_addFolder (UiForm me, conststring32 *variable, conststring32 variableName, conststring32 label, conststring32 defaultValue) {
1037 	UiField thee = UiForm_addField (me, _kUiField_type::FOLDER_, label);
1038 	thy stringDefaultValue = Melder_dup (defaultValue);
1039 	thy stringVariable = variable;
1040 	thy variableName = variableName;
1041 	thy numberOfLines = 3;
1042 	return thee;
1043 }
1044 
UiForm_addRealVector(UiForm me,constVEC * variable,conststring32 variableName,conststring32 label,kUi_realVectorFormat defaultFormat,conststring32 defaultValue)1045 UiField UiForm_addRealVector (UiForm me, constVEC *variable, conststring32 variableName, conststring32 label, kUi_realVectorFormat defaultFormat, conststring32 defaultValue) {
1046 	UiField thee = UiForm_addField (me, _kUiField_type::REALVECTOR_, label);
1047 	thy realVectorDefaultFormat = defaultFormat;
1048 	thy stringDefaultValue = Melder_dup (defaultValue);
1049 	thy realVectorVariable = variable;
1050 	thy variableName = variableName;
1051 	thy numberOfLines = 7;
1052 	return thee;
1053 }
1054 
UiForm_addPositiveVector(UiForm me,constVEC * variable,conststring32 variableName,conststring32 label,kUi_realVectorFormat defaultFormat,conststring32 defaultValue)1055 UiField UiForm_addPositiveVector (UiForm me, constVEC *variable, conststring32 variableName, conststring32 label, kUi_realVectorFormat defaultFormat, conststring32 defaultValue) {
1056 	UiField thee = UiForm_addField (me, _kUiField_type::POSITIVEVECTOR_, label);
1057 	thy realVectorDefaultFormat = defaultFormat;
1058 	thy stringDefaultValue = Melder_dup (defaultValue);
1059 	thy realVectorVariable = variable;
1060 	thy variableName = variableName;
1061 	thy numberOfLines = 7;
1062 	return thee;
1063 }
1064 
UiForm_addIntegerVector(UiForm me,constINTVEC * variable,conststring32 variableName,conststring32 label,kUi_integerVectorFormat defaultFormat,conststring32 defaultValue)1065 UiField UiForm_addIntegerVector (UiForm me, constINTVEC *variable, conststring32 variableName, conststring32 label, kUi_integerVectorFormat defaultFormat, conststring32 defaultValue) {
1066 	UiField thee = UiForm_addField (me, _kUiField_type::INTEGERVECTOR_, label);
1067 	thy integerVectorDefaultFormat = defaultFormat;
1068 	thy stringDefaultValue = Melder_dup (defaultValue);
1069 	thy integerVectorVariable = variable;
1070 	thy variableName = variableName;
1071 	thy numberOfLines = 7;
1072 	return thee;
1073 }
1074 
UiForm_addNaturalVector(UiForm me,constINTVEC * variable,conststring32 variableName,conststring32 label,kUi_integerVectorFormat defaultFormat,conststring32 defaultValue)1075 UiField UiForm_addNaturalVector (UiForm me, constINTVEC *variable, conststring32 variableName, conststring32 label, kUi_integerVectorFormat defaultFormat, conststring32 defaultValue) {
1076 	UiField thee = UiForm_addField (me, _kUiField_type::NATURALVECTOR_, label);
1077 	thy integerVectorDefaultFormat = defaultFormat;
1078 	thy stringDefaultValue = Melder_dup (defaultValue);
1079 	thy integerVectorVariable = variable;
1080 	thy variableName = variableName;
1081 	thy numberOfLines = 7;
1082 	return thee;
1083 }
1084 
UiForm_addRealMatrix(UiForm me,constMAT * variable,conststring32 variableName,conststring32 label,constMATVU defaultValue)1085 UiField UiForm_addRealMatrix (UiForm me, constMAT *variable, conststring32 variableName, conststring32 label, constMATVU defaultValue) {
1086 	UiField thee = UiForm_addField (me, _kUiField_type::REALMATRIX_, label);
1087 	thy numericMatrixDefaultValue = copy_MAT (defaultValue);
1088 	thy numericMatrixVariable = variable;
1089 	thy variableName = variableName;
1090 	thy numberOfLines = 10;
1091 	return thee;
1092 }
1093 
UiForm_addStringArray(UiForm me,constSTRVEC * variable,conststring32 variableName,conststring32 label,constSTRVEC defaultValue,integer numberOfLines)1094 UiField UiForm_addStringArray (UiForm me, constSTRVEC *variable, conststring32 variableName, conststring32 label, constSTRVEC defaultValue, integer numberOfLines) {
1095 	UiField thee = UiForm_addField (me, _kUiField_type::STRINGARRAY_, label);
1096 	thy stringArrayDefaultValue = copy_STRVEC (defaultValue);
1097 	thy stringArrayFormat = theStringArrayFormat;
1098 	thy stringArrayVariable = variable;
1099 	thy variableName = variableName;
1100 	thy numberOfLines = numberOfLines;
1101 	return thee;
1102 }
1103 
UiForm_addRadio(UiForm me,int * intVariable,conststring32 * stringVariable,conststring32 variableName,conststring32 label,int defaultValue,int base)1104 UiField UiForm_addRadio (UiForm me, int *intVariable, conststring32 *stringVariable, conststring32 variableName, conststring32 label, int defaultValue, int base) {
1105 	UiField thee = UiForm_addField (me, _kUiField_type::RADIO_, label);
1106 	thy integerDefaultValue = defaultValue;
1107 	thy intVariable = intVariable;
1108 	thy stringVariable = stringVariable;
1109 	thy variableName = variableName;
1110 	thy subtract = ( base == 1 ? 0 : 1 );
1111 	return thee;
1112 }
1113 
UiForm_addOptionMenu(UiForm me,int * intVariable,conststring32 * stringVariable,conststring32 variableName,conststring32 label,int defaultValue,int base)1114 UiField UiForm_addOptionMenu (UiForm me, int *intVariable, conststring32 *stringVariable, conststring32 variableName, conststring32 label, int defaultValue, int base) {
1115 	UiField thee = UiForm_addField (me, _kUiField_type::OPTIONMENU_, label);
1116 	thy integerDefaultValue = defaultValue;
1117 	thy intVariable = intVariable;
1118 	thy stringVariable = stringVariable;
1119 	thy variableName = variableName;
1120 	thy subtract = ( base == 1 ? 0 : 1 );
1121 	return thee;
1122 }
1123 
UiForm_addList(UiForm me,integer * integerVariable,conststring32 * stringVariable,conststring32 variableName,conststring32 label,constSTRVEC strings,integer defaultValue)1124 UiField UiForm_addList (UiForm me, integer *integerVariable, conststring32 *stringVariable, conststring32 variableName, conststring32 label, constSTRVEC strings, integer defaultValue) {
1125 	UiField thee = UiForm_addField (me, _kUiField_type::LIST_, label);
1126 	thy strings = strings;
1127 	thy integerDefaultValue = defaultValue;
1128 	thy integerVariable = integerVariable;
1129 	thy stringVariable = stringVariable;
1130 	thy variableName = variableName;
1131 	return thee;
1132 }
1133 
UiForm_addColour(UiForm me,MelderColour * colourVariable,conststring32 variableName,conststring32 label,conststring32 defaultValue)1134 UiField UiForm_addColour (UiForm me, MelderColour *colourVariable, conststring32 variableName, conststring32 label, conststring32 defaultValue) {
1135 	UiField thee = UiForm_addField (me, _kUiField_type::COLOUR_, label);
1136 	thy stringDefaultValue = Melder_dup (defaultValue);
1137 	thy colourVariable = colourVariable;
1138 	thy variableName = variableName;
1139 	return thee;
1140 }
1141 
UiForm_addChannel(UiForm me,integer * variable,conststring32 variableName,conststring32 label,conststring32 defaultValue)1142 UiField UiForm_addChannel (UiForm me, integer *variable, conststring32 variableName, conststring32 label, conststring32 defaultValue) {
1143 	UiField thee = UiForm_addField (me, _kUiField_type::CHANNEL_, label);
1144 	thy stringDefaultValue = Melder_dup (defaultValue);
1145 	thy integerVariable = variable;
1146 	thy variableName = variableName;
1147 	return thee;
1148 }
1149 
1150 #define DIALOG_X  150
1151 #define DIALOG_Y  70
1152 #define HELP_BUTTON_WIDTH  60
1153 #define STANDARDS_BUTTON_WIDTH  100
1154 #define REVERT_BUTTON_WIDTH  60
1155 #define STOP_BUTTON_WIDTH  50
1156 #define HELP_BUTTON_X  20
1157 #define LIST_HEIGHT  192
1158 
1159 static MelderString theFinishBuffer;
1160 
appendColon()1161 static void appendColon () {
1162 	integer length = theFinishBuffer.length;
1163 	if (length < 1)
1164 		return;
1165 	char32 lastCharacter = theFinishBuffer.string [length - 1];
1166 	if (lastCharacter == U':' || lastCharacter == U'?' || lastCharacter == U'.')
1167 		return;
1168 	MelderString_appendCharacter (& theFinishBuffer, U':');
1169 }
1170 
multiLineTextHeight(integer numberOfLines)1171 static int multiLineTextHeight (integer numberOfLines) {
1172 	if (numberOfLines <= 1)
1173 		return Gui_TEXTFIELD_HEIGHT;
1174 	#if defined (macintosh)
1175 		return numberOfLines * (Gui_TEXTFIELD_HEIGHT - 9) + 21;   // 15 is the minimum, but 21 gives some feedback about what is outside (2020-10-19)
1176 	#elif defined (_WIN32)
1177 		return numberOfLines * (Gui_TEXTFIELD_HEIGHT - 4) + 21;   // 21 is the mininum (2020-10-19)
1178 	#elif defined (linux)
1179 		return numberOfLines * (Gui_TEXTFIELD_HEIGHT - 8) + 15;   // 13 is the minimum, but if there is no horizontal scrollbar, there is a line more (2020-10-19)
1180 	#else
1181 		return numberOfLines * Gui_TEXTFIELD_HEIGHT;
1182 	#endif
1183 }
1184 
UiForm_finish(UiForm me)1185 void UiForm_finish (UiForm me) {
1186 	if (! my d_dialogParent && ! my isPauseForm)
1187 		return;
1188 
1189 	int size = my numberOfFields;
1190 	int dialogHeight = 0;
1191 	const int textFieldHeight = Gui_TEXTFIELD_HEIGHT;
1192 	const int headerLabelHeight = textFieldHeight
1193 		#ifdef _WIN32
1194 			- 6;
1195 		#else
1196 			- 10;
1197 		#endif
1198 	int dialogWidth = 520, dialogCentre = dialogWidth / 2, fieldX = dialogCentre + Gui_LABEL_SPACING / 2;
1199 	int labelWidth = fieldX - Gui_LABEL_SPACING - Gui_LEFT_DIALOG_SPACING, fieldWidth = labelWidth, halfFieldWidth = fieldWidth / 2 - 6;
1200 
1201 	GuiForm form;
1202 	bool okButtonIsDefault = true;
1203 
1204 	/*
1205 		Compute height. Cannot leave this to the default geometry management system.
1206 	*/
1207 	for (integer ifield = 1; ifield <= my numberOfFields; ifield ++ ) {
1208 		UiField thee = my field [ifield].get(), previous = my field [ifield - 1].get();
1209 		dialogHeight +=
1210 			ifield == 1 ?
1211 				Gui_TOP_DIALOG_SPACING
1212 			: thy type == _kUiField_type::RADIO_ || previous -> type == _kUiField_type::RADIO_ ?
1213 				Gui_VERTICAL_DIALOG_SPACING_DIFFERENT
1214 			: thy type >= _kUiField_type::LABELLED_TEXT_MIN_ && thy type <= _kUiField_type::LABELLED_TEXT_MAX_ && str32nequ (thy name.get(), U"right ", 6) &&
1215 					previous -> type >= _kUiField_type::LABELLED_TEXT_MIN_ && previous -> type <= _kUiField_type::LABELLED_TEXT_MAX_ &&
1216 							str32nequ (previous -> name.get(), U"left ", 5) ?
1217 				- textFieldHeight
1218 			:
1219 				Gui_VERTICAL_DIALOG_SPACING_SAME;
1220 		const bool thouHastVerticallyAddedLabel =
1221 			thy type == _kUiField_type::TEXT_ || thy type == _kUiField_type::FORMULA_ ||
1222 			thy type == _kUiField_type::INFILE_ || thy type == _kUiField_type::OUTFILE_ || thy type == _kUiField_type::FOLDER_ ||
1223 			thy type == _kUiField_type::REALVECTOR_ || thy type == _kUiField_type::POSITIVEVECTOR_ ||
1224 			thy type == _kUiField_type::INTEGERVECTOR_ || thy type == _kUiField_type::NATURALVECTOR_ ||
1225 			thy type == _kUiField_type::REALMATRIX_ ||
1226 			thy type == _kUiField_type::STRINGARRAY_
1227 		;
1228 		if (thouHastVerticallyAddedLabel)
1229 			dialogHeight += (headerLabelHeight + Gui_VERTICAL_DIALOG_SPACING_SAME) * !! thy formLabel;
1230 		thy y = dialogHeight;
1231 		dialogHeight +=
1232 			thy type == _kUiField_type::BOOLEAN_ ?
1233 				Gui_CHECKBUTTON_HEIGHT
1234 			: thy type == _kUiField_type::RADIO_ ?
1235 				thy options.size * Gui_RADIOBUTTON_HEIGHT + (thy options.size - 1) * Gui_RADIOBUTTON_SPACING
1236 			: thy type == _kUiField_type::OPTIONMENU_ ?
1237 				Gui_OPTIONMENU_HEIGHT
1238 			: thy type == _kUiField_type::LIST_ ?
1239 				LIST_HEIGHT
1240 			: thy type == _kUiField_type::LABEL_ && thy stringValue [0] != U'\0' && thy stringValue [str32len (thy stringValue.get()) - 1] != U'.' && ifield != my numberOfFields ?
1241 				headerLabelHeight
1242 			: thouHastVerticallyAddedLabel ?
1243 				multiLineTextHeight (thy numberOfLines)
1244 			:
1245 				textFieldHeight;
1246 	}
1247 	dialogHeight += 2 * Gui_BOTTOM_DIALOG_SPACING + Gui_PUSHBUTTON_HEIGHT;
1248 	my d_dialogForm = GuiDialog_create (my d_dialogParent, DIALOG_X, DIALOG_Y, dialogWidth, dialogHeight, my name.get(), gui_dialog_cb_close, me, 0);
1249 	GuiDialog_setDefaultCallback (my d_dialogForm, gui_dialog_cb_default, me);
1250 
1251 	form = my d_dialogForm;
1252 
1253 	for (integer ifield = 1; ifield <= size; ifield ++) {
1254 		UiField thee = my field [ifield].get();
1255 		switch (thy type)
1256 		{
1257 			case _kUiField_type::REAL_:
1258 			case _kUiField_type::REAL_OR_UNDEFINED_:
1259 			case _kUiField_type::POSITIVE_:
1260 			case _kUiField_type::INTEGER_:
1261 			case _kUiField_type::NATURAL_:
1262 			case _kUiField_type::WORD_:
1263 			case _kUiField_type::SENTENCE_:
1264 			case _kUiField_type::COLOUR_:
1265 			case _kUiField_type::CHANNEL_:
1266 			{
1267 				int ylabel = thy y;
1268 				#if defined (macintosh)
1269 					ylabel += 3;
1270 				#endif
1271 				if (str32nequ (thy name.get(), U"left ", 5)) {
1272 					MelderString_copy (& theFinishBuffer, thy formLabel.get() + 5);
1273 					appendColon ();
1274 					thy label = GuiLabel_createShown (form, 0, Gui_LEFT_DIALOG_SPACING + labelWidth, ylabel, ylabel + textFieldHeight,
1275 						theFinishBuffer.string, GuiLabel_RIGHT);
1276 					thy text = GuiText_createShown (form, fieldX, fieldX + halfFieldWidth, thy y, thy y + Gui_TEXTFIELD_HEIGHT, 0);
1277 				} else if (str32nequ (thy name.get(), U"right ", 6)) {
1278 					thy text = GuiText_createShown (form, fieldX + halfFieldWidth + 12, fieldX + fieldWidth,
1279 						thy y, thy y + Gui_TEXTFIELD_HEIGHT, 0);
1280 				} else {
1281 					MelderString_copy (& theFinishBuffer, thy formLabel.get());
1282 					appendColon ();
1283 					thy label = GuiLabel_createShown (form, 0, Gui_LEFT_DIALOG_SPACING + labelWidth,
1284 						ylabel, ylabel + textFieldHeight,
1285 						theFinishBuffer.string, GuiLabel_RIGHT);
1286 					thy text = GuiText_createShown (form, fieldX, fieldX + fieldWidth, // or once the dialog is a Form: - Gui_RIGHT_DIALOG_SPACING,
1287 						thy y, thy y + Gui_TEXTFIELD_HEIGHT, 0);
1288 				}
1289 			}
1290 			break;
1291 			case _kUiField_type::TEXT_:
1292 			{
1293 				MelderString_copy (& theFinishBuffer, thy formLabel.get());
1294 				appendColon ();
1295 				const int ylabel = thy y + 5 - headerLabelHeight - Gui_VERTICAL_DIALOG_SPACING_SAME;
1296 				thy label = GuiLabel_createShown (form,
1297 					Gui_LEFT_DIALOG_SPACING, dialogWidth /* allow to extend into the margin */,
1298 					ylabel, ylabel + textFieldHeight,
1299 					theFinishBuffer.string, 0
1300 				);
1301 				thy text = GuiText_createShown (form, Gui_LEFT_DIALOG_SPACING, dialogWidth - Gui_RIGHT_DIALOG_SPACING,
1302 					thy y, thy y + multiLineTextHeight (thy numberOfLines), GuiText_INKWRAP | GuiText_SCROLLED);
1303 			}
1304 			break;
1305 			case _kUiField_type::REALVECTOR_:
1306 			case _kUiField_type::POSITIVEVECTOR_:
1307 			{
1308 				MelderString_copy (& theFinishBuffer, thy formLabel.get());
1309 				appendColon ();
1310 				const int ylabel = thy y + 5 - headerLabelHeight - Gui_VERTICAL_DIALOG_SPACING_SAME;
1311 				thy label = GuiLabel_createShown (form,
1312 					Gui_LEFT_DIALOG_SPACING, dialogWidth /* allow to extend into the margin */,
1313 					ylabel, ylabel + textFieldHeight,
1314 					theFinishBuffer.string, 0
1315 				);
1316 				thy optionMenu = GuiOptionMenu_createShown (form,
1317 					dialogWidth - Gui_LEFT_DIALOG_SPACING - 200, dialogWidth - Gui_LEFT_DIALOG_SPACING,
1318 					thy y - Gui_OPTIONMENU_HEIGHT, thy y, 0);
1319 				for (int format = (int) kUi_realVectorFormat::MIN; format <= (int) kUi_realVectorFormat::MAX; format ++)
1320 					GuiOptionMenu_addOption (thy optionMenu, kUi_realVectorFormat_getText ((kUi_realVectorFormat) format));
1321 				//GuiOptionMenu_setValue (thy optionMenu, (int) thy realVectorDefaultFormat);   // SUPERFLUOUS
1322 				thy text = GuiText_createShown (form, Gui_LEFT_DIALOG_SPACING, dialogWidth - Gui_RIGHT_DIALOG_SPACING,
1323 					thy y, thy y + multiLineTextHeight (thy numberOfLines), GuiText_INKWRAP | GuiText_SCROLLED);
1324 			}
1325 			break;
1326 			case _kUiField_type::INTEGERVECTOR_:
1327 			case _kUiField_type::NATURALVECTOR_:
1328 			{
1329 				MelderString_copy (& theFinishBuffer, thy formLabel.get());
1330 				appendColon ();
1331 				const int ylabel = thy y + 5 - headerLabelHeight - Gui_VERTICAL_DIALOG_SPACING_SAME;
1332 				thy label = GuiLabel_createShown (form,
1333 					Gui_LEFT_DIALOG_SPACING, dialogWidth /* allow to extend into the margin */,
1334 					ylabel, ylabel + textFieldHeight,
1335 					theFinishBuffer.string, 0
1336 				);
1337 				thy optionMenu = GuiOptionMenu_createShown (form,
1338 					dialogWidth - Gui_LEFT_DIALOG_SPACING - 200, dialogWidth - Gui_LEFT_DIALOG_SPACING,
1339 					thy y - Gui_OPTIONMENU_HEIGHT, thy y, 0);
1340 				for (int format = (int) kUi_integerVectorFormat::MIN; format <= (int) kUi_integerVectorFormat::MAX; format ++)
1341 					GuiOptionMenu_addOption (thy optionMenu, kUi_integerVectorFormat_getText ((kUi_integerVectorFormat) format));
1342 				//GuiOptionMenu_setValue (thy optionMenu, (int) thy integerVectorDefaultFormat);   // SUPERFLUOUS
1343 				thy text = GuiText_createShown (form, Gui_LEFT_DIALOG_SPACING, dialogWidth - Gui_RIGHT_DIALOG_SPACING,
1344 					thy y, thy y + multiLineTextHeight (thy numberOfLines), GuiText_INKWRAP | GuiText_SCROLLED);
1345 			}
1346 			break;
1347 			case _kUiField_type::REALMATRIX_:
1348 			{
1349 				MelderString_copy (& theFinishBuffer, thy formLabel.get());
1350 				appendColon ();
1351 				const int ylabel = thy y + 5 - headerLabelHeight - Gui_VERTICAL_DIALOG_SPACING_SAME;
1352 				thy label = GuiLabel_createShown (form,
1353 					Gui_LEFT_DIALOG_SPACING, dialogWidth /* allow to extend into the margin */,
1354 					ylabel, ylabel + textFieldHeight,
1355 					theFinishBuffer.string, 0
1356 				);
1357 				thy text = GuiText_createShown (form, Gui_LEFT_DIALOG_SPACING, dialogWidth - Gui_RIGHT_DIALOG_SPACING,
1358 					thy y, thy y + multiLineTextHeight (thy numberOfLines), GuiText_SCROLLED);
1359 				thy optionMenu = GuiOptionMenu_createShown (form,
1360 					dialogWidth - Gui_LEFT_DIALOG_SPACING - 200, dialogWidth - Gui_LEFT_DIALOG_SPACING,
1361 					thy y - Gui_OPTIONMENU_HEIGHT, thy y, 0);
1362 				for (int format = (int) kUi_realMatrixFormat::MIN; format <= (int) kUi_realMatrixFormat::MAX; format ++)
1363 					GuiOptionMenu_addOption (thy optionMenu, kUi_realMatrixFormat_getText ((kUi_realMatrixFormat) format));
1364 				GuiOptionMenu_setValue (thy optionMenu, (int) theRealMatrixFormat);
1365 				okButtonIsDefault = false;   // because otherwise, the Enter key would be ambiguous
1366 			}
1367 			break;
1368 			case _kUiField_type::STRINGARRAY_:
1369 			{
1370 				MelderString_copy (& theFinishBuffer, thy formLabel.get());
1371 				appendColon ();
1372 				const int ylabel = thy y + 5 - headerLabelHeight - Gui_VERTICAL_DIALOG_SPACING_SAME;
1373 				thy label = GuiLabel_createShown (form,
1374 					Gui_LEFT_DIALOG_SPACING, dialogWidth /* allow to extend into the margin */,
1375 					ylabel, ylabel + textFieldHeight,
1376 					theFinishBuffer.string, 0
1377 				);
1378 				thy text = GuiText_createShown (form, Gui_LEFT_DIALOG_SPACING, dialogWidth - Gui_RIGHT_DIALOG_SPACING,
1379 					thy y, thy y + multiLineTextHeight (thy numberOfLines), GuiText_INKWRAP | GuiText_SCROLLED);
1380 				thy optionMenu = GuiOptionMenu_createShown (form,
1381 					dialogWidth - Gui_LEFT_DIALOG_SPACING - 200, dialogWidth - Gui_LEFT_DIALOG_SPACING,
1382 					thy y - Gui_OPTIONMENU_HEIGHT, thy y, 0);
1383 				for (int format = (int) kUi_stringArrayFormat::MIN; format <= (int) kUi_stringArrayFormat::MAX; format ++)
1384 					GuiOptionMenu_addOption (thy optionMenu, kUi_stringArrayFormat_getText ((kUi_stringArrayFormat) format));
1385 				GuiOptionMenu_setValue (thy optionMenu, (int) theStringArrayFormat);
1386 			}
1387 			break;
1388 			case _kUiField_type::FORMULA_:
1389 			{
1390 				MelderString_copy (& theFinishBuffer, thy formLabel.get());
1391 				appendColon ();
1392 				const int ylabel = thy y + 5 - headerLabelHeight - Gui_VERTICAL_DIALOG_SPACING_SAME;
1393 				thy label = GuiLabel_createShown (form,
1394 					Gui_LEFT_DIALOG_SPACING, dialogWidth /* allow to extend into the margin */,
1395 					ylabel, ylabel + textFieldHeight,
1396 					theFinishBuffer.string, 0
1397 				);
1398 				thy text = GuiText_createShown (form, Gui_LEFT_DIALOG_SPACING, dialogWidth - Gui_RIGHT_DIALOG_SPACING,
1399 					thy y, thy y + multiLineTextHeight (thy numberOfLines), GuiText_INKWRAP | GuiText_SCROLLED);
1400 			}
1401 			break;
1402 			case _kUiField_type::INFILE_:
1403 			{
1404 				MelderString_copy (& theFinishBuffer, thy formLabel.get());
1405 				appendColon ();
1406 				const int ylabel = thy y + 5 - headerLabelHeight - Gui_VERTICAL_DIALOG_SPACING_SAME;
1407 				thy label = GuiLabel_createShown (form,
1408 					Gui_LEFT_DIALOG_SPACING, dialogWidth /* allow to extend into the margin */,
1409 					ylabel, ylabel + textFieldHeight,
1410 					theFinishBuffer.string, 0
1411 				);
1412 				thy text = GuiText_createShown (form, Gui_LEFT_DIALOG_SPACING, dialogWidth - Gui_RIGHT_DIALOG_SPACING,
1413 					thy y, thy y + multiLineTextHeight (thy numberOfLines), GuiText_CHARWRAP | GuiText_SCROLLED);
1414 				thy pushButton = GuiButton_createShown (form,
1415 					dialogWidth - Gui_LEFT_DIALOG_SPACING - 100, dialogWidth - Gui_LEFT_DIALOG_SPACING,
1416 					thy y - 3 - Gui_PUSHBUTTON_HEIGHT, thy y - 3, U"Browse...", gui_button_cb_browseInfile, thee, 0
1417 				);
1418 			}
1419 			break;
1420 			case _kUiField_type::OUTFILE_:
1421 			{
1422 				MelderString_copy (& theFinishBuffer, thy formLabel.get());
1423 				appendColon ();
1424 				const int ylabel = thy y + 5 - headerLabelHeight - Gui_VERTICAL_DIALOG_SPACING_SAME;
1425 				thy label = GuiLabel_createShown (form,
1426 					Gui_LEFT_DIALOG_SPACING, dialogWidth /* allow to extend into the margin */,
1427 					ylabel, ylabel + textFieldHeight,
1428 					theFinishBuffer.string, 0
1429 				);
1430 				thy text = GuiText_createShown (form, Gui_LEFT_DIALOG_SPACING, dialogWidth - Gui_RIGHT_DIALOG_SPACING,
1431 					thy y, thy y + multiLineTextHeight (thy numberOfLines), GuiText_CHARWRAP | GuiText_SCROLLED);
1432 				thy pushButton = GuiButton_createShown (form,
1433 					dialogWidth - Gui_LEFT_DIALOG_SPACING - 100, dialogWidth - Gui_LEFT_DIALOG_SPACING,
1434 					thy y - 3 - Gui_PUSHBUTTON_HEIGHT, thy y - 3, U"Browse...", gui_button_cb_browseOutfile, thee, 0
1435 				);
1436 			}
1437 			break;
1438 			case _kUiField_type::FOLDER_:
1439 			{
1440 				MelderString_copy (& theFinishBuffer, thy formLabel.get());
1441 				appendColon ();
1442 				const int ylabel = thy y + 5 - headerLabelHeight - Gui_VERTICAL_DIALOG_SPACING_SAME;
1443 				thy label = GuiLabel_createShown (form,
1444 					Gui_LEFT_DIALOG_SPACING, dialogWidth /* allow to extend into the margin */,
1445 					ylabel, ylabel + textFieldHeight,
1446 					theFinishBuffer.string, 0
1447 				);
1448 				thy text = GuiText_createShown (form, Gui_LEFT_DIALOG_SPACING, dialogWidth - Gui_RIGHT_DIALOG_SPACING,
1449 					thy y, thy y + multiLineTextHeight (thy numberOfLines), GuiText_CHARWRAP | GuiText_SCROLLED);
1450 				thy pushButton = GuiButton_createShown (form,
1451 					dialogWidth - Gui_LEFT_DIALOG_SPACING - 100, dialogWidth - Gui_LEFT_DIALOG_SPACING,
1452 					thy y - 3 - Gui_PUSHBUTTON_HEIGHT, thy y - 3, U"Browse...", gui_button_cb_browseFolder, thee, 0
1453 				);
1454 			}
1455 			break;
1456 			case _kUiField_type::LABEL_:
1457 			{
1458 				MelderString_copy (& theFinishBuffer, thy stringValue.get());
1459 				thy label = GuiLabel_createShown (form,
1460 					Gui_LEFT_DIALOG_SPACING, dialogWidth /* allow to extend into the margin */,
1461 					thy y + 5, thy y + 5 + textFieldHeight,
1462 					theFinishBuffer.string, 0
1463 				);
1464 			}
1465 			break;
1466 			case _kUiField_type::RADIO_:
1467 			{
1468 				int ylabel = thy y;
1469 				#if defined (macintosh)
1470 					ylabel += 1;
1471 				#endif
1472 				MelderString_copy (& theFinishBuffer, thy formLabel.get());
1473 				appendColon ();
1474 				thy label = GuiLabel_createShown (form, Gui_LEFT_DIALOG_SPACING, Gui_LEFT_DIALOG_SPACING + labelWidth, ylabel, ylabel + Gui_RADIOBUTTON_HEIGHT,
1475 					theFinishBuffer.string, GuiLabel_RIGHT);
1476 				GuiRadioGroup_begin ();
1477 				for (integer ibutton = 1; ibutton <= thy options.size; ibutton ++) {
1478 					UiOption button = thy options.at [ibutton];
1479 					MelderString_copy (& theFinishBuffer, button -> name.get());
1480 					button -> radioButton = GuiRadioButton_createShown (form,
1481 						fieldX, dialogWidth /* allow to extend into the margin */,
1482 						thy y + (ibutton - 1) * (Gui_RADIOBUTTON_HEIGHT + Gui_RADIOBUTTON_SPACING),
1483 						thy y + (ibutton - 1) * (Gui_RADIOBUTTON_HEIGHT + Gui_RADIOBUTTON_SPACING) + Gui_RADIOBUTTON_HEIGHT,
1484 						theFinishBuffer.string, nullptr, nullptr, 0);
1485 				}
1486 				GuiRadioGroup_end ();
1487 			}
1488 			break;
1489 			case _kUiField_type::OPTIONMENU_:
1490 			{
1491 				int ylabel = thy y;
1492 				#if defined (macintosh)
1493 					ylabel += 2;
1494 				#endif
1495 				MelderString_copy (& theFinishBuffer, thy formLabel.get());
1496 				appendColon ();
1497 				thy label = GuiLabel_createShown (form, Gui_LEFT_DIALOG_SPACING, Gui_LEFT_DIALOG_SPACING + labelWidth, ylabel, ylabel + Gui_OPTIONMENU_HEIGHT,
1498 					theFinishBuffer.string, GuiLabel_RIGHT);
1499 				thy optionMenu = GuiOptionMenu_createShown (form, fieldX, fieldX + fieldWidth, thy y, thy y + Gui_OPTIONMENU_HEIGHT, 0);
1500 				for (integer ibutton = 1; ibutton <= thy options.size; ibutton ++) {
1501 					UiOption button = thy options.at [ibutton];
1502 					MelderString_copy (& theFinishBuffer, button -> name.get());
1503 					GuiOptionMenu_addOption (thy optionMenu, theFinishBuffer.string);
1504 				}
1505 			}
1506 			break;
1507 			case _kUiField_type::BOOLEAN_:
1508 			{
1509 				MelderString_copy (& theFinishBuffer, thy formLabel.get());
1510 				/*field -> label = GuiLabel_createShown (form, x, x + labelWidth, thy y, thy y + Gui_CHECKBUTTON_HEIGHT,
1511 					theFinishBuffer.string, GuiLabel_RIGHT); */
1512 				thy checkButton = GuiCheckButton_createShown (form,
1513 					fieldX, dialogWidth /* allow to extend into the margin */, thy y, thy y + Gui_CHECKBUTTON_HEIGHT,
1514 					theFinishBuffer.string, nullptr, nullptr, 0);
1515 			}
1516 			break;
1517 			case _kUiField_type::LIST_:
1518 			{
1519 				int listWidth = my numberOfFields == 1 ? dialogWidth - fieldX : fieldWidth;
1520 				MelderString_copy (& theFinishBuffer, thy formLabel.get());
1521 				appendColon ();
1522 				thy label = GuiLabel_createShown (form, Gui_LEFT_DIALOG_SPACING, Gui_LEFT_DIALOG_SPACING + labelWidth, thy y + 1, thy y + 21,
1523 					theFinishBuffer.string, GuiLabel_RIGHT);
1524 				thy list = GuiList_create (form, fieldX, fieldX + listWidth, thy y, thy y + LIST_HEIGHT, false, theFinishBuffer.string);
1525 				for (integer i = 1; i <= thy strings.size; i ++)
1526 					GuiList_insertItem (thy list, thy strings [i], 0);
1527 				GuiThing_show (thy list);
1528 			}
1529 			break;
1530 		}
1531 	}
1532 	for (integer ifield = 1; ifield <= my numberOfFields; ifield ++)
1533 		UiField_setDefault (my field [ifield].get());
1534 	/*separator = XmCreateSeparatorGadget (column, "separator", nullptr, 0);*/
1535 	const int y = dialogHeight - Gui_BOTTOM_DIALOG_SPACING - Gui_PUSHBUTTON_HEIGHT;
1536 	if (my helpTitle) {
1537 		my helpButton = GuiButton_createShown (form, HELP_BUTTON_X, HELP_BUTTON_X + HELP_BUTTON_WIDTH, y, y + Gui_PUSHBUTTON_HEIGHT,
1538 			U"Help", gui_button_cb_help, me, 0);
1539 	}
1540 	bool commentsOnly = true;
1541 	for (integer ifield = 1; ifield <= my numberOfFields; ifield ++) {
1542 		if (my field [ifield] -> type != _kUiField_type::LABEL_) {
1543 			commentsOnly = false;
1544 			break;
1545 		}
1546 	}
1547 	if (! commentsOnly) {
1548 		if (my isPauseForm) {
1549 			my revertButton = GuiButton_createShown (form,
1550 				HELP_BUTTON_X, HELP_BUTTON_X + REVERT_BUTTON_WIDTH,
1551 				y, y + Gui_PUSHBUTTON_HEIGHT, U"Undo", gui_button_cb_revert, me, 0);
1552 		} else {
1553 			my revertButton = GuiButton_createShown (form,
1554 				HELP_BUTTON_X + HELP_BUTTON_WIDTH + Gui_HORIZONTAL_DIALOG_SPACING,
1555 				HELP_BUTTON_X + HELP_BUTTON_WIDTH + Gui_HORIZONTAL_DIALOG_SPACING + STANDARDS_BUTTON_WIDTH,
1556 				y, y + Gui_PUSHBUTTON_HEIGHT, U"Standards", gui_button_cb_revert, me, 0);
1557 		}
1558 	}
1559 	if (my isPauseForm) {
1560 		int x = HELP_BUTTON_X + REVERT_BUTTON_WIDTH + Gui_HORIZONTAL_DIALOG_SPACING;
1561 		if (my cancelContinueButton == 0) {
1562 			my cancelButton = GuiButton_createShown (form, x, x + STOP_BUTTON_WIDTH, y, y + Gui_PUSHBUTTON_HEIGHT,
1563 				U"Stop", gui_button_cb_cancel, me, GuiButton_CANCEL);
1564 			x += STOP_BUTTON_WIDTH + 7;
1565 		} else {
1566 			x += 30;
1567 		}
1568 		int room = dialogWidth - Gui_RIGHT_DIALOG_SPACING - x;
1569 		int roomPerContinueButton = room / my numberOfContinueButtons;
1570 		int horizontalSpacing = (
1571 			my numberOfContinueButtons > 7 ?
1572 				Gui_HORIZONTAL_DIALOG_SPACING - 2 * (my numberOfContinueButtons - 7)
1573 			:
1574 				Gui_HORIZONTAL_DIALOG_SPACING
1575 		);
1576 		int continueButtonWidth = roomPerContinueButton - horizontalSpacing;
1577 		for (int i = 1; i <= my numberOfContinueButtons; i ++) {
1578 			x = dialogWidth - Gui_RIGHT_DIALOG_SPACING - roomPerContinueButton * (my numberOfContinueButtons - i + 1) + horizontalSpacing;
1579 			my continueButtons [i] = GuiButton_createShown (form, x, x + continueButtonWidth, y, y + Gui_PUSHBUTTON_HEIGHT,
1580 				my continueTexts [i], gui_button_cb_ok, me,
1581 				i == my defaultContinueButton && okButtonIsDefault ? GuiButton_DEFAULT : i == my cancelContinueButton ? GuiButton_CANCEL : 0
1582 			);
1583 		}
1584 	} else {
1585 		int x = dialogWidth - Gui_RIGHT_DIALOG_SPACING - Gui_OK_BUTTON_WIDTH - 2 * Gui_HORIZONTAL_DIALOG_SPACING
1586 			 - Gui_APPLY_BUTTON_WIDTH - Gui_CANCEL_BUTTON_WIDTH;
1587 		my cancelButton = GuiButton_createShown (form, x, x + Gui_CANCEL_BUTTON_WIDTH, y, y + Gui_PUSHBUTTON_HEIGHT,
1588 			U"Cancel", gui_button_cb_cancel, me, GuiButton_CANCEL);
1589 		x = dialogWidth - Gui_RIGHT_DIALOG_SPACING - Gui_OK_BUTTON_WIDTH - Gui_HORIZONTAL_DIALOG_SPACING - Gui_APPLY_BUTTON_WIDTH;
1590 		if (my numberOfFields > 1 || my field [1] -> type != _kUiField_type::LABEL_) {
1591 			my applyButton = GuiButton_createShown (form, x, x + Gui_APPLY_BUTTON_WIDTH, y, y + Gui_PUSHBUTTON_HEIGHT,
1592 				U"Apply", gui_button_cb_apply, me, 0);
1593 		}
1594 		x = dialogWidth - Gui_RIGHT_DIALOG_SPACING - Gui_OK_BUTTON_WIDTH;
1595 		my okButton = GuiButton_createShown (form, x, x + Gui_OK_BUTTON_WIDTH, y, y + Gui_PUSHBUTTON_HEIGHT,
1596 			my isPauseForm ? U"Continue" : U"OK", gui_button_cb_ok, me, okButtonIsDefault ? GuiButton_DEFAULT : 0);
1597 	}
1598 	/*GuiObject_show (separator);*/
1599 }
1600 
UiForm_destroyWhenUnmanaged(UiForm me)1601 void UiForm_destroyWhenUnmanaged (UiForm me) {
1602 	my destroyWhenUnmanaged = true;
1603 }
1604 
UiForm_do(UiForm me,bool modified)1605 void UiForm_do (UiForm me, bool modified) {
1606 	my allowExecutionHook = theAllowExecutionHookHint;
1607 	my allowExecutionClosure = theAllowExecutionClosureHint;
1608 	Melder_assert (my d_dialogForm);
1609 	GuiThing_show (my d_dialogForm);
1610 	if (modified)
1611 		UiForm_okOrApply (me, nullptr, true);
1612 }
1613 
UiField_api_header_C(UiField me,UiField next,bool isLastNonLabelField)1614 static void UiField_api_header_C (UiField me, UiField next, bool isLastNonLabelField) {
1615 	if (my type == _kUiField_type::LABEL_) {
1616 		bool weAreFollowedByAWideField =
1617 			next && (next -> type == _kUiField_type::TEXT_ || next -> type == _kUiField_type::FORMULA_ ||
1618 			next -> type == _kUiField_type::INFILE_ || next -> type == _kUiField_type::OUTFILE_ ||
1619 			next -> type == _kUiField_type::FOLDER_ ||
1620 			next -> type == _kUiField_type::REALMATRIX_ ||
1621 			next -> type == _kUiField_type::STRINGARRAY_);
1622 		bool weLabelTheFollowingField =
1623 			weAreFollowedByAWideField &&
1624 			Melder_stringMatchesCriterion (my stringValue.get(), kMelder_string::ENDS_WITH, U":", true);
1625 		bool weAreAComment = ! weLabelTheFollowingField;
1626 		if (weAreAComment)
1627 			MelderInfo_writeLine (U"\t/* ", my stringValue.get(), U" */");
1628 		return;
1629 	}
1630 
1631 	/*
1632 		Write the type of the field.
1633 	*/
1634 	bool isText = false, isBoolean = false, isEnum = false, isPositive = false;
1635 	switch (my type)
1636 	{
1637 		case _kUiField_type::REAL_:
1638 		case _kUiField_type::REAL_OR_UNDEFINED_:
1639 		case _kUiField_type::POSITIVE_:
1640 		{
1641 			MelderInfo_write (U"\tdouble ");
1642 			isPositive = ( my type == _kUiField_type::POSITIVE_);
1643 		}
1644 		break;
1645 		case _kUiField_type::INTEGER_:
1646 		case _kUiField_type::NATURAL_:
1647 		case _kUiField_type::CHANNEL_:
1648 		{
1649 			MelderInfo_write (U"\tint64_t ");
1650 			isPositive = ( my type == _kUiField_type::NATURAL_);
1651 		}
1652 		break;
1653 		case _kUiField_type::WORD_:
1654 		case _kUiField_type::SENTENCE_:
1655 		case _kUiField_type::TEXT_:
1656 		case _kUiField_type::FORMULA_:
1657 		case _kUiField_type::INFILE_:
1658 		case _kUiField_type::OUTFILE_:
1659 		case _kUiField_type::FOLDER_:
1660 		case _kUiField_type::COLOUR_:
1661 		case _kUiField_type::LIST_:
1662 		{
1663 			MelderInfo_write (U"\tconst char *");
1664 			isText = true;
1665 		}
1666 		break;
1667 		case _kUiField_type::RADIO_:
1668 		case _kUiField_type::OPTIONMENU_:
1669 		{
1670 			MelderInfo_write (U"\tconst char *");
1671 			isText = true;
1672 			isEnum = true;
1673 		}
1674 		break;
1675 		case _kUiField_type::BOOLEAN_:
1676 		{
1677 			MelderInfo_write (U"\tint32_t ");
1678 			isBoolean = true;
1679 		}
1680 		break;
1681 		default:
1682 		{
1683 		}
1684 	}
1685 
1686 	/*
1687 		Write the title of the field.
1688 	*/
1689 	char32 cName [100], *q = & cName [0];
1690 	Melder_assert (my formLabel);
1691 	const char32 *p = & my formLabel [0];
1692 	*q ++ = Melder_toLowerCase (*p ++);
1693 	bool up = false;
1694 	for (; *p != U'\0'; p ++) {
1695 		if (*p == U'(') {
1696 			break;
1697 		} else if (*p == U'\'') {
1698 			continue;
1699 		} else if (*p == U' ' || *p == U'-') {
1700 			if (p [1] == U'(') { p ++; break; }
1701 			up = true;
1702 		} else if (*p == U'*') {
1703 			*q ++ = U'S';
1704 			*q ++ = U't';
1705 			*q ++ = U'a';
1706 			*q ++ = U'r';
1707 		} else if (up) {
1708 			*q ++ = Melder_toUpperCase (*p);
1709 			up = false;
1710 		} else {
1711 			*q ++ = *p;
1712 		}
1713 	}
1714 	*q = U'\0';
1715 	if (! my variableName)
1716 		Melder_warning (U"Missing variable name for field label: ", my formLabel.get());
1717 	MelderInfo_write (my variableName ? my variableName : cName);
1718 	if (! isLastNonLabelField) MelderInfo_write (U",");
1719 
1720 	/*
1721 		Get the units.
1722 	*/
1723 	char32 units [100];
1724 	q = & units [0];
1725 	if (*p == U'(') {
1726 		for (p ++; *p != U'\0'; p ++) {
1727 			if (*p == U')') {
1728 				break;
1729 			} else {
1730 				*q ++ = *p;
1731 			}
1732 		}
1733 	}
1734 	*q = U'\0';
1735 	bool unitsAreAvailable = ( units [0] != U'\0' );
1736 	bool unitsContainRange = str32str (units, U"-");
1737 
1738 	/*
1739 		Get the example.
1740 	*/
1741 	conststring32 example = my stringDefaultValue.get();   // BUG dangle
1742 	bool exampleIsAvailable = ( example && example [0] != U'\0' );
1743 
1744 	if (exampleIsAvailable) {
1745 		/*
1746 			Split up the default string.
1747 		*/
1748 		char32 defaultValue [100], defaultComment [100];
1749 		str32cpy (defaultValue, my stringDefaultValue.get());
1750 		str32cpy (defaultComment, U"");
1751 		if (unitsAreAvailable) {
1752 			char32 *parenthesis = str32chr (defaultValue, U'(');
1753 			if (parenthesis && parenthesis - defaultValue > 1) {
1754 				parenthesis [-1] = U'\0';
1755 				str32cpy (defaultComment, parenthesis);
1756 			}
1757 		}
1758 
1759 		MelderInfo_write (U"   // ");
1760 		if (isPositive)
1761 			MelderInfo_write (U"positive, ");
1762 		if (unitsContainRange)
1763 			MelderInfo_write (units, U", ");
1764 		MelderInfo_write (U"e.g. ");
1765 		if (isText)
1766 			MelderInfo_write (U"\"");
1767 		MelderInfo_write (defaultValue);
1768 		if (isText)
1769 			MelderInfo_write (U"\"");
1770 		if (unitsAreAvailable && ! unitsContainRange) {
1771 			MelderInfo_write (U" ", units);
1772 		}
1773 		if (defaultComment [0]) {
1774 			MelderInfo_write (U" ", defaultComment);
1775 		}
1776 	} else if (isBoolean) {
1777 		MelderInfo_write (U"   // boolean, e.g. ");
1778 		MelderInfo_write (my integerDefaultValue, my integerDefaultValue ? U" (true)" : U" (false)");
1779 	} else if (isEnum) {
1780 		MelderInfo_write (U"   // e.g. \"");
1781 		MelderInfo_write (my options.at [my integerDefaultValue] -> name.get());
1782 		MelderInfo_write (U"\"; other choice", ( my options.size > 2 ? U"s" : U"" ), U":");
1783 		bool firstWritten = false;
1784 		for (int i = 1; i <= my options.size; i ++) {
1785 			if (i == my integerDefaultValue)
1786 				continue;
1787 			if (firstWritten) MelderInfo_write (U",");
1788 			MelderInfo_write (U" \"", my options.at [i] -> name.get(), U"\"");
1789 			firstWritten = true;
1790 		}
1791 	}
1792 	MelderInfo_writeLine (U"");
1793 }
1794 
UiForm_info(UiForm me,integer narg)1795 void UiForm_info (UiForm me, integer narg) {
1796 	if (narg == -1) {
1797 		/*
1798 			The C interface.
1799 		*/
1800 		int lastNonLabelFieldNumber = 0;
1801 		for (int ifield = my numberOfFields; ifield > 0; ifield --) {
1802 			if (my field [ifield] -> type != _kUiField_type::LABEL_) {
1803 				lastNonLabelFieldNumber = ifield;
1804 				break;
1805 			}
1806 		}
1807 		for (int ifield = 1; ifield <= my numberOfFields; ifield ++)
1808 			UiField_api_header_C (my field [ifield].get(), ifield == my numberOfFields ? nullptr : my field [ifield + 1].get(), ifield == lastNonLabelFieldNumber);
1809 	}
1810 }
1811 
UiField_argToValue(UiField me,Stackel arg,Interpreter)1812 static void UiField_argToValue (UiField me, Stackel arg, Interpreter /* interpreter */) {
1813 	switch (my type)
1814 	{
1815 		case _kUiField_type::REAL_:
1816 		case _kUiField_type::REAL_OR_UNDEFINED_:
1817 		case _kUiField_type::POSITIVE_:
1818 		{
1819 			if (arg -> which != Stackel_NUMBER)
1820 				Melder_throw (U"Argument \"", my name.get(), U"\" should be a number, not ", arg -> whichText(), U".");
1821 			my realValue = arg -> number;
1822 			if (isundef (my realValue) && my type != _kUiField_type::REAL_OR_UNDEFINED_)
1823 				Melder_throw (U"Argument \"", my name.get(), U"\" has the value \"undefined\".");
1824 			if (my type == _kUiField_type::POSITIVE_ && my realValue <= 0.0)
1825 				Melder_throw (U"Argument \"", my name.get(), U"\" must be greater than 0.");
1826 			if (my realVariable)
1827 				*my realVariable = my realValue;
1828 		}
1829 		break;
1830 		case _kUiField_type::INTEGER_:
1831 		case _kUiField_type::NATURAL_:
1832 		case _kUiField_type::CHANNEL_:
1833 		{
1834 			if (arg -> which == Stackel_STRING) {
1835 				if (my type == _kUiField_type::CHANNEL_) {
1836 					if (str32equ (arg -> getString(), U"All") || str32equ (arg -> getString(), U"Average")) {
1837 						my integerValue = 0;
1838 					} else if (str32equ (arg -> getString(), U"Left") || str32equ (arg -> getString(), U"Mono")) {
1839 						my integerValue = 1;
1840 					} else if (str32equ (arg -> getString(), U"Right") || str32equ (arg -> getString(), U"Stereo")) {
1841 						my integerValue = 2;
1842 					} else {
1843 						Melder_throw (U"Channel argument \"", my name.get(),
1844 							U"\" can only be a number or one of the strings \"All\", \"Average\", \"Left\", \"Right\", \"Mono\" or \"Stereo\".");
1845 					}
1846 				} else {
1847 					Melder_throw (U"Argument \"", my name.get(), U"\" should be a number, not ", arg -> whichText(), U".");
1848 				}
1849 			} else if (arg -> which == Stackel_NUMBER) {
1850 				double realValue = arg -> number;
1851 				my integerValue = Melder_iround (realValue);
1852 				Melder_require (my integerValue == realValue,
1853 					U"Argument \"", my name.get(), U"\" should be a whole number.");
1854 				if (my type == _kUiField_type::NATURAL_ && my integerValue < 1)
1855 					Melder_throw (U"Argument \"", my name.get(), U"\" should be a positive whole number.");
1856 			} else {
1857 				Melder_throw (U"Argument \"", my name.get(), U"\" should be a number, not ", arg -> whichText(), U".");
1858 			}
1859 			if (my integerVariable)
1860 				*my integerVariable = my integerValue;
1861 		}
1862 		break;
1863 		case _kUiField_type::WORD_:
1864 		case _kUiField_type::SENTENCE_:
1865 		case _kUiField_type::TEXT_:
1866 		case _kUiField_type::FORMULA_:
1867 		case _kUiField_type::INFILE_:
1868 		case _kUiField_type::OUTFILE_:
1869 		case _kUiField_type::FOLDER_:
1870 		{
1871 			if (arg -> which != Stackel_STRING)
1872 				Melder_throw (U"Argument \"", my name.get(), U"\" should be a string, not ", arg -> whichText(), U".");
1873 			my stringValue = Melder_dup (arg -> getString());
1874 			if (my stringVariable)
1875 				*my stringVariable = my stringValue.get();   // BUG dangle
1876 		}
1877 		break;
1878 		case _kUiField_type::REALVECTOR_:
1879 		case _kUiField_type::POSITIVEVECTOR_:
1880 		{
1881 			if (arg -> which != Stackel_NUMERIC_VECTOR && arg -> which != Stackel_STRING)
1882 				Melder_throw (U"Argument \"", my name.get(), U"\" should be a numeric vector, not ", arg -> whichText(), U".");
1883 			if (arg -> which == Stackel_STRING) {
1884 				my realVectorValue = splitByWhitespace_VEC (arg -> getString());
1885 			} else if (arg -> owned) {
1886 				my realVectorValue. adoptFromAmbiguousOwner (arg -> numericVector);
1887 				arg -> owned = false;
1888 			} else {
1889 				my realVectorValue = copy_VEC (arg -> numericVector);
1890 			}
1891 			if (my type == _kUiField_type::POSITIVEVECTOR_)
1892 				for (integer i = 1; i <= my realVectorValue.size; i ++)
1893 					if (my realVectorValue [i] <= 0.0)
1894 						Melder_throw (U"Element ", i, U" of vector “", my name.get(), U"” is ", my realVectorValue [i], U" but should be greater than 0.0.");
1895 			if (my realVectorVariable)
1896 				*my realVectorVariable = my realVectorValue.get();
1897 		}
1898 		break;
1899 		case _kUiField_type::INTEGERVECTOR_:
1900 		case _kUiField_type::NATURALVECTOR_:
1901 		{
1902 			if (arg -> which != Stackel_NUMERIC_VECTOR && arg -> which != Stackel_STRING)
1903 				Melder_throw (U"Argument \"", my name.get(), U"\" should be a numeric vector, not ", arg -> whichText(), U".");
1904 			if (arg -> which == Stackel_STRING) {
1905 				my integerVectorValue = splitByWhitespaceWithRanges_INTVEC (arg -> getString());
1906 			} else {
1907 				my integerVectorValue = raw_INTVEC (arg -> numericVector.size);
1908 				for (integer i = 1; i <= arg -> numericVector.size; i ++) {
1909 					my integerVectorValue [i] = Melder_iround (arg -> numericVector [i]);
1910 					Melder_require (my integerVectorValue [i] == arg -> numericVector [i],
1911 						U"Element ", i, U" of vector “", my name.get(), U"” is ", arg -> numericVector [i], U" but should be a whole number.");
1912 				}
1913 			}
1914 			if (my type == _kUiField_type::NATURALVECTOR_)
1915 				for (integer i = 1; i <= my integerVectorValue.size; i ++)
1916 					if (my integerVectorValue [i] <= 0)
1917 						Melder_throw (U"Element ", i, U" of vector “", my name.get(), U"” is ", my integerVectorValue [i], U" but should be greater than 0.");
1918 			if (my integerVectorVariable)
1919 				*my integerVectorVariable = my integerVectorValue.get();
1920 		}
1921 		break;
1922 		case _kUiField_type::REALMATRIX_:
1923 		{
1924 			if (arg -> which != Stackel_NUMERIC_MATRIX)
1925 				Melder_throw (U"Argument \"", my name.get(), U"\" should be a numeric matrix, not ", arg -> whichText(), U".");
1926 			if (arg -> owned) {
1927 				my numericMatrixValue. adoptFromAmbiguousOwner (arg -> numericMatrix);
1928 				arg -> owned = false;
1929 			} else {
1930 				my numericMatrixValue = copy_MAT (arg -> numericMatrix);
1931 			}
1932 			if (my numericMatrixVariable)
1933 				*my numericMatrixVariable = my numericMatrixValue.get();
1934 		}
1935 		break;
1936 		case _kUiField_type::STRINGARRAY_:
1937 		{
1938 			if (arg -> which != Stackel_STRING_ARRAY && arg -> which != Stackel_STRING)
1939 				Melder_throw (U"Argument \"", my name.get(), U"\" should be a string array, not ", arg -> whichText(), U".");
1940 			if (arg -> which == Stackel_STRING) {
1941 				my stringArrayValue = splitByWhitespace_STRVEC (arg -> getString());
1942 			} else if (arg -> owned) {
1943 				my stringArrayValue. adoptFromAmbiguousOwner (arg -> stringArray);
1944 				arg -> owned = false;
1945 			} else {
1946 				my stringArrayValue = copy_STRVEC (arg -> stringArray);
1947 			}
1948 			if (my stringArrayVariable)
1949 				*my stringArrayVariable = my stringArrayValue.get();
1950 		}
1951 		break;
1952 		case _kUiField_type::BOOLEAN_:
1953 		{
1954 			if (arg -> which == Stackel_STRING) {
1955 				if (str32equ (arg -> getString(), U"no") || str32equ (arg -> getString(), U"off")) {
1956 					my integerValue = 0;
1957 				} else if (str32equ (arg -> getString(), U"yes") || str32equ (arg -> getString(), U"on")) {
1958 					my integerValue = 1;
1959 				} else {
1960 					Melder_throw (U"Boolean argument \"", my name.get(),
1961 						U"\" can only be a number or one of the strings \"yes\" or \"no\".");
1962 				}
1963 			} else if (arg -> which == Stackel_NUMBER) {
1964 				my integerValue = ( arg -> number == 0.0 ? 0.0 : 1.0 );
1965 			} else {
1966 				Melder_throw (U"Boolean argument \"", my name.get(), U"\" should be a number (0 or 1), not ", arg -> whichText(), U".");
1967 			}
1968 			if (my boolVariable)
1969 				*my boolVariable = my integerValue;
1970 		}
1971 		break;
1972 		case _kUiField_type::RADIO_:
1973 		case _kUiField_type::OPTIONMENU_:
1974 		{
1975 			if (arg -> which != Stackel_STRING)
1976 				Melder_throw (U"Option argument \"", my name.get(), U"\" should be a string, not ", arg -> whichText(), U".");
1977 			my integerValue = 0;
1978 			for (int i = 1; i <= my options.size; i ++) {
1979 				UiOption b = my options.at [i];
1980 				if (str32equ (arg -> getString(), b -> name.get()))
1981 					my integerValue = i;
1982 			}
1983 			if (my integerValue == 0) {
1984 				/*
1985 					Retry with different case.
1986 				*/
1987 				for (int i = 1; i <= my options.size; i ++) {
1988 					UiOption b = my options.at [i];
1989 					if (Melder_equ_firstCharacterCaseInsensitive (arg -> getString(), b -> name.get()))
1990 						my integerValue = i;
1991 				}
1992 			}
1993 			if (my integerValue == 0) {
1994 				if (my intVariable)
1995 					Melder_throw (U"Option argument \"", my name.get(), U"\" cannot have the value \"", arg -> getString(), U"\".");
1996 				if (my stringVariable) {
1997 					*my stringVariable = arg -> getString();
1998 					return;
1999 				}
2000 			}
2001 			if (my intVariable)
2002 				*my intVariable = int (my integerValue) - my subtract;
2003 			if (my stringVariable)
2004 				*my stringVariable = my options.at [my integerValue] -> name.get();
2005 		}
2006 		break;
2007 		case _kUiField_type::LIST_:
2008 		{
2009 			if (arg -> which != Stackel_STRING)
2010 				Melder_throw (U"List argument \"", my name.get(), U"\" should be a string, not ", arg -> whichText(), U".");
2011 			integer i = 1;
2012 			for (; i <= my strings.size; i ++)
2013 				if (str32equ (arg -> getString(), my strings [i]))
2014 					break;
2015 			if (i > my strings.size)
2016 				Melder_throw (U"List argument \"", my name.get(), U"\" cannot have the value \"", arg -> getString(), U"\".");
2017 			my integerValue = i;
2018 			if (my integerVariable)
2019 				*my integerVariable = my integerValue;
2020 			if (my stringVariable)
2021 				*my stringVariable = (char32 *) my strings [my integerValue];
2022 		}
2023 		break;
2024 		case _kUiField_type::COLOUR_:
2025 		{
2026 			if (arg -> which == Stackel_NUMBER) {
2027 				if (arg -> number < 0.0 || arg -> number > 1.0)
2028 					Melder_throw (U"Grey colour argument \"", my name.get(), U"\" has to lie between 0.0 and 1.0.");
2029 				my colourValue. red = my colourValue. green = my colourValue. blue = arg -> number;
2030 			} else if (arg -> which == Stackel_STRING) {
2031 				autostring32 string2 = Melder_dup (arg -> getString());
2032 				MelderColour colour = MelderColour_fromColourNameOrRGBString (string2.get());
2033 				if (colour.valid())
2034 					my colourValue = colour;
2035 				else
2036 					Melder_throw (U"Cannot compute a colour from \"", string2.get(), U"\".");
2037 			} else if (arg -> which == Stackel_NUMERIC_VECTOR) {
2038 				my colourValue = MelderColour (arg -> numericVector);
2039 			} else
2040 				Melder_throw (U"Colour argument \"", my name.get(), U"\" should be a string or number or numeric vector, not ", arg -> whichText(), U".");
2041 			if (my colourVariable)
2042 				*my colourVariable = my colourValue;
2043 		}
2044 		break;
2045 		default:
2046 		{
2047 			Melder_throw (U"Unknown field type ", (int) my type, U".");
2048 		}
2049 	}
2050 }
2051 
UiForm_call(UiForm me,integer narg,Stackel args,Interpreter interpreter)2052 void UiForm_call (UiForm me, integer narg, Stackel args, Interpreter interpreter) {
2053 	integer size = my numberOfFields, iarg = 0;
2054 	//while (size >= 1 && my field [size] -> type == _kUiField_type::LABEL_)
2055 	//	size --;   // ignore trailing fields without a value
2056 	for (integer i = 1; i <= size; i ++) {
2057 		if (my field [i] -> type == _kUiField_type::LABEL_)
2058 			continue;   // ignore non-trailing fields without a value
2059 		iarg ++;
2060 		if (iarg > narg)
2061 			Melder_throw (U"Command requires more than the given ", narg, U" arguments: argument \"", my field [i] -> name.get(), U"\" not given.");
2062 		UiField_argToValue (my field [i].get(), & args [iarg], interpreter);
2063 	}
2064 	if (iarg < narg)
2065 		Melder_throw (U"Command requires only ", iarg, U" arguments, not the ", narg, U" given.");
2066 	my okCallback (me, 0, nullptr, nullptr, interpreter, nullptr, false, my buttonClosure);
2067 }
2068 
2069 /*
2070 	DEPRECATED_2014 (i.e. remove in 2036)
2071 */
UiField_stringToValue(UiField me,conststring32 string,Interpreter interpreter)2072 static void UiField_stringToValue (UiField me, conststring32 string, Interpreter interpreter) {
2073 	/*
2074 		This belongs to the deprecated dots-based syntax described below at `UiForm_parseString`.
2075 		This is included for backward compatibility (until 2036),
2076 		but does not support newer expression types such as numeric vectors and matrices.
2077 	*/
2078 	switch (my type)
2079 	{
2080 		case _kUiField_type::REAL_:
2081 		case _kUiField_type::REAL_OR_UNDEFINED_:
2082 		case _kUiField_type::POSITIVE_:
2083 		{
2084 			if (str32spn (string, U" \t") == str32len (string))
2085 				Melder_throw (U"Argument “", my name.get(), U"” empty.");
2086 			Interpreter_numericExpression (interpreter, string, & my realValue);
2087 			if (isundef (my realValue) && my type != _kUiField_type::REAL_OR_UNDEFINED_)
2088 				Melder_throw (U"\"", my name.get(), U"\" has the value \"undefined\".");
2089 			if (my type == _kUiField_type::POSITIVE_ && my realValue <= 0.0)
2090 				Melder_throw (U"\"", my name.get(), U"\" must be greater than 0.");
2091 			if (my realVariable)
2092 				*my realVariable = my realValue;
2093 		}
2094 		break;
2095 		case _kUiField_type::INTEGER_:
2096 		case _kUiField_type::NATURAL_:
2097 		case _kUiField_type::CHANNEL_: {
2098 			if (str32spn (string, U" \t") == str32len (string))
2099 				Melder_throw (U"Argument “", my name.get(), U"” empty.");
2100 			if (my type == _kUiField_type::CHANNEL_ && (str32equ (string, U"All") || str32equ (string, U"Average"))) {
2101 				my integerValue = 0;
2102 			} else if (my type == _kUiField_type::CHANNEL_ && (str32equ (string, U"Left") || str32equ (string, U"Mono"))) {
2103 				my integerValue = 1;
2104 			} else if (my type == _kUiField_type::CHANNEL_ && (str32equ (string, U"Right") || str32equ (string, U"Stereo"))) {
2105 				my integerValue = 2;
2106 			} else {
2107 				double realValue;
2108 				Interpreter_numericExpression (interpreter, string, & realValue);
2109 				my integerValue = Melder_iround (realValue);
2110 			}
2111 			if (my type == _kUiField_type::NATURAL_ && my integerValue < 1)
2112 				Melder_throw (U"\"", my name.get(), U"\" should be a positive whole number.");
2113 			if (my integerVariable)
2114 				*my integerVariable = my integerValue;
2115 		}
2116 		break;
2117 		case _kUiField_type::WORD_:
2118 		case _kUiField_type::SENTENCE_:
2119 		case _kUiField_type::TEXT_:
2120 		case _kUiField_type::FORMULA_:
2121 		case _kUiField_type::INFILE_:
2122 		case _kUiField_type::OUTFILE_:
2123 		case _kUiField_type::FOLDER_:
2124 		{
2125 			my stringValue = Melder_dup (string);
2126 			if (my stringVariable)
2127 				*my stringVariable = my stringValue.get();   // BUG dangle
2128 		}
2129 		break;
2130 		case _kUiField_type::REALVECTOR_:
2131 		case _kUiField_type::POSITIVEVECTOR_:
2132 		{
2133 			my realVectorValue = splitByWhitespace_VEC (string);
2134 			if (my type == _kUiField_type::POSITIVEVECTOR_)
2135 				for (integer i = 1; i <= my realVectorValue.size; i ++)
2136 					if (my realVectorValue [i] <= 0.0)
2137 						Melder_throw (U"Element ", i, U" of vector “", my name.get(), U"” is ", my realVectorValue [i], U" but should be greater than 0.0.");
2138 			if (my realVectorVariable)
2139 				*my realVectorVariable = my realVectorValue.get();
2140 		}
2141 		break;
2142 		case _kUiField_type::INTEGERVECTOR_:
2143 		case _kUiField_type::NATURALVECTOR_:
2144 		{
2145 			my integerVectorValue = splitByWhitespaceWithRanges_INTVEC (string);
2146 			if (my type == _kUiField_type::NATURALVECTOR_)
2147 				for (integer i = 1; i <= my integerVectorValue.size; i ++)
2148 					if (my integerVectorValue [i] <= 0)
2149 						Melder_throw (U"Element ", i, U" of vector “", my name.get(), U"” is ", my integerVectorValue [i], U" but should be greater than 0.");
2150 			if (my integerVectorVariable)
2151 				*my integerVectorVariable = my integerVectorValue.get();
2152 		}
2153 		break;
2154 		case _kUiField_type::STRINGARRAY_:
2155 			my stringArrayValue = splitByWhitespace_STRVEC (string);
2156 			if (my stringArrayVariable)
2157 				*my stringArrayVariable = my stringArrayValue.get();
2158 		break;
2159 		case _kUiField_type::BOOLEAN_:
2160 		{
2161 			if (! string [0])
2162 				Melder_throw (U"Empty argument for toggle button.");
2163 			my integerValue = string [0] == U'1' || string [0] == U'y' || string [0] == U'Y' ||
2164 				string [0] == U't' || string [0] == U'T';
2165 			if (my boolVariable)
2166 				*my boolVariable = my integerValue;
2167 		}
2168 		break;
2169 		case _kUiField_type::RADIO_:
2170 		case _kUiField_type::OPTIONMENU_:
2171 		{
2172 			my integerValue = 0;
2173 			for (int i = 1; i <= my options.size; i ++) {
2174 				UiOption b = my options.at [i];
2175 				if (str32equ (string, b -> name.get()))
2176 					my integerValue = i;
2177 			}
2178 			if (my integerValue == 0) {
2179 				/*
2180 					Retry with different case.
2181 				*/
2182 				for (int i = 1; i <= my options.size; i ++) {
2183 					UiOption b = my options.at [i];
2184 					if (Melder_equ_firstCharacterCaseInsensitive (string, b -> name.get()))
2185 						my integerValue = i;
2186 				}
2187 			}
2188 			if (my integerValue == 0) {
2189 				Melder_throw (U"Field \"", my name.get(), U"\" must not have the value \"", string, U"\".");
2190 			}
2191 			if (my intVariable)
2192 				*my intVariable = int (my integerValue) - my subtract;
2193 			if (my stringVariable)
2194 				*my stringVariable = my options.at [my integerValue] -> name.get();
2195 		}
2196 		break;
2197 		case _kUiField_type::LIST_:
2198 		{
2199 			integer i = 1;
2200 			for (; i <= my strings.size; i ++)
2201 				if (str32equ (string, my strings [i]))
2202 					break;
2203 			if (i > my strings.size)
2204 				Melder_throw (U"Field \"", my name.get(), U"\" must not have the value \"", string, U"\".");
2205 			my integerValue = i;
2206 			if (my integerVariable)
2207 				*my integerVariable = my integerValue;
2208 			if (my stringVariable)
2209 				*my stringVariable = (char32 *) my strings [my integerValue];
2210 		}
2211 		break;
2212 		case _kUiField_type::COLOUR_:
2213 		{
2214 			autostring32 string2 = Melder_dup (string);
2215 			MelderColour colour = MelderColour_fromColourNameOrRGBString (string2.get());
2216 			if (colour.valid()) {
2217 				my colourValue = colour;
2218 			} else {
2219 				try {
2220 					double greyValue;
2221 					Interpreter_numericExpression (interpreter, string2.get(), & greyValue);
2222 					my colourValue = MelderColour (greyValue);
2223 				} catch (MelderError) {
2224 					Melder_clearError ();
2225 					Melder_throw (U"Cannot compute a colour from \"", string2.get(), U"\".");
2226 				}
2227 			}
2228 			if (my colourVariable)
2229 				*my colourVariable = my colourValue;
2230 		}
2231 		break;
2232 		default:
2233 		{
2234 			Melder_throw (U"Unknown field type ", (int) my type, U".");
2235 		}
2236 	}
2237 }
2238 
2239 /*
2240 	DEPRECATED_2014 (i.e. remove in 2036)
2241 */
UiForm_parseString(UiForm me,conststring32 arguments,Interpreter interpreter)2242 void UiForm_parseString (UiForm me, conststring32 arguments, Interpreter interpreter) {
2243 	/*
2244 		This implements the dots-based scripting style
2245 			Create Sound from formula... sineWithNoise 1 0 1 44100 0.5 * sin (2*pi*377*x)
2246 		This was deprecated with the advent of the colon-based scripting style
2247 			Create Sound from formula: "sineWithNoise", 1, 0, 1, 44100, "0.5 * sin (2*pi*377*x)"
2248 		or
2249 			Create Sound from formula: "sineWithNoise", 1, 0, 1, 44100, ~ 0.5 * sin (2*pi*377*x)
2250 		in 2014, i.e. 22 years after Praat started.
2251 		If we want to conservatively support old scripts, we will have
2252 		to continue to support the dots-based scripting style until 2036.
2253 	*/
2254 	int size = my numberOfFields;
2255 	while (size >= 1 && my field [size] -> type == _kUiField_type::LABEL_)
2256 		size --;   // ignore trailing fields without a value
2257 	for (int i = 1; i < size; i ++) {
2258 		static char32 stringValue [3000];
2259 		int ichar = 0;
2260 		if (my field [i] -> type == _kUiField_type::LABEL_)
2261 			continue;   // ignore non-trailing fields without a value
2262 		Melder_skipHorizontalOrVerticalSpace (& arguments);   // go to next argument
2263 		/*
2264 			The argument is everything up to the next space, or, if that starts with a double quote,
2265 			everything between this quote and the matching double quote;
2266 			in this case, the argument can represent a double quote by a sequence of two double quotes.
2267 			Example: the string
2268 				"I said ""hello"""
2269 			will be passed to the dialog as a single argument containing the text
2270 				I said "hello"
2271 		*/
2272 		if (*arguments == U'\"') {
2273 			arguments ++;   // do not include leading double quote
2274 			for (;;) {
2275 				if (*arguments == U'\0')
2276 					Melder_throw (U"Missing matching quote.");
2277 				if (*arguments == U'\"' && * ++ arguments != U'\"')
2278 					break;   // remember second quote
2279 				stringValue [ichar ++] = *arguments ++;
2280 			}
2281 		} else {
2282 			while (*arguments != U' ' && *arguments != U'\t' && *arguments != U'\0')
2283 				stringValue [ichar ++] = *arguments ++;
2284 		}
2285 		stringValue [ichar] = U'\0';   // trailing null character
2286 		try {
2287 			UiField_stringToValue (my field [i].get(), stringValue, interpreter);
2288 		} catch (MelderError) {
2289 			Melder_throw (U"Don't understand contents of field \"", my field [i] -> name.get(), U"\".");
2290 		}
2291 	}
2292 	/*
2293 		The last item is handled separately, because it consists of the rest of the line.
2294 		Leading spaces are skipped, but trailing spaces are included.
2295 	*/
2296 	if (size > 0) {
2297 		Melder_skipHorizontalOrVerticalSpace (& arguments);   // rid leading spaces
2298 		try {
2299 			UiField_stringToValue (my field [size].get(), arguments, interpreter);
2300 		} catch (MelderError) {
2301 			Melder_throw (U"Don't understand contents of field \"", my field [size] -> name.get(), U"\".");
2302 		}
2303 	}
2304 	my okCallback (me, 0, nullptr, nullptr, interpreter, nullptr, false, my buttonClosure);
2305 }
2306 
UiForm_parseStringE(EditorCommand cmd,integer narg,Stackel args,conststring32 arguments,Interpreter interpreter)2307 void UiForm_parseStringE (EditorCommand cmd, integer narg, Stackel args, conststring32 arguments, Interpreter interpreter) {
2308 	if (args)
2309 		UiForm_call (cmd -> d_uiform.get(), narg, args, interpreter);
2310 	else
2311 		UiForm_parseString (cmd -> d_uiform.get(), arguments, interpreter);
2312 }
2313 
fatalField(UiForm dia)2314 static void fatalField (UiForm dia) {
2315 	Melder_fatal (U"Wrong field in command window \"", dia -> name.get(), U"\".");
2316 }
2317 
UiForm_setReal(UiForm me,double * p_variable,double value)2318 void UiForm_setReal (UiForm me, double *p_variable, double value) {
2319 	for (int ifield = 1; ifield <= my numberOfFields; ifield ++) {
2320 		UiField field = my field [ifield].get();
2321 		if (field -> realVariable == p_variable) {
2322 			switch (field -> type)
2323 			{
2324 				case _kUiField_type::REAL_:
2325 				case _kUiField_type::REAL_OR_UNDEFINED_:
2326 				case _kUiField_type::POSITIVE_:
2327 				{
2328 					if (value == Melder_atof (field -> stringDefaultValue.get())) {
2329 						GuiText_setString (field -> text, field -> stringDefaultValue.get());
2330 					} else {
2331 						char32 s [40];
2332 						str32cpy (s, Melder_double (value));
2333 						/*
2334 							If the default is overtly real, the shown value should be as well.
2335 						*/
2336 						if ((str32chr (field -> stringDefaultValue.get(), U'.') || str32chr (field -> stringDefaultValue.get(), U'e')) &&
2337 							! (str32chr (s, U'.') || str32chr (s, U'e')))
2338 						{
2339 							str32cpy (s + str32len (s), U".0");
2340 						}
2341 						GuiText_setString (field -> text, s);
2342 					}
2343 				}
2344 				break;
2345 				default:
2346 				{
2347 					fatalField (me);
2348 				}
2349 			}
2350 			return;
2351 		}
2352 	}
2353 	Melder_fatal (U"Real field not found in command window \"", my name.get(), U"\".");
2354 }
2355 
UiForm_setRealAsString(UiForm me,double * p_variable,conststring32 stringValue)2356 void UiForm_setRealAsString (UiForm me, double *p_variable, conststring32 stringValue) {
2357 	for (int ifield = 1; ifield <= my numberOfFields; ifield ++) {
2358 		UiField field = my field [ifield].get();
2359 		if (field -> realVariable == p_variable) {
2360 			switch (field -> type)
2361 			{
2362 				case _kUiField_type::REAL_:
2363 				case _kUiField_type::REAL_OR_UNDEFINED_:
2364 				case _kUiField_type::POSITIVE_:
2365 				{
2366 					GuiText_setString (field -> text, stringValue);
2367 				}
2368 				break;
2369 				default:
2370 				{
2371 					fatalField (me);
2372 				}
2373 			}
2374 			return;
2375 		}
2376 	}
2377 	Melder_fatal (U"Real field not found in command window \"", my name.get(), U"\".");
2378 }
2379 
UiForm_setInteger(UiForm me,integer * p_variable,integer value)2380 void UiForm_setInteger (UiForm me, integer *p_variable, integer value) {
2381 	for (int ifield = 1; ifield <= my numberOfFields; ifield ++) {
2382 		UiField field = my field [ifield].get();
2383 		if (field -> integerVariable == p_variable) {
2384 			switch (field -> type)
2385 			{
2386 				case _kUiField_type::INTEGER_:
2387 				case _kUiField_type::NATURAL_:
2388 				case _kUiField_type::CHANNEL_:
2389 				{
2390 					if (value == Melder_atoi (field -> stringDefaultValue.get())) {
2391 						GuiText_setString (field -> text, field -> stringDefaultValue.get());
2392 					} else {
2393 						GuiText_setString (field -> text, Melder_integer (value));
2394 					}
2395 				}
2396 				break;
2397 				case _kUiField_type::LIST_:
2398 				{
2399 					if (value < 1 || value > field -> strings.size)
2400 						value = 1;   // guard against incorrect prefs file
2401 					GuiList_selectItem (field -> list, value);
2402 				}
2403 				break;
2404 				default:
2405 				{
2406 					fatalField (me);
2407 				}
2408 			}
2409 			return;
2410 		}
2411 	}
2412 	Melder_fatal (U"Integer field not found in command window \"", my name.get(), U"\".");
2413 }
2414 
UiForm_setIntegerAsString(UiForm me,integer * p_variable,conststring32 stringValue)2415 void UiForm_setIntegerAsString (UiForm me, integer *p_variable, conststring32 stringValue /* cattable */) {
2416 	for (int ifield = 1; ifield <= my numberOfFields; ifield ++) {
2417 		UiField field = my field [ifield].get();
2418 		if (field -> integerVariable == p_variable) {
2419 			switch (field -> type)
2420 			{
2421 				case _kUiField_type::INTEGER_:
2422 				case _kUiField_type::NATURAL_:
2423 				case _kUiField_type::CHANNEL_:
2424 				{
2425 					GuiText_setString (field -> text, stringValue);
2426 				}
2427 				break;
2428 				case _kUiField_type::LIST_:
2429 				{
2430 					integer i = 1;
2431 					for (; i <= field -> strings.size; i ++)
2432 						if (str32equ (stringValue, field -> strings [i])) break;
2433 					if (i > field -> strings.size)
2434 						i = 1;   // guard against incorrect prefs file
2435 					GuiList_selectItem (field -> list, i);
2436 				}
2437 				break;
2438 				default:
2439 				{
2440 					fatalField (me);
2441 				}
2442 			}
2443 			return;
2444 		}
2445 	}
2446 	Melder_fatal (U"Integer field not found in command window \"", my name.get(), U"\".");
2447 }
2448 
UiForm_setBoolean(UiForm me,bool * p_variable,bool value)2449 void UiForm_setBoolean (UiForm me, bool *p_variable, bool value) {
2450 	for (int ifield = 1; ifield <= my numberOfFields; ifield ++) {
2451 		UiField field = my field [ifield].get();
2452 		if (field -> boolVariable == p_variable) {
2453 			switch (field -> type)
2454 			{
2455 				case _kUiField_type::BOOLEAN_:
2456 				{
2457 					GuiCheckButton_setValue (field -> checkButton, value);
2458 				}
2459 				break;
2460 				default:
2461 				{
2462 					fatalField (me);
2463 				}
2464 			}
2465 			return;
2466 		}
2467 	}
2468 	Melder_fatal (U"Boolean field not found in command window \"", my name.get(), U"\".");
2469 }
2470 
UiForm_setOption(UiForm me,int * p_variable,int value)2471 void UiForm_setOption (UiForm me, int *p_variable, int value) {
2472 	for (int ifield = 1; ifield <= my numberOfFields; ifield ++) {
2473 		UiField field = my field [ifield].get();
2474 		if (field -> intVariable == p_variable) {
2475 			switch (field -> type)
2476 			{
2477 				case _kUiField_type::RADIO_:
2478 				{
2479 					if (value < 1 || value > field -> options.size)
2480 						value = 1;   // guard against incorrect prefs file
2481 					UiOption option = field -> options.at [value];
2482 					GuiRadioButton_set (option -> radioButton);
2483 				}
2484 				break;
2485 				case _kUiField_type::OPTIONMENU_:
2486 				{
2487 					if (value < 1 || value > field -> options.size)
2488 						value = 1;   // guard against incorrect prefs file
2489 					GuiOptionMenu_setValue (field -> optionMenu, value);
2490 				}
2491 				break;
2492 				default:
2493 				{
2494 					fatalField (me);
2495 				}
2496 			}
2497 			return;
2498 		}
2499 	}
2500 	Melder_fatal (U"Option field not found in command window \"", my name.get(), U"\".");
2501 }
2502 
UiForm_setOptionAsString(UiForm me,int * p_variable,conststring32 stringValue)2503 void UiForm_setOptionAsString (UiForm me, int *p_variable, conststring32 stringValue) {
2504 	for (int ifield = 1; ifield <= my numberOfFields; ifield ++) {
2505 		UiField field = my field [ifield].get();
2506 		if (field -> intVariable == p_variable) {
2507 			switch (field -> type)
2508 			{
2509 				case _kUiField_type::RADIO_:
2510 				{
2511 					for (int i = 1; i <= field -> options.size; i ++) {
2512 						UiOption b = field -> options.at [i];
2513 						if (str32equ (stringValue, b -> name.get())) {
2514 							GuiRadioButton_set (b -> radioButton);
2515 						}
2516 					}
2517 					/* If not found: do nothing (guard against incorrect prefs file). */
2518 				}
2519 				break;
2520 				case _kUiField_type::OPTIONMENU_:
2521 				{
2522 					int optionValue = 0;
2523 					for (int i = 1; i <= field -> options.size; i ++) {
2524 						UiOption b = field -> options.at [i];
2525 						if (str32equ (stringValue, b -> name.get())) {
2526 							optionValue = i;
2527 							break;
2528 						}
2529 					}
2530 					GuiOptionMenu_setValue (field -> optionMenu, optionValue);
2531 					/* If not found: do nothing (guard against incorrect prefs file). */
2532 				}
2533 				break;
2534 				default:
2535 				{
2536 					fatalField (me);
2537 				}
2538 			}
2539 			return;
2540 		}
2541 	}
2542 	Melder_fatal (U"Option field not found in command window \"", my name.get(), U"\".");
2543 }
2544 
UiForm_setString(UiForm me,conststring32 * p_variable,conststring32 value)2545 void UiForm_setString (UiForm me, conststring32 *p_variable, conststring32 value /* cattable */) {
2546 	if (! value) value = U"";   // accept null strings
2547 	for (int ifield = 1; ifield <= my numberOfFields; ifield ++) {
2548 		UiField field = my field [ifield].get();
2549 		if (field -> stringVariable == p_variable) {
2550 			switch (field -> type)
2551 			{
2552 				case _kUiField_type::WORD_:
2553 				case _kUiField_type::SENTENCE_:
2554 				case _kUiField_type::COLOUR_:
2555 				case _kUiField_type::TEXT_:
2556 				case _kUiField_type::FORMULA_:
2557 				case _kUiField_type::INFILE_:
2558 				case _kUiField_type::OUTFILE_:
2559 				case _kUiField_type::FOLDER_:
2560 				{
2561 					GuiText_setString (field -> text, value);
2562 				}
2563 				break;
2564 				case _kUiField_type::LABEL_:
2565 				{
2566 					GuiLabel_setText (field -> label, value);
2567 				}
2568 				break;
2569 				default:
2570 				{
2571 					fatalField (me);
2572 				}
2573 			}
2574 			return;
2575 		}
2576 	}
2577 	Melder_fatal (U"Text field not found in command window \"", my name.get(), U"\".");
2578 }
2579 
UiForm_setColourAsGreyValue(UiForm me,MelderColour * p_variable,double greyValue)2580 void UiForm_setColourAsGreyValue (UiForm me, MelderColour *p_variable, double greyValue) {
2581 	for (int ifield = 1; ifield <= my numberOfFields; ifield ++) {
2582 		UiField field = my field [ifield].get();
2583 		if (field -> colourVariable == p_variable) {
2584 			switch (field -> type)
2585 			{
2586 				case _kUiField_type::COLOUR_:
2587 				{
2588 					GuiText_setString (field -> text, Melder_double (greyValue));
2589 				}
2590 				break;
2591 				default:
2592 				{
2593 					fatalField (me);
2594 				}
2595 			}
2596 			return;
2597 		}
2598 	}
2599 	Melder_fatal (U"Colour field not found in command window \"", my name.get(), U"\".");
2600 }
2601 
findField(UiForm me,conststring32 fieldName)2602 static UiField findField (UiForm me, conststring32 fieldName) {
2603 	for (int ifield = 1; ifield <= my numberOfFields; ifield ++)
2604 		if (str32equ (fieldName, my field [ifield] -> name.get()))
2605 			return my field [ifield].get();
2606 	return nullptr;
2607 }
2608 
findField_check(UiForm me,conststring32 fieldName)2609 static UiField findField_check (UiForm me, conststring32 fieldName) {
2610 	UiField result = findField (me, fieldName);
2611 	if (! result)
2612 		Melder_throw (U"Cannot find field \"", fieldName, U"\" in form.\n"
2613 			U"The script may have changed while the form was open.\n"
2614 			U"Please click Cancel in the form and try again.");
2615 	return result;
2616 }
2617 
UiForm_getReal_check(UiForm me,conststring32 fieldName)2618 double UiForm_getReal_check (UiForm me, conststring32 fieldName) {
2619 	UiField field = findField_check (me, fieldName);
2620 	switch (field -> type)
2621 	{
2622 		case _kUiField_type::REAL_:
2623 		case _kUiField_type::REAL_OR_UNDEFINED_:
2624 		case _kUiField_type::POSITIVE_:
2625 		{
2626 			return field -> realValue;
2627 		}
2628 		break;
2629 		default:
2630 		{
2631 			Melder_throw (U"Cannot find a real value in field \"", fieldName, U"\" in the form.\n"
2632 				U"The script may have changed while the form was open.\n"
2633 				U"Please click Cancel in the form and try again.");
2634 		}
2635 	}
2636 	return 0.0;
2637 }
2638 
UiForm_getInteger(UiForm me,conststring32 fieldName)2639 integer UiForm_getInteger (UiForm me, conststring32 fieldName) {
2640 	UiField field = findField (me, fieldName);
2641 	if (! field)
2642 		Melder_fatal (U"(UiForm_getInteger:) No field \"", fieldName, U"\" in command window \"", my name.get(), U"\".");
2643 	switch (field -> type)
2644 	{
2645 		case _kUiField_type::INTEGER_:
2646 		case _kUiField_type::NATURAL_:
2647 		case _kUiField_type::CHANNEL_:
2648 		case _kUiField_type::BOOLEAN_:
2649 		case _kUiField_type::RADIO_:
2650 		case _kUiField_type::OPTIONMENU_:
2651 		case _kUiField_type::LIST_:
2652 		{
2653 			return field -> integerValue;
2654 		}
2655 		break;
2656 		default:
2657 		{
2658 			fatalField (me);
2659 		}
2660 	}
2661 	return 0;
2662 }
2663 
UiForm_getInteger_check(UiForm me,conststring32 fieldName)2664 integer UiForm_getInteger_check (UiForm me, conststring32 fieldName) {
2665 	UiField field = findField_check (me, fieldName);
2666 	switch (field -> type)
2667 	{
2668 		case _kUiField_type::INTEGER_:
2669 		case _kUiField_type::NATURAL_:
2670 		case _kUiField_type::CHANNEL_:
2671 		case _kUiField_type::BOOLEAN_:
2672 		case _kUiField_type::RADIO_:
2673 		case _kUiField_type::OPTIONMENU_:
2674 		case _kUiField_type::LIST_:
2675 		{
2676 			return field -> integerValue;
2677 		}
2678 		break;
2679 		default:
2680 		{
2681 			Melder_throw (U"Cannot find an integer value in field \"", fieldName, U"\" in the form.\n"
2682 				U"The script may have changed while the form was open.\n"
2683 				U"Please click Cancel in the form and try again.");
2684 		}
2685 	}
2686 	return 0;
2687 }
2688 
UiForm_getString(UiForm me,conststring32 fieldName)2689 char32 * UiForm_getString (UiForm me, conststring32 fieldName) {
2690 	UiField field = findField (me, fieldName);
2691 	if (! field)
2692 		Melder_fatal (U"(UiForm_getString:) No field \"", fieldName, U"\" in command window \"", my name.get(), U"\".");
2693 	switch (field -> type)
2694 	{
2695 		case _kUiField_type::WORD_:
2696 		case _kUiField_type::SENTENCE_:
2697 		case _kUiField_type::TEXT_:
2698 		case _kUiField_type::FORMULA_:
2699 		case _kUiField_type::INFILE_:
2700 		case _kUiField_type::OUTFILE_:
2701 		case _kUiField_type::FOLDER_:
2702 		{
2703 			return field -> stringValue.get();   // BUG dangle
2704 		}
2705 		break;
2706 		case _kUiField_type::RADIO_:
2707 		case _kUiField_type::OPTIONMENU_:
2708 		{
2709 			UiOption b = field -> options.at [field -> integerValue];
2710 			return b -> name.get();
2711 		}
2712 		break;
2713 		case _kUiField_type::LIST_:
2714 		{
2715 			return (char32 *) field -> strings [field -> integerValue];
2716 		}
2717 		break;
2718 		default:
2719 		{
2720 			fatalField (me);
2721 		}
2722 	}
2723 	return nullptr;
2724 }
2725 
UiForm_getString_check(UiForm me,conststring32 fieldName)2726 char32 * UiForm_getString_check (UiForm me, conststring32 fieldName) {
2727 	UiField field = findField_check (me, fieldName);
2728 	switch (field -> type)
2729 	{
2730 		case _kUiField_type::WORD_:
2731 		case _kUiField_type::SENTENCE_:
2732 		case _kUiField_type::TEXT_:
2733 		case _kUiField_type::INFILE_:
2734 		case _kUiField_type::OUTFILE_:
2735 		case _kUiField_type::FOLDER_:
2736 		{
2737 			return field -> stringValue.get();
2738 		}
2739 		break;
2740 		case _kUiField_type::RADIO_:
2741 		case _kUiField_type::OPTIONMENU_:
2742 		{
2743 			UiOption b = field -> options.at [field -> integerValue];
2744 			return b -> name.get();
2745 		}
2746 		break;
2747 		case _kUiField_type::LIST_:
2748 		{
2749 			return (char32 *) field -> strings [field -> integerValue];
2750 		}
2751 		break;
2752 		default:
2753 		{
2754 			Melder_throw (U"Cannot find a string in field \"", fieldName, U"\" in the form.\n"
2755 				U"The script may have changed while the form was open.\n"
2756 				U"Please click Cancel in the form and try again.");
2757 		}
2758 	}
2759 	return nullptr;
2760 }
2761 
UiForm_getRealVector(UiForm me,conststring32 fieldName)2762 VEC UiForm_getRealVector (UiForm me, conststring32 fieldName) {
2763 	UiField field = findField (me, fieldName);
2764 	if (! field)
2765 		Melder_fatal (U"(UiForm_getRealVector:) No field \"", fieldName, U"\" in command window \"", my name.get(), U"\".");
2766 	switch (field -> type)
2767 	{
2768 		case _kUiField_type::REALVECTOR_:
2769 		case _kUiField_type::POSITIVEVECTOR_:
2770 		{
2771 			return field -> realVectorValue.get();
2772 		}
2773 		break;
2774 		default:
2775 		{
2776 			fatalField (me);
2777 		}
2778 	}
2779 	return VEC();
2780 }
2781 
UiForm_getIntegerVector(UiForm me,conststring32 fieldName)2782 INTVEC UiForm_getIntegerVector (UiForm me, conststring32 fieldName) {
2783 	UiField field = findField (me, fieldName);
2784 	if (! field)
2785 		Melder_fatal (U"(UiForm_getIntegerVector:) No field \"", fieldName, U"\" in command window \"", my name.get(), U"\".");
2786 	switch (field -> type)
2787 	{
2788 		case _kUiField_type::INTEGERVECTOR_:
2789 		case _kUiField_type::NATURALVECTOR_:
2790 		{
2791 			return field -> integerVectorValue.get();
2792 		}
2793 		break;
2794 		default:
2795 		{
2796 			fatalField (me);
2797 		}
2798 	}
2799 	return INTVEC();
2800 }
2801 
UiForm_getColour_check(UiForm me,conststring32 fieldName)2802 MelderColour UiForm_getColour_check (UiForm me, conststring32 fieldName) {
2803 	UiField field = findField_check (me, fieldName);
2804 	switch (field -> type)
2805 	{
2806 		case _kUiField_type::COLOUR_: {
2807 			return field -> colourValue;
2808 		}
2809 		break;
2810 		default:
2811 		{
2812 			Melder_throw (U"Cannot find a colour value in field \"", fieldName, U"\" in the form.\n"
2813 				U"The script may have changed while the form was open.\n"
2814 				U"Please click Cancel in the form and try again.");
2815 		}
2816 	}
2817 	return Melder_BLACK;
2818 }
2819 
UiForm_Interpreter_addVariables(UiForm me,Interpreter interpreter)2820 void UiForm_Interpreter_addVariables (UiForm me, Interpreter interpreter) {
2821 	static MelderString lowerCaseFieldName;
2822 	for (int ifield = 1; ifield <= my numberOfFields; ifield ++) {
2823 		UiField field = my field [ifield].get();
2824 		MelderString_copy (& lowerCaseFieldName, field -> name.get());
2825 		/*
2826 			Change e.g. "Number of people" to "number_of_people".
2827 		*/
2828 		lowerCaseFieldName.string [0] = Melder_toLowerCase (lowerCaseFieldName.string [0]);
2829 		for (char32 *p = & lowerCaseFieldName.string [0]; *p != U'\0'; p ++) {
2830 			if (*p == U' ')
2831 				*p = U'_';
2832 		}
2833 		switch (field -> type)
2834 		{
2835 			case _kUiField_type::INTEGER_:
2836 			case _kUiField_type::NATURAL_:
2837 			case _kUiField_type::CHANNEL_:
2838 			case _kUiField_type::BOOLEAN_:
2839 			{
2840 				InterpreterVariable var = Interpreter_lookUpVariable (interpreter, lowerCaseFieldName.string);
2841 				var -> numericValue = field -> integerValue;
2842 			}
2843 			break;
2844 			case _kUiField_type::REAL_:
2845 			case _kUiField_type::REAL_OR_UNDEFINED_:
2846 			case _kUiField_type::POSITIVE_:
2847 			{
2848 				InterpreterVariable var = Interpreter_lookUpVariable (interpreter, lowerCaseFieldName.string);
2849 				var -> numericValue = field -> realValue;
2850 			}
2851 			break;
2852 			case _kUiField_type::RADIO_:
2853 			case _kUiField_type::OPTIONMENU_:
2854 			{
2855 				InterpreterVariable var = Interpreter_lookUpVariable (interpreter, lowerCaseFieldName.string);
2856 				var -> numericValue = field -> integerValue;
2857 				MelderString_appendCharacter (& lowerCaseFieldName, U'$');
2858 				var = Interpreter_lookUpVariable (interpreter, lowerCaseFieldName.string);
2859 				UiOption b = field -> options.at [field -> integerValue];
2860 				var -> stringValue = Melder_dup (b -> name.get());
2861 			}
2862 			break;
2863 			case _kUiField_type::LIST_:
2864 			{
2865 				InterpreterVariable var = Interpreter_lookUpVariable (interpreter, lowerCaseFieldName.string);
2866 				var -> numericValue = field -> integerValue;
2867 				MelderString_appendCharacter (& lowerCaseFieldName, U'$');
2868 				var = Interpreter_lookUpVariable (interpreter, lowerCaseFieldName.string);
2869 				var -> stringValue = Melder_dup (field -> strings [field -> integerValue]);
2870 			}
2871 			break;
2872 			case _kUiField_type::WORD_:
2873 			case _kUiField_type::SENTENCE_:
2874 			case _kUiField_type::TEXT_:
2875 			case _kUiField_type::FORMULA_:
2876 			case _kUiField_type::INFILE_:
2877 			case _kUiField_type::OUTFILE_:
2878 			case _kUiField_type::FOLDER_:
2879 			{
2880 				MelderString_appendCharacter (& lowerCaseFieldName, U'$');
2881 				InterpreterVariable var = Interpreter_lookUpVariable (interpreter, lowerCaseFieldName.string);
2882 				var -> stringValue = Melder_dup (field -> stringValue.get());
2883 			}
2884 			break;
2885 			case _kUiField_type::REALVECTOR_:
2886 			case _kUiField_type::POSITIVEVECTOR_:
2887 			{
2888 				MelderString_appendCharacter (& lowerCaseFieldName, U'#');
2889 				InterpreterVariable var = Interpreter_lookUpVariable (interpreter, lowerCaseFieldName.string);
2890 				var -> numericVectorValue = copy_VEC (field -> realVectorValue.get());   // TODO: can we move this instead of copying it?
2891 			}
2892 			break;
2893 			case _kUiField_type::INTEGERVECTOR_:
2894 			case _kUiField_type::NATURALVECTOR_:
2895 			{
2896 				MelderString_appendCharacter (& lowerCaseFieldName, U'#');
2897 				InterpreterVariable var = Interpreter_lookUpVariable (interpreter, lowerCaseFieldName.string);
2898 				var -> numericVectorValue = cast_VEC (field -> integerVectorValue.get());
2899 			}
2900 			break;
2901 			case _kUiField_type::COLOUR_:
2902 			{
2903 				// to be implemented
2904 			}
2905 			break;
2906 			default:
2907 			{
2908 			}
2909 		}
2910 	}
2911 }
2912 
UiForm_getClickedContinueButton(UiForm me)2913 int UiForm_getClickedContinueButton (UiForm me) {
2914 	return my clickedContinueButton;
2915 }
2916 
2917 /* End of file Ui.cpp */
2918