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