1 /* DataEditor.cpp
2  *
3  * Copyright (C) 1995-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 #define NAME_X  30
20 #define TEXT_X  250
21 #define BUTTON_X  250
22 #define LIST_Y  (2 * Gui_TOP_DIALOG_SPACING + Gui_PUSHBUTTON_HEIGHT)
23 #define EDITOR_WIDTH  820
24 #define EDITOR_HEIGHT  (Machine_getMenuBarBottom () + LIST_Y + kDataSubEditor_MAXNUM_ROWS * ROW_HEIGHT + 29)
25 #define ROW_HEIGHT  31
26 
27 #define SCROLL_BAR_WIDTH  Machine_getScrollBarWidth ()
28 
29 #include "DataEditor.h"
30 #include "EditorM.h"
31 #include "Collection.h"
32 #include "machine.h"
33 
Class_getDescription(ClassInfo table)34 static Data_Description Class_getDescription (ClassInfo table) {
35 	return ((Daata) _Thing_dummyObject (table)) -> v_description ();
36 }
37 
38 static void VectorEditor_create (DataEditor root, conststring32 title, void *address,
39 	Data_Description description, integer minimum, integer maximum);
40 
41 static void MatrixEditor_create (DataEditor root, conststring32 title, void *address,
42 	Data_Description description, integer min1, integer max1, integer min2, integer max2);
43 
44 static void StructEditor_create (DataEditor root, conststring32 title, void *address, Data_Description description);
45 
46 static void ClassEditor_create (DataEditor root, conststring32 title, void *address, Data_Description description);
47 
strip_d(conststring32 s)48 static inline conststring32 strip_d (conststring32 s) {
49 	return s && s [0] == U'd' && s [1] == U'_' ? & s [2] : & s [0];
50 }
51 
52 /********** DataSubEditor **********/
53 
54 Thing_implement (DataSubEditor, Editor, 0);
55 
v_destroy()56 void structDataSubEditor :: v_destroy () noexcept {
57 	//for (int i = 1; i <= kDataSubEditor_MAXNUM_ROWS; i ++)
58 	//	Melder_free (d_fieldData [i]. history);
59 	if (our root)
60 		for (integer i = our root -> children.size; i > 0; i --)
61 			if (our root -> children.at [i] == this)
62 				our root -> children.subtractItem_ref (i);
63 	DataSubEditor_Parent :: v_destroy ();
64 }
65 
update(DataSubEditor me)66 static void update (DataSubEditor me) {
67 
68 	/* Hide all the existing widgets. */
69 
70 	for (integer i = 1; i <= kDataSubEditor_MAXNUM_ROWS; i ++) {
71 		my d_fieldData [i]. address = nullptr;
72 		my d_fieldData [i]. description = nullptr;
73 		GuiThing_hide (my d_fieldData [i]. label);
74 		GuiThing_hide (my d_fieldData [i]. button);
75 		GuiThing_hide (my d_fieldData [i]. text);
76 	}
77 
78 	my d_irow = 0;
79 	my v_showMembers ();
80 }
81 
DataSubEditor_findNumberUse(DataSubEditor me,conststring32 number)82 static Data_Description DataSubEditor_findNumberUse (DataSubEditor me, conststring32 number) {
83 	Data_Description structDescription, result;
84 	char32 string [100];
85 	if (my classInfo == classMatrixEditor) return nullptr;   // no structs inside
86 	if (my classInfo == classVectorEditor) {
87 		if (my d_description -> type != structwa) return nullptr;   // no structs inside
88 		structDescription = * (Data_Description *) my d_description -> tagType;
89 	} else { /* StructEditor or ClassEditor or DataEditor. */
90 		structDescription = my d_description;
91 	}
92 	Melder_sprint (string,100, number);
93 	if ((result = Data_Description_findNumberUse (structDescription, string)) != nullptr) return result;
94 	Melder_sprint (string,100, number, U" - 1");
95 	if ((result = Data_Description_findNumberUse (structDescription, string)) != nullptr) return result;
96 	return nullptr;
97 }
98 
gui_button_cb_change(DataSubEditor me,GuiButtonEvent)99 static void gui_button_cb_change (DataSubEditor me, GuiButtonEvent /* event */) {
100 	int irow = 1;
101 	for (; irow <= kDataSubEditor_MAXNUM_ROWS; irow ++) {
102 		#if motif
103 			const bool visible = XtIsManaged (my d_fieldData [irow]. text -> d_widget);
104 		#elif gtk
105 			gboolean visible;
106 			g_object_get (G_OBJECT (my d_fieldData [irow]. text -> d_widget), "visible", & visible, nullptr);
107 		#elif defined (macintosh)
108 			const bool visible = ! [(GuiCocoaTextField *) my d_fieldData [irow]. text -> d_widget   isHidden];
109 		#else
110 			const bool visible = false;
111 		#endif
112 		if (visible) {
113 			int type = my d_fieldData [irow]. description -> type;
114 			if (type > maxsingletypewa)
115 				continue;
116 			autostring32 text = GuiText_getString (my d_fieldData [irow]. text);
117 			switch (type) {
118 				case bytewa: {
119 					signed char oldValue = * (signed char *) my d_fieldData [irow]. address, newValue = (signed char) Melder_atoi (text.get());
120 					if (newValue != oldValue) {
121 						Data_Description numberUse = DataSubEditor_findNumberUse (me, my d_fieldData [irow]. description -> name);
122 						if (numberUse) {
123 							Melder_flushError (U"Changing field \"", strip_d (my d_fieldData [irow]. description -> name),
124 								U"\" would damage the array \"", strip_d (numberUse -> name), U"\".");
125 						} else {
126 							* (signed char *) my d_fieldData [irow]. address = newValue;
127 						}
128 					}
129 				} break;
130 				case int16wa: {
131 					int16 oldValue = * (int16 *) my d_fieldData [irow]. address;
132 					int64 newValue = Melder_atoi (text.get());
133 					if (newValue != oldValue) {
134 						Data_Description numberUse = DataSubEditor_findNumberUse (me, my d_fieldData [irow]. description -> name);
135 						if (numberUse) {
136 							Melder_flushError (U"Changing field \"", strip_d (my d_fieldData [irow]. description -> name),
137 								U"\" would damage the array \"", strip_d (numberUse -> name), U"\".");
138 						} else if (newValue < INT16_MIN || newValue > INT16_MAX) {
139 							Melder_flushError (U"Field \"", strip_d (my d_fieldData [irow]. description -> name),
140 								U"\" can have no values less than ", INT16_MIN, U" or greater than ", INT16_MAX, U".");
141 						} else {
142 							* (int16 *) my d_fieldData [irow]. address = (int16) newValue;   // guarded conversion
143 						}
144 					}
145 				} break;
146 				case intwa: {
147 					int oldValue = * (int *) my d_fieldData [irow]. address, newValue = (int) Melder_atoi (text.get());
148 					if (newValue != oldValue) {
149 						Data_Description numberUse = DataSubEditor_findNumberUse (me, my d_fieldData [irow]. description -> name);
150 						if (numberUse) {
151 							Melder_flushError (U"Changing field \"", strip_d (my d_fieldData [irow]. description -> name),
152 								U"\" would damage the array \"", strip_d (numberUse -> name), U"\".");
153 						} else {
154 							* (int *) my d_fieldData [irow]. address = newValue;
155 						}
156 					}
157 				} break;
158 				case integerwa: {
159 					integer oldValue = * (integer *) my d_fieldData [irow]. address, newValue = Melder_atoi (text.get());
160 					if (newValue != oldValue) {
161 						Data_Description numberUse = DataSubEditor_findNumberUse (me, my d_fieldData [irow]. description -> name);
162 						if (numberUse) {
163 							Melder_flushError (U"Changing field \"", strip_d (my d_fieldData [irow]. description -> name),
164 								U"\" would damage the array \"", strip_d (numberUse -> name), U"\".");
165 						} else {
166 							* (integer *) my d_fieldData [irow]. address = newValue;
167 						}
168 					}
169 				} break;
170 				case ubytewa: { * (unsigned char *) my d_fieldData [irow]. address = (uint8) Melder_atoi (text.get()); } break;
171 				case uintwa: { * (unsigned int *) my d_fieldData [irow]. address = (uint32) Melder_atoi (text.get()); } break;
172 				case uintegerwa: { * (uinteger *) my d_fieldData [irow]. address = (uinteger) Melder_atoi (text.get()); } break;
173 				case floatwa: { * (double *) my d_fieldData [irow]. address = Melder_atof (text.get()); } break;
174 				case doublewa: { * (double *) my d_fieldData [irow]. address = Melder_atof (text.get()); } break;
175 				case complexwa: { dcomplex *x = (dcomplex *) my d_fieldData [irow]. address;
176 					double re, im;
177 					sscanf (Melder_peek32to8 (text.get()), "%lf + %lf i", & re, & im);
178 					x -> real (re);
179 					x -> imag (im);
180 				} break;
181 				case enumwa: {
182 					if (str32len (text.get()) < 3) goto error;
183 					text [str32len (text.get()) - 1] = U'\0';   // remove trailing ">"
184 					int value = ((int (*) (conststring32)) (my d_fieldData [irow]. description -> tagType)) (text.get() + 1);   // skip leading "<"
185 					if (value < 0) goto error;
186 					* (signed char *) my d_fieldData [irow]. address = (signed char) value;
187 				} break;
188 				case lenumwa: {
189 					if (str32len (text.get()) < 3) goto error;
190 					text [str32len (text.get()) - 1] = U'\0';   // remove trailing ">"
191 					int value = ((int (*) (conststring32)) (my d_fieldData [irow]. description -> tagType)) (text.get() + 1);   // skip leading "<"
192 					if (value < 0) goto error;
193 					* (signed short *) my d_fieldData [irow]. address = (signed short) value;
194 				} break;
195 				case booleanwa: {
196 					bool value;
197 					if (str32nequ (text.get(), U"<true>", 6)) {
198 						value = true;
199 					} else if (str32nequ (text.get(), U"<false>", 7)) {
200 						value = false;
201 					} else {
202 						goto error;
203 					}
204 					* (bool *) my d_fieldData [irow]. address = value;
205 				} break;
206 				case questionwa: {
207 					bool value;
208 					if (str32nequ (text.get(), U"<yes>", 5)) {
209 						value = true;
210 					} else if (str32nequ (text.get(), U"<no>", 4)) {
211 						value = false;
212 					} else {
213 						goto error;
214 					}
215 					* (bool *) my d_fieldData [irow]. address = value;
216 				} break;
217 				case stringwa:
218 				case lstringwa: {
219 					char32 *old = * (char32 **) my d_fieldData [irow]. address;
220 					Melder_free (old);
221 					* (char32 **) my d_fieldData [irow]. address = Melder_dup_f (text.get()).transfer();
222 				} break;
223 				default: break;
224 			}
225 		}
226 	}
227 	/*
228 		Several collaborators have to be notified of this change:
229 		1. The owner (creator) of our root DataEditor: so that she can notify other editors, if any.
230 		2. All our sibling DataSubEditors.
231 	*/
232 	Editor_broadcastDataChanged (my root);
233 	update (me);
234 	for (int isub = 1; isub <= my root -> children.size; isub ++) {
235 		DataSubEditor subeditor = my root -> children.at [isub];
236 		if (subeditor != me) update (subeditor);
237 	}
238 	return;
239 error:
240 	Melder_flushError (U"Edit field \"", strip_d (my d_fieldData [irow]. description -> name), U"\" or click \"Cancel\".");
241 }
242 
gui_button_cb_cancel(DataSubEditor me,GuiButtonEvent)243 static void gui_button_cb_cancel (DataSubEditor me, GuiButtonEvent /* event */) {
244 	update (me);
245 }
246 
gui_cb_scroll(DataSubEditor me,GuiScrollBarEvent event)247 static void gui_cb_scroll (DataSubEditor me, GuiScrollBarEvent event) {
248 	my d_topField = GuiScrollBar_getValue (event -> scrollBar) + 1;
249 	update (me);
250 }
251 
gui_button_cb_open(DataSubEditor me,GuiButtonEvent event)252 static void gui_button_cb_open (DataSubEditor me, GuiButtonEvent event) {
253 	integer ifield = 0;
254 	static MelderString name;
255 	MelderString_empty (& name);
256 
257 	/* Identify the pressed button; it must be one of those created in the list. */
258 
259 	for (integer i = 1; i <= kDataSubEditor_MAXNUM_ROWS; i ++)
260 		if (my d_fieldData [i]. button == event -> button) {
261 			ifield = i;
262 			break;
263 		}
264 	Melder_assert (ifield != 0);
265 
266 	/* Launch the appropriate subeditor. */
267 
268 	DataSubEditor_FieldData fieldData = & my d_fieldData [ifield];
269 	if (! fieldData -> description) {
270 		Melder_casual (U"Not yet implemented.");
271 		return;   // not yet implemented
272 	}
273 
274 	if (fieldData -> description -> rank == 1 || fieldData -> description -> rank == 3 || fieldData -> description -> rank < 0) {
275 		MelderString_append (& name, fieldData -> history.get(), U". ", strip_d (fieldData -> description -> name),
276 				U" [", fieldData -> minimum, U"..", fieldData -> maximum, U"]");
277 		VectorEditor_create (my root, name.string, fieldData -> address,
278 				fieldData -> description, fieldData -> minimum, fieldData -> maximum);
279 	} else if (fieldData -> description -> rank == 2) {
280 		MelderString_append (& name, fieldData -> history.get(), U". ", strip_d (fieldData -> description -> name),
281 				U" [", fieldData -> minimum, U"..", fieldData -> maximum, U"]");
282 		MelderString_append (& name, U" [", fieldData -> min2, U"..", fieldData -> max2, U"]");
283 		MatrixEditor_create (my root, name.string, fieldData -> address, fieldData -> description,
284 				fieldData -> minimum, fieldData -> maximum, fieldData -> min2, fieldData -> max2);
285 	} else if (fieldData -> description -> type == structwa) {
286 		MelderString_append (& name, fieldData -> history.get(), U". ", strip_d (fieldData -> description -> name));
287 		StructEditor_create (my root, name.string, fieldData -> address,
288 				* (Data_Description *) fieldData -> description -> tagType);
289 	} else if (fieldData -> description -> type == objectwa ||
290 	           fieldData -> description -> type == collectionofwa ||
291 			   fieldData -> description -> type == collectionwa) {
292 		MelderString_append (& name, fieldData -> history.get(), U". ", strip_d (fieldData -> description -> name));
293 		ClassEditor_create (my root, name.string, fieldData -> address,
294 				Class_getDescription ((ClassInfo) fieldData -> description -> tagType));
295 	} else /*if (fieldData -> description -> type == inheritwa)*/ {
296 		ClassEditor_create (my root, fieldData -> history.get(), fieldData -> address,
297 				fieldData -> description);
298 /*	} else {
299 		Melder_casual (
300 			U"Strange editor \"", strip_d (fieldData -> description -> name),
301 			U"\" required (type ", fieldData -> description -> type,
302 			U", rank ", fieldData -> description -> rank,
303 			U")."
304 		);*/
305 	}
306 }
307 
v_createChildren()308 void structDataSubEditor :: v_createChildren () {
309 	int x = Gui_LEFT_DIALOG_SPACING, y = Gui_TOP_DIALOG_SPACING + Machine_getMenuBarBottom (), buttonWidth = 120;
310 
311 	GuiButton_createShown (our windowForm, x, x + buttonWidth, y, y + Gui_PUSHBUTTON_HEIGHT,
312 			U"Change", gui_button_cb_change, this, 0);
313 	x += buttonWidth + Gui_HORIZONTAL_DIALOG_SPACING;
314 	GuiButton_createShown (our windowForm, x, x + buttonWidth, y, y + Gui_PUSHBUTTON_HEIGHT,
315 			U"Cancel", gui_button_cb_cancel, this, 0);
316 
317 	y = Machine_getMenuBarBottom () + LIST_Y;
318 	d_scrollBar = GuiScrollBar_createShown (our windowForm,
319 		- SCROLL_BAR_WIDTH, 0, y, 0,
320 		0, d_numberOfFields, 0, d_numberOfFields < kDataSubEditor_MAXNUM_ROWS ? d_numberOfFields : kDataSubEditor_MAXNUM_ROWS, 1, kDataSubEditor_MAXNUM_ROWS - 1,
321 		gui_cb_scroll, this, 0
322 	);
323 
324 	y += 10;
325 	for (int i = 1; i <= kDataSubEditor_MAXNUM_ROWS; i ++) {
326 		d_fieldData [i]. label = GuiLabel_create (our windowForm, 0, 200, y, y + Gui_TEXTFIELD_HEIGHT, U"label", 0);   // no fixed x value: sometimes indent
327 		d_fieldData [i]. button = GuiButton_create (our windowForm, BUTTON_X, BUTTON_X + buttonWidth, y, y + Gui_TEXTFIELD_HEIGHT,
328 				U"Open", gui_button_cb_open, this, 0);
329 		d_fieldData [i]. text = GuiText_create (our windowForm, TEXT_X, -30, y, y + Gui_TEXTFIELD_HEIGHT, 0);
330 		d_fieldData [i]. y = y;
331 		y += ROW_HEIGHT;
332 	}
333 }
334 
menu_cb_help(DataSubEditor,EDITOR_ARGS_DIRECT)335 static void menu_cb_help (DataSubEditor, EDITOR_ARGS_DIRECT) { Melder_help (U"Inspect"); }
336 
v_createHelpMenuItems(EditorMenu menu)337 void structDataSubEditor :: v_createHelpMenuItems (EditorMenu menu) {
338 	DataSubEditor_Parent :: v_createHelpMenuItems (menu);
339 	EditorMenu_addCommand (menu, U"DataEditor help", '?', menu_cb_help);
340 }
341 
DataSubEditor_init(DataSubEditor me,DataEditor root,conststring32 title,void * address,Data_Description description)342 static void DataSubEditor_init (DataSubEditor me, DataEditor root, conststring32 title, void *address, Data_Description description) {
343 	my root = root;
344 	if (me != root)
345 		root -> children.addItem_ref (me);
346 	my d_address = address;
347 	my d_description = description;
348 	my d_topField = 1;
349 	my d_numberOfFields = my v_countFields ();
350 	Editor_init (me, 0, 0, EDITOR_WIDTH, EDITOR_HEIGHT, title, nullptr);
351 	update (me);
352 }
353 
354 /********** StructEditor **********/
355 
356 Thing_implement (StructEditor, DataSubEditor, 0);
357 
v_countFields()358 integer structStructEditor :: v_countFields () {
359 	return Data_Description_countMembers (d_description);
360 }
361 
singleTypeToText(void * address,int type,void * tagType,MelderString * buffer)362 static conststring32 singleTypeToText (void *address, int type, void *tagType, MelderString *buffer) {
363 	switch (type) {
364 		case bytewa:     MelderString_append (buffer, Melder_integer  (* (signed char *)    address)); break;
365 		case int16wa:    MelderString_append (buffer, Melder_integer  (* (int16 *)          address)); break;
366 		case intwa:      MelderString_append (buffer, Melder_integer  (* (int *)            address)); break;
367 		case integerwa:  MelderString_append (buffer, Melder_integer  (* (integer *)        address)); break;
368 		case ubytewa:    MelderString_append (buffer, Melder_integer  (* (unsigned char *)  address)); break;
369 		case uintwa:     MelderString_append (buffer, Melder_integer  (* (unsigned int *)   address)); break;
370 		case uintegerwa: MelderString_append (buffer, Melder_integer  (* (uinteger *)       address)); break;
371 		case floatwa:    MelderString_append (buffer, Melder_single   (* (double *)         address)); break;
372 		case doublewa:   MelderString_append (buffer, Melder_double   (* (double *)         address)); break;
373 		case complexwa:  MelderString_append (buffer, Melder_dcomplex (* (dcomplex *)       address)); break;
374 		case enumwa:     MelderString_append (buffer, U"<", ((conststring32 (*) (int)) tagType) (* (signed char *)  address), U">"); break;
375 		case lenumwa:    MelderString_append (buffer, U"<", ((conststring32 (*) (int)) tagType) (* (signed short *) address), U">"); break;
376 		case booleanwa:  MelderString_append (buffer, * (bool *) address ? U"<true>" : U"<false>"); break;
377 		case questionwa: MelderString_append (buffer, * (bool *) address ? U"<yes>"  : U"<no>"   ); break;
378 		case stringwa:
379 		case lstringwa: {
380 			char32 *string = * (char32 **) address;
381 			if (! string) {
382 				MelderString_empty (buffer);   // convert null string to empty string
383 				return buffer -> string;
384 			}
385 			return string;   // may be much longer than the usual size of 'buffer'
386 		} break;
387 		default: return U"(unknown)";
388 	}
389 	return buffer -> string;   // Mind the special return for strings above.
390 }
391 
showStructMember(void * structAddress,Data_Description structDescription,Data_Description memberDescription,DataSubEditor_FieldData fieldData,char32 * history)392 static void showStructMember (
393 	void *structAddress,   /* The address of (the first member of) the struct. */
394 	Data_Description structDescription,   /* The description of (the first member of) the struct. */
395 	Data_Description memberDescription,   /* The description of the current member. */
396 	DataSubEditor_FieldData fieldData,   /* The widgets in which to show the info about the current member. */
397 	char32 *history)
398 {
399 	int type = memberDescription -> type, rank = memberDescription -> rank;
400 	bool isSingleType = ( type <= maxsingletypewa && rank == 0 );
401 	unsigned char *memberAddress = (unsigned char *) structAddress + memberDescription -> offset;
402 	if (type == inheritwa) {
403 		GuiLabel_setText (fieldData -> label,
404 			Melder_cat (U"Class part \"", memberDescription -> name, U"\":"));
405 	} else {
406 		GuiLabel_setText (fieldData -> label,
407 			Melder_cat (U"   ", strip_d (memberDescription -> name),
408 				( rank == 0 ? U"" : rank == 1 || rank == 3 || rank < 0 ? U" [ ]" : U" [ ] [ ]" )));
409 	}
410 	//GuiControl_move (fieldData -> label, type == inheritwa ? 0 : NAME_X, fieldData -> y);
411 	GuiThing_show (fieldData -> label);
412 
413 	/* Show the value (for a single type) or a button (for a composite type). */
414 	if (isSingleType) {
415 		#if motif
416 			XtVaSetValues (fieldData -> text -> d_widget, XmNcolumns, 60, nullptr);   // TODO: change to GuiObject_size
417 		#endif
418 		autoMelderString buffer;
419 		conststring32 text = singleTypeToText (memberAddress, type, memberDescription -> tagType, & buffer);
420 		GuiText_setString (fieldData -> text, text);
421 		GuiThing_show (fieldData -> text);
422 		fieldData -> address = memberAddress;
423 		fieldData -> description = memberDescription;
424 		fieldData -> rank = 0;
425 	} else if (rank == 1) {
426 		void *arrayAddress = * (void **) memberAddress;
427 		integer minimum, maximum;
428 		if (! arrayAddress)
429 			return;   // no button for empty fields
430 		Data_Description_evaluateInteger (structAddress, structDescription,
431 			memberDescription -> min1, & minimum);
432 		Data_Description_evaluateInteger (structAddress, structDescription,
433 			memberDescription -> max1, & maximum);
434 		if (maximum < minimum)
435 			return;   // no button if no elements
436 		fieldData -> address = arrayAddress;   // indirect
437 		fieldData -> description = memberDescription;
438 		fieldData -> minimum = minimum;   // normally 1
439 		fieldData -> maximum = maximum;
440 		fieldData -> rank = 1;
441 		fieldData -> history = Melder_dup_f (history);
442 		GuiThing_show (fieldData -> button);
443 	} else if (rank < 0) {
444 		/*
445 			This represents an in-line array.
446 		*/
447 		integer maximum;   /* But: capacity = - rank */
448 		Data_Description_evaluateInteger (structAddress, structDescription,
449 			memberDescription -> max1, & maximum);
450 		if (-- maximum < 0)
451 			return;   // subtract one for zero-based array; no button if no elements
452 		fieldData -> address = memberAddress;   /* Direct. */
453 		fieldData -> description = memberDescription;
454 		fieldData -> minimum = 0;   // in-line arrays start with index 0
455 		fieldData -> maximum = maximum;   // probably between -1 and capacity - 1
456 		fieldData -> rank = rank;
457 		fieldData -> history = Melder_dup_f (history);
458 		GuiThing_show (fieldData -> button);
459 	} else if (rank == 3) {
460 		/*
461 			This represents an in-line set.
462 		*/
463 		fieldData -> address = memberAddress;   // direct
464 		fieldData -> description = memberDescription;
465 		fieldData -> minimum = str32equ (((conststring32 (*) (int)) memberDescription -> min1) (0), U"_") ? 1 : 0;
466 		fieldData -> maximum = ((int (*) (conststring32)) memberDescription -> max1) (U"\n");
467 		fieldData -> rank = rank;
468 		fieldData -> history = Melder_dup_f (history);
469 		GuiThing_show (fieldData -> button);
470 	} else if (rank == 2) {
471 		constMAT mat = * (constMAT *) memberAddress;
472 		//Melder_casual (U"Showing matrix member with ", mat.nrow, U" rows and ", mat.ncol, U" columns.");
473 		if (NUMisEmpty (mat))
474 			return;   // no button for empty fields
475 		integer min1, max1, min2, max2;
476 		Data_Description_evaluateInteger (structAddress, structDescription,
477 			memberDescription -> min1, & min1);
478 		Data_Description_evaluateInteger (structAddress, structDescription,
479 			memberDescription -> max1, & max1);
480 		Data_Description_evaluateInteger (structAddress, structDescription,
481 			memberDescription -> min2, & min2);
482 		Data_Description_evaluateInteger (structAddress, structDescription,
483 			memberDescription -> max2, & max2);
484 		if (max1 < min1 || max2 < min2)
485 			return;   // no button if no elements
486 		fieldData -> address = memberAddress;   // direct
487 		fieldData -> description = memberDescription;
488 		fieldData -> minimum = min1;   // normally 1
489 		fieldData -> maximum = max1;
490 		fieldData -> min2 = min2;
491 		fieldData -> max2 = max2;
492 		fieldData -> rank = 2;
493 		fieldData -> history = Melder_dup_f (history);
494 		GuiThing_show (fieldData -> button);
495 	} else if (type == structwa) {   // in-line struct
496 		fieldData -> address = memberAddress;   // direct
497 		fieldData -> description = memberDescription;
498 		fieldData -> rank = 0;
499 		fieldData -> history = Melder_dup_f (history);
500 		GuiThing_show (fieldData -> button);
501 	} else if (type == objectwa || type == collectionwa) {
502 		fieldData -> address = * (Daata *) memberAddress;   // indirect  // FIXME: not guaranteed for auto objects
503 		if (! fieldData -> address)
504 			return;   // no button if no object
505 		fieldData -> description = memberDescription;
506 		fieldData -> rank = 0;
507 		fieldData -> history = Melder_dup_f (history);
508 		GuiThing_show (fieldData -> button);
509 	} else if (type == collectionofwa) {
510 		fieldData -> address = (Daata) memberAddress;   // direct  // FIXME: not guaranteed for auto objects
511 		//Melder_casual (U"Daata ", Melder_pointer (fieldData -> address));
512 		//Melder_casual (U"Class ", ((Daata) fieldData -> address) -> classInfo -> className);
513 		if (! fieldData -> address)
514 			return;   // no button if no object
515 		fieldData -> description = memberDescription;
516 		fieldData -> rank = 0;
517 		fieldData -> history = Melder_dup_f (history);
518 		GuiThing_show (fieldData -> button);
519 	}
520 }
521 
showStructMembers(DataSubEditor me,void * structAddress,Data_Description structDescription,int fromMember,char32 * history)522 static void showStructMembers (DataSubEditor me, void *structAddress, Data_Description structDescription, int fromMember, char32 *history) {
523 	integer i = 1;
524 	Data_Description memberDescription = structDescription;
525 	for (; i < fromMember && memberDescription -> name != nullptr; i ++, memberDescription ++)
526 		(void) 0;
527 	for (; memberDescription -> name != nullptr; memberDescription ++) {
528 		if (++ my d_irow > kDataSubEditor_MAXNUM_ROWS)
529 			return;
530 		showStructMember (structAddress, structDescription, memberDescription, & my d_fieldData [my d_irow], history);
531 	}
532 }
533 
v_showMembers()534 void structStructEditor :: v_showMembers () {
535 	showStructMembers (this, our d_address, our d_description, our d_topField, our name.get());
536 }
537 
StructEditor_init(StructEditor me,DataEditor root,conststring32 title,void * address,Data_Description description)538 static void StructEditor_init (StructEditor me, DataEditor root, conststring32 title, void *address, Data_Description description) {
539 	DataSubEditor_init (me, root, title, address, description);
540 }
541 
StructEditor_create(DataEditor root,conststring32 title,void * address,Data_Description description)542 static void StructEditor_create (DataEditor root, conststring32 title, void *address, Data_Description description) {
543 	try {
544 		autoStructEditor me = Thing_new (StructEditor);
545 		StructEditor_init (me.get(), root, title, address, description);
546 		return me.releaseToUser();
547 	} catch (MelderError) {
548 		Melder_throw (U"Struct inspector window not created.");
549 	}
550 }
551 
552 /********** VectorEditor **********/
553 
554 Thing_implement (VectorEditor, DataSubEditor, 0);
555 
v_countFields()556 integer structVectorEditor :: v_countFields () {
557 	integer numberOfElements = d_maximum - d_minimum + 1;
558 	if (d_description -> type == structwa)
559 		return numberOfElements * (Data_Description_countMembers (* (Data_Description *) d_description -> tagType) + 1);
560 	else
561 		return numberOfElements;
562 }
563 
v_showMembers()564 void structVectorEditor :: v_showMembers () {
565 	int type = our d_description -> type;
566 	bool isSingleType = ( type <= maxsingletypewa );
567 	integer elementSize = ( type == structwa ?
568 		Data_Description_countMembers (* (Data_Description *) d_description -> tagType) + 1 : 1 );
569 	integer firstElement = d_minimum + (d_topField - 1) / elementSize;
570 
571 	for (integer ielement = firstElement; ielement <= d_maximum; ielement ++) {
572 		int skip = ielement == firstElement ? (our d_topField - 1) % elementSize : 0;
573 
574 		if (++ our d_irow > kDataSubEditor_MAXNUM_ROWS) return;
575 		DataSubEditor_FieldData fieldData = & our d_fieldData [d_irow];
576 
577 		if (isSingleType) {
578 			/*
579 				TODO: assume the address refers to a constVEC;
580 				this is not yet true of Collections, and
581 				indirect has to be changed to direct in showStructMember()
582 			*/
583 			unsigned char *elementAddress = (unsigned char *) our d_address + (ielement - 1) * our d_description -> size;
584 
585 			GuiControl_move (fieldData -> label, 0, fieldData -> y);
586 			GuiLabel_setText (fieldData -> label,
587 				Melder_cat (strip_d (our d_description -> name), U" [",
588 					( our d_description -> rank == 3 ? ((conststring32 (*) (int)) our d_description -> min1) (ielement) : Melder_integer (ielement) ),
589 					U"]"));
590 			GuiThing_show (fieldData -> label);
591 
592 			autoMelderString buffer;
593 			conststring32 text = singleTypeToText (elementAddress, type, our d_description -> tagType, & buffer);
594 			#if motif
595 				XtVaSetValues (fieldData -> text -> d_widget, XmNcolumns, 60, nullptr);   // TODO: change to GuiObject_size
596 			#endif
597 			GuiText_setString (fieldData -> text, text);
598 			GuiThing_show (fieldData -> text);
599 			fieldData -> address = elementAddress;
600 			fieldData -> description = d_description;
601 		} else if (type == structwa) {
602 			/*
603 				TODO: assume the address refers to a constVEC;
604 				this is not yet true of Collections, and
605 				indirect has to be changed to direct in showStructMember()
606 			*/
607 			unsigned char *elementAddress = (unsigned char *) our d_address + (ielement - 1) * our d_description -> size;
608 
609 			static MelderString history;
610 			MelderString_copy (& history, our name.get());
611 
612 			/* Replace things like [1..100] by things like [19]. */
613 
614 			if (history.string [history.length - 1] == ']') {
615 				char32 *openingBracket = str32rchr (history.string, U'[');
616 				Melder_assert (openingBracket != nullptr);
617 				* openingBracket = '\0';
618 				history.length = openingBracket - history.string;
619 			}
620 			MelderString_append (& history, U"[", ielement, U"]");
621 
622 			if (skip) {
623 				our d_irow --;
624 			} else {
625 				GuiControl_move (fieldData -> label, 0, fieldData -> y);
626 				GuiLabel_setText (fieldData -> label,
627 					Melder_cat (strip_d (d_description -> name), U" [", ielement, U"]: ---------------------------"));
628 				GuiThing_show (fieldData -> label);
629 			}
630 			showStructMembers (this, elementAddress, * (Data_Description *) d_description -> tagType, skip, history.string);
631 		} else if (type == objectwa) {
632 			/*
633 				TODO: assume the address refers to the items of a Collection (?)
634 			*/
635 			unsigned char *elementAddress = (unsigned char *) our d_address + ielement * our d_description -> size;
636 
637 			static MelderString history;
638 			MelderString_copy (& history, our name.get());
639 			if (history.string [history.length - 1] == U']') {
640 				char32 *openingBracket = str32rchr (history.string, U'[');
641 				Melder_assert (openingBracket != nullptr);
642 				* openingBracket = U'\0';
643 				history.length = openingBracket - history.string;
644 			}
645 			MelderString_append (& history, U"[", ielement, U"]");
646 
647 			GuiControl_move (fieldData -> label, 0, fieldData -> y);
648 			GuiLabel_setText (fieldData -> label, Melder_cat (strip_d (our d_description -> name), U" [", ielement, U"]"));
649 			GuiThing_show (fieldData -> label);
650 
651 			Daata object = * (Daata *) elementAddress;
652 			if (! object)
653 				return;   // no button if no object
654 			if (! Class_getDescription (object -> classInfo))
655 				return;   // no button if no description for this class
656 			fieldData -> address = object;
657 			fieldData -> description = Class_getDescription (object -> classInfo);
658 			fieldData -> rank = 0;
659 			fieldData -> history = Melder_dup_f (history.string);
660 			GuiThing_show (fieldData -> button);
661 		}
662 	}
663 }
664 
VectorEditor_create(DataEditor root,conststring32 title,void * address,Data_Description description,integer minimum,integer maximum)665 static void VectorEditor_create (DataEditor root, conststring32 title, void *address,
666 	Data_Description description, integer minimum, integer maximum)
667 {
668 	try {
669 		autoVectorEditor me = Thing_new (VectorEditor);
670 		my d_minimum = minimum;
671 		my d_maximum = maximum;
672 		DataSubEditor_init (me.get(), root, title, address, description);
673 		return me.releaseToUser();
674 	} catch (MelderError) {
675 		Melder_throw (U"Vector inspector window not created.");
676 	}
677 }
678 
679 /********** MatrixEditor **********/
680 
681 Thing_implement (MatrixEditor, DataSubEditor, 0);
682 
v_countFields()683 integer structMatrixEditor :: v_countFields () {
684 	integer numberOfElements = (d_maximum - d_minimum + 1) * (d_max2 - d_min2 + 1);
685 	if (d_description -> type == structwa)
686 		return numberOfElements * (Data_Description_countMembers (* (Data_Description *) d_description -> tagType) + 1);
687 	else
688 		return numberOfElements;
689 }
690 
v_showMembers()691 void structMatrixEditor :: v_showMembers () {
692 	int type = our d_description -> type;
693 	bool isSingleType = ( type <= maxsingletypewa );
694 	Melder_assert (isSingleType);   // allow no struct matrices
695 	integer rowSize = our d_max2 - our d_min2 + 1;
696 	constMAT mat = * (constMAT *) d_address;   // HACK: this could be a MAT or an INTMAT
697 	Melder_assert (rowSize == mat.ncol);   // HACK: this should work correctly even for an INTMAT
698 	integer firstRow = d_minimum + (d_topField - 1) / rowSize;
699 	integer firstColumn = d_min2 + (d_topField - 1 - (firstRow - d_minimum) * rowSize);
700 
701 	for (integer irow = firstRow; irow <= d_maximum; irow ++)
702 	for (integer icolumn = irow == firstRow ? firstColumn : d_min2; icolumn <= d_max2; icolumn ++) {
703 		unsigned char *elementAddress = (unsigned char *) mat.cells + ((irow - 1) * rowSize + (icolumn - 1)) * d_description -> size;
704 			// not & mat [irow] [icol], because that HACK would not work for an INTMAT
705 
706 		if (++ d_irow > kDataSubEditor_MAXNUM_ROWS)
707 			return;
708 		DataSubEditor_FieldData fieldData = & d_fieldData [d_irow];
709 
710 		if (isSingleType) {
711 			GuiControl_move (fieldData -> label, 0, fieldData -> y);
712 			GuiLabel_setText (fieldData -> label, Melder_cat (strip_d (d_description -> name), U" [", irow, U"] [", icolumn, U"]"));
713 			GuiThing_show (fieldData -> label);
714 
715 			autoMelderString buffer;
716 			conststring32 text = singleTypeToText (elementAddress, type, d_description -> tagType, & buffer);
717 			#if motif
718 				XtVaSetValues (fieldData -> text -> d_widget, XmNcolumns, 60, nullptr);   // TODO: change to GuiObject_size
719 			#endif
720 			GuiText_setString (fieldData -> text, text);
721 			GuiThing_show (fieldData -> text);
722 			fieldData -> address = elementAddress;
723 			fieldData -> description = d_description;
724 		}
725 	}
726 }
727 
MatrixEditor_create(DataEditor root,conststring32 title,void * address,Data_Description description,integer min1,integer max1,integer min2,integer max2)728 static void MatrixEditor_create (DataEditor root, conststring32 title, void *address,
729 	Data_Description description, integer min1, integer max1, integer min2, integer max2)
730 {
731 	try {
732 		autoMatrixEditor me = Thing_new (MatrixEditor);
733 		my d_minimum = min1;
734 		my d_maximum = max1;
735 		my d_min2 = min2;
736 		my d_max2 = max2;
737 		DataSubEditor_init (me.get(), root, title, address, description);
738 		return me.releaseToUser();
739 	} catch (MelderError) {
740 		Melder_throw (U"Matrix inspector window not created.");
741 	}
742 }
743 
744 /********** ClassEditor **********/
745 
746 Thing_implement (ClassEditor, StructEditor, 0);
747 
ClassEditor_showMembers_recursive(ClassEditor me,ClassInfo klas)748 static void ClassEditor_showMembers_recursive (ClassEditor me, ClassInfo klas) {
749 	ClassInfo parentClass = klas -> semanticParent;
750 	Data_Description description = Class_getDescription (klas);
751 	int classFieldsTraversed = 0;
752 	while (Class_getDescription (parentClass) == description)
753 		parentClass = parentClass -> semanticParent;
754 	if (parentClass != classDaata) {
755 		ClassEditor_showMembers_recursive (me, parentClass);
756 		classFieldsTraversed = Data_Description_countMembers (Class_getDescription (parentClass));
757 		//Melder_casual (U"ClassEditor_showMembers_recursive: classFieldsTraversed = ", classFieldsTraversed);
758 	}
759 	showStructMembers (me, my d_address, description, my d_irow ? 1 : my d_topField - classFieldsTraversed, my name.get());
760 }
761 
v_showMembers()762 void structClassEditor :: v_showMembers () {
763 	ClassEditor_showMembers_recursive (this, ((Daata) d_address) -> classInfo);
764 }
765 
ClassEditor_init(ClassEditor me,DataEditor root,conststring32 title,void * address,Data_Description description)766 static void ClassEditor_init (ClassEditor me, DataEditor root, conststring32 title, void *address, Data_Description description) {
767 	if (! description)
768 		Melder_throw (U"Class ", Thing_className ((Thing) address), U" cannot be inspected.");
769 	StructEditor_init (me, root, title, address, description);
770 }
771 
ClassEditor_create(DataEditor root,conststring32 title,void * address,Data_Description description)772 static void ClassEditor_create (DataEditor root, conststring32 title, void *address, Data_Description description) {
773 	try {
774 		autoClassEditor me = Thing_new (ClassEditor);
775 		ClassEditor_init (me.get(), root, title, address, description);
776 		return me.releaseToUser();
777 	} catch (MelderError) {
778 		Melder_throw (U"Class inspector window not created.");
779 	}
780 }
781 
782 /********** DataEditor **********/
783 
784 Thing_implement (DataEditor, ClassEditor, 0);
785 
DataEditor_destroyAllChildren(DataEditor me)786 static void DataEditor_destroyAllChildren (DataEditor me) {
787 	/*
788 		To destroy all children, we travel them from youngest to oldest,
789 		because the array of children will change from under us:
790 	*/
791 	for (int i = my children.size; i >= 1; i --) {
792 		/*
793 			An optimization coming!
794 
795 			Instead of
796 				DataSubEditor child = my children [i];
797 				forget (child);
798 			we isolate the child from the parent before destroying the child,
799 			so that the child won't try to remove the reference
800 			that the parent has to her.
801 			So first we make the parent forget the moribund child,
802 			which prevents a dangling pointer:
803 		*/
804 		DataSubEditor child = my children.subtractItem_ref (i);
805 		/*
806 			That was fast, because subtracting the last item involves no shifting
807 			of the remaining items.
808 
809 			Second, we make the child forget the parent,
810 			so that the child won't try to remove the reference
811 			that the parent had to her (but no longer has):
812 		*/
813 		child -> root = nullptr;
814 		/*
815 			The child is now fully isolated, so we are ready to destroy her:
816 		*/
817 		forget (child);
818 		/*
819 			This procedure was an optimization because if we just destroyed each child,
820 			each child would remove itself from the array by (1) searching for itself
821 			and (2) shifting the remaining children, both of which have a complexity
822 			that is linear in the number of children. So we would end up with quadratic complexity,
823 			whereas the procedure that we use above has linear complexity.
824 
825 			This linear complexity makes this procedure good enough for `v_destroy()`
826 			(where obtaining linear complexity would have been easy anyway),
827 			and nice enough for `v_dataChanged()`.
828 
829 			Something to note is that this procedure doesn't care whether the autoCollection
830 			`children` owns its items or not.
831 		*/
832 	}
833 }
834 
v_destroy()835 void structDataEditor :: v_destroy () noexcept {
836 	DataEditor_destroyAllChildren (this);
837 	DataEditor_Parent :: v_destroy ();
838 }
839 
v_dataChanged()840 void structDataEditor :: v_dataChanged () {
841 	/*
842 		Someone else changed our data.
843 		We know that the top-level data is still accessible,
844 		so we update the top-level window to show the change:
845 	*/
846 	update (this);
847 	/*
848 		Changing the data may have changed any part of the *structure* of the data,
849 		so we do not know if the data visible in any of the subeditors is still valid.
850 		We follow the most straightforward solution, which is to simply close all the child windows,
851 		which guarantees the removal of all dangling visual representations:
852 	*/
853 	DataEditor_destroyAllChildren (this);
854 }
855 
DataEditor_create(conststring32 title,Daata data)856 autoDataEditor DataEditor_create (conststring32 title, Daata data) {
857 	try {
858 		ClassInfo klas = data -> classInfo;
859 		if (Class_getDescription (klas) == nullptr)
860 			Melder_throw (U"Class ", klas -> className, U" cannot be inspected.");
861 		autoDataEditor me = Thing_new (DataEditor);
862 		ClassEditor_init (me.get(), me.get(), title, data, Class_getDescription (klas));
863 		return me;
864 	} catch (MelderError) {
865 		Melder_throw (U"Inspector window not created.");
866 	}
867 }
868 
869 /* End of file DataEditor.cpp */
870