1 /* praat.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 "melder.h"
20 #include <stdarg.h>
21 #if defined (UNIX) || defined (macintosh)
22 	#include <sys/types.h>
23 	#include <sys/stat.h>
24 	#include <signal.h>
25 #endif
26 #include <locale.h>
27 #if defined (UNIX)
28 	#include <unistd.h>
29 #endif
30 #if defined (_WIN32)
31 	#include <windows.h>
32 	#include <fcntl.h>
33 	#include <io.h>
34 #endif
35 
36 #include "praatP.h"
37 #include "praatM.h"
38 #include "praat_script.h"
39 #include "praat_version.h"
40 #include "site.h"
41 #include "machine.h"
42 #include "Printer.h"
43 #include "ScriptEditor.h"
44 #include "Strings_.h"
45 #include "../kar/UnicodeData.h"
46 #include "InfoEditor.h"
47 
48 Thing_implement (Praat_Command, Thing, 0);
49 
50 #define EDITOR  theCurrentPraatObjects -> list [IOBJECT]. editors
51 
52 #define WINDOW_WIDTH 520
53 #define WINDOW_HEIGHT 700
54 
55 /*
56 	Six globals, not `inline` (see praat.h 2021-04-15).
57 */
58 structPraatApplication theForegroundPraatApplication;
59 PraatApplication theCurrentPraatApplication = & theForegroundPraatApplication;
60 structPraatObjects theForegroundPraatObjects;
61 PraatObjects theCurrentPraatObjects = & theForegroundPraatObjects;
62 structPraatPicture theForegroundPraatPicture;
63 PraatPicture theCurrentPraatPicture = & theForegroundPraatPicture;
64 
65 static char32 programName [64];
66 static structMelderDir homeDir { };
67 
68 /*
69  * Melder_preferencesFolder: a folder containing preferences file, buttons file, message files, tracing file, plugins.
70  *    Unix:   /home/miep/.praat-dir   (without slash)
71  *    Windows XP/Vista/7/8/10:   \\myserver\myshare\Miep\Praat
72  *                         or:   C:\Users\Miep\Praat
73  *    MacOS:   /Users/Miep/Library/Preferences/Praat Prefs
74  */
75 // inline structMelderDir Melder_preferencesFolder { };   // already declared in Melder_files.h (checked 2021-03-07)
76 
77 /*
78  * prefsFile: preferences file.
79  *    Unix:   /home/miep/.praat-dir/prefs5
80  *    Windows XP/Vista/7/8/10:   \\myserver\myshare\Miep\Praat\Preferences5.ini
81  *                         or:   C:\Users\Miep\Praat\Preferences5.ini
82  *    MacOS:   /Users/Miep/Library/Preferences/Praat Prefs/Prefs5
83  */
84 static structMelderFile prefsFile { };
85 
86 /*
87  * buttonsFile: buttons file.
88  *    Unix:   /home/miep/.praat-dir/buttons
89  *    Windows XP/Vista/7/8/10:   \\myserver\myshare\Miep\Praat\Buttons5.ini
90  *                         or:   C:\Users\Miep\Praat\Buttons5.ini
91  *    MacOS:   /Users/Miep/Library/Preferences/Praat Prefs/Buttons5
92  */
93 static structMelderFile buttonsFile { };
94 
95 #if defined (UNIX)
96 	static structMelderFile pidFile { };   // like /home/miep/.praat-dir/pid
97 	static structMelderFile messageFile { };   // like /home/miep/.praat-dir/message
98 #elif defined (_WIN32)
99 	static structMelderFile messageFile { };   // like C:\Users\Miep\Praat\Message.txt
100 #endif
101 
102 /*
103  * tracingFile: tracing file.
104  *    Unix:   /home/miep/.praat-dir/tracing
105  *    Windows XP/Vista/7/8/10:   \\myserver\myshare\Miep\Praat\Tracing.txt
106  *                         or:   C:\Users\Miep\Praat\Tracing.txt
107  *    MacOS:   /Users/Miep/Library/Preferences/Praat Prefs/Tracing.txt
108  */
109 static structMelderFile tracingFile { };
110 
111 static GuiList praatList_objects;
112 
113 /***** selection *****/
114 
praat_idOfSelected(ClassInfo klas,integer inplace)115 integer praat_idOfSelected (ClassInfo klas, integer inplace) {
116 	integer place = inplace, IOBJECT;
117 	if (place == 0)
118 		place = 1;
119 	if (place > 0) {
120 		WHERE (SELECTED && (! klas || CLASS == klas)) {
121 			if (place == 1)
122 				return ID;
123 			place --;
124 		}
125 	} else {
126 		WHERE_DOWN (SELECTED && (! klas || CLASS == klas)) {
127 			if (place == -1)
128 				return ID;
129 			place ++;
130 		}
131 	}
132 	if (inplace)
133 		Melder_throw (U"No ", klas ? klas -> className : U"object", U" #", inplace, U" selected.");
134 	else
135 		Melder_throw (U"No ", klas ? klas -> className : U"object", U" selected.");
136 	return 0;
137 }
138 
praat_idsOfAllSelected(ClassInfo klas)139 autoVEC praat_idsOfAllSelected (ClassInfo klas) {
140 	autoVEC result = raw_VEC (praat_numberOfSelected (klas));
141 	integer selectedObjectNumber = 0, IOBJECT;
142 	WHERE (SELECTED && (! klas || CLASS == klas))
143 		result [++ selectedObjectNumber] = ID;
144 	return result;
145 }
146 
praat_nameOfSelected(ClassInfo klas,integer inplace)147 char32 * praat_nameOfSelected (ClassInfo klas, integer inplace) {
148 	integer place = inplace, IOBJECT;
149 	if (place == 0)
150 		place = 1;
151 	if (place > 0) {
152 		WHERE (SELECTED && (! klas || CLASS == klas)) {
153 			if (place == 1)
154 				return klas ? NAME : FULL_NAME;
155 			place --;
156 		}
157 	} else {
158 		WHERE_DOWN (SELECTED && (! klas || CLASS == klas)) {
159 			if (place == -1)
160 				return klas ? NAME : FULL_NAME;
161 			place ++;
162 		}
163 	}
164 	if (inplace)
165 		Melder_throw (U"No ", klas ? klas -> className : U"object", U" #", inplace, U" selected.");
166 	else
167 		Melder_throw (U"No ", klas ? klas -> className : U"object", U" selected.");
168 	return nullptr;   // failure, but never reached anyway
169 }
170 
praat_numberOfSelected(ClassInfo klas)171 integer praat_numberOfSelected (ClassInfo klas) {
172 	if (! klas)
173 		return theCurrentPraatObjects -> totalSelection;
174 	integer readableClassId = klas -> sequentialUniqueIdOfReadableClass;
175 	if (readableClassId == 0)
176 		Melder_fatal (U"No sequential unique ID for class ", klas -> className, U".");
177 	return theCurrentPraatObjects -> numberOfSelected [readableClassId];
178 }
179 
praat_deselect(integer IOBJECT)180 void praat_deselect (integer IOBJECT) {
181 	if (! SELECTED)
182 		return;
183 	SELECTED = false;
184 	theCurrentPraatObjects -> totalSelection -= 1;
185 	integer readableClassId = theCurrentPraatObjects -> list [IOBJECT]. object -> classInfo -> sequentialUniqueIdOfReadableClass;
186 	Melder_assert (readableClassId != 0);
187 	theCurrentPraatObjects -> numberOfSelected [readableClassId] -= 1;
188 	if (! theCurrentPraatApplication -> batch && ! Melder_backgrounding) {
189 		trace (U"deselecting object ", IOBJECT);
190 		GuiList_deselectItem (praatList_objects, IOBJECT);
191 		trace (U"deselected object ", IOBJECT);
192 	}
193 }
194 
praat_deselectAll()195 void praat_deselectAll () {
196 	integer IOBJECT;
197 	WHERE (1)
198 		praat_deselect (IOBJECT);
199 }
200 
praat_select(integer IOBJECT)201 void praat_select (integer IOBJECT) {
202 	if (SELECTED)
203 		return;
204 	SELECTED = true;
205 	theCurrentPraatObjects -> totalSelection += 1;
206 	Thing object = theCurrentPraatObjects -> list [IOBJECT]. object;
207 	Melder_assert (object);
208 	integer readableClassId = object -> classInfo -> sequentialUniqueIdOfReadableClass;
209 	if (readableClassId == 0)
210 		Melder_fatal (U"No sequential unique ID for class ", object -> classInfo -> className, U".");
211 	theCurrentPraatObjects -> numberOfSelected [readableClassId] += 1;
212 	if (! theCurrentPraatApplication -> batch && ! Melder_backgrounding)
213 		GuiList_selectItem (praatList_objects, IOBJECT);
214 }
215 
praat_selectAll()216 void praat_selectAll () {
217 	integer IOBJECT;
218 	WHERE (true)
219 		praat_select (IOBJECT);
220 }
221 
praat_list_background()222 void praat_list_background () {
223 	integer IOBJECT;
224 	WHERE (SELECTED)
225 		GuiList_deselectItem (praatList_objects, IOBJECT);
226 }
praat_list_foreground()227 void praat_list_foreground () {
228 	integer IOBJECT;
229 	WHERE (SELECTED)
230 		GuiList_selectItem (praatList_objects, IOBJECT);
231 }
232 
praat_getSelectedObjects()233 autoCollection praat_getSelectedObjects () {
234 	autoCollection thee = Collection_create ();
235 	integer IOBJECT;
236 	LOOP {
237 		iam_LOOP (Daata);
238 		thy addItem_ref (me);
239 	}
240 	return thee;
241 }
242 
praat_name(integer IOBJECT)243 char32 *praat_name (integer IOBJECT) { return str32chr (FULL_NAME, U' ') + 1; }
244 
praat_write_do(UiForm dia,conststring32 extension)245 void praat_write_do (UiForm dia, conststring32 extension) {
246 	static MelderString defaultFileName;
247 	if (extension && str32chr (extension, U'.')) {
248 		/*
249 			Apparently, the "extension" is a complete file name.
250 			This should be used as the default file name.
251 			(This case typically occurs when saving a picture.)
252 		*/
253 		MelderString_copy (& defaultFileName, extension);
254 	} else {
255 		/*
256 			Apparently, the "extension" is not a complete file name.
257 			We are expected to prepend the "extension" with the name of a selected object.
258 		*/
259 		integer IOBJECT, found = 0;
260 		Daata data = nullptr;
261 		WHERE (SELECTED) {
262 			if (! data)
263 				data = (Daata) OBJECT;
264 			found += 1;
265 		}
266 		if (found == 1) {
267 			MelderString_copy (& defaultFileName, data -> name.get());
268 			if (defaultFileName.length > 200) {
269 				defaultFileName.string [200] = U'\0';
270 				defaultFileName.length = 200;
271 			}
272 			MelderString_append (& defaultFileName, U".", extension ? extension : Thing_className (data));
273 		} else if (! extension) {
274 			MelderString_copy (& defaultFileName, U"praat.Collection");
275 		} else {
276 			MelderString_copy (& defaultFileName, U"praat.", extension);
277 		}
278 	}
279 	UiOutfile_do (dia, defaultFileName.string);
280 }
281 
removeAllReferencesToMoribundEditor(Editor editor)282 static void removeAllReferencesToMoribundEditor (Editor editor) {
283 	/*
284 		Remove all references to this editor.
285 		It may be editing multiple objects.
286 	*/
287 	for (integer iobject = 1; iobject <= theCurrentPraatObjects -> n; iobject ++)
288 		for (integer ieditor = 0; ieditor < praat_MAXNUM_EDITORS; ieditor ++)
289 			if (theCurrentPraatObjects -> list [iobject]. editors [ieditor] == editor)
290 				theCurrentPraatObjects -> list [iobject]. editors [ieditor] = nullptr;
291 	if (praatP. editor == editor)
292 		praatP. editor = nullptr;
293 }
294 
295 /**
296 	Remove the "object" from the list,
297 	killing everything that has to do with the selection.
298 */
praat_remove(integer iobject,bool removeVisibly)299 static void praat_remove (integer iobject, bool removeVisibly) {
300 	Melder_assert (iobject >= 1 && iobject <= theCurrentPraatObjects -> n);
301 	if (theCurrentPraatObjects -> list [iobject]. isBeingCreated) {
302 		theCurrentPraatObjects -> list [iobject]. isBeingCreated = false;
303 		theCurrentPraatObjects -> totalBeingCreated --;
304 	}
305 	trace (U"deselect object ", iobject);
306 	if (removeVisibly)
307 		praat_deselect (iobject);
308 	trace (U"deselected object ", iobject);
309 
310 	/*
311 		To prevent synchronization problems, kill editors before killing the data.
312 	*/
313 	for (integer ieditor = 0; ieditor < praat_MAXNUM_EDITORS; ieditor ++) {
314 		Editor editor = theCurrentPraatObjects -> list [iobject]. editors [ieditor];   // save this one reference
315 		if (editor) {
316 			trace (U"remove references to editor ", ieditor);
317 			removeAllReferencesToMoribundEditor (editor);
318 			trace (U"forget editor ", ieditor);
319 			if (removeVisibly)
320 				forget (editor);   // TODO: doesn't this call removeAllReferencesToMoribundEditor() again?
321 			trace (U"forgotten editor ", ieditor);
322 		}
323 	}
324 	MelderFile_setToNull (& theCurrentPraatObjects -> list [iobject]. file);
325 	trace (U"free name");
326 	theCurrentPraatObjects -> list [iobject]. name. reset();
327 	trace (U"forget object");
328 	forget (theCurrentPraatObjects -> list [iobject]. object);   // note: this might save a file-based object to file
329 	trace (U"forgotten object");
330 }
331 
praat_cleanUpName(char32 * name)332 void praat_cleanUpName (char32 *name) {
333 /*
334 	Replaces spaces and special characters by underscores.
335 */
336 	for (; *name; name ++)
337 		if (str32chr (U" ,.:;\\/()[]{}~`\'<>*&^%#@!?$\"|", *name))
338 			*name = U'_';
339 }
340 
341 /***** objects + commands *****/
342 
praat_new_unpackCollection(autoCollection me,const char32 * myName)343 static void praat_new_unpackCollection (autoCollection me, const char32* myName) {
344 	for (integer idata = 1; idata <= my size; idata ++) {
345 		autoDaata object;
346 		object. adoptFromAmbiguousOwner ((Daata) my at [idata]);
347 		my at [idata] = nullptr;   // disown; once the elements are autoThings, the move will handle this
348 		const conststring32 name = ( object -> name ? object -> name.get() : myName );
349 		Melder_assert (name);
350 		praat_new (object.move(), name);   // recurse
351 	}
352 }
353 
praat_newWithFile(autoDaata me,MelderFile file,conststring32 myName)354 void praat_newWithFile (autoDaata me, MelderFile file, conststring32 myName) {
355 	if (! me)
356 		Melder_throw (U"No object was put into the list.");
357 
358 	if (my classInfo == classCollection) {
359 		praat_new_unpackCollection (me.static_cast_move<structCollection>(), myName);
360 		return;
361 	}
362 
363 	autoMelderString name, givenName;
364 	if (myName && myName [0]) {
365 		MelderString_copy (& givenName, myName);
366 		/*
367 		 * Remove extension.
368 		 */
369 		char32 *p = str32rchr (givenName.string, U'.');
370 		if (p)
371 			*p = U'\0';
372 	} else {
373 		MelderString_copy (& givenName, my name && my name [0] ? my name.get() : U"untitled");
374 	}
375 	praat_cleanUpName (givenName.string);
376 	MelderString_append (& name, Thing_className (me.get()), U" ", givenName.string);
377 
378 	if (theCurrentPraatObjects -> n == praat_MAXNUM_OBJECTS) {
379 		//forget (me);
380 		Melder_throw (U"The Object Window cannot contain more than ", praat_MAXNUM_OBJECTS, U" objects. You could remove some objects.");
381 	}
382 
383 	integer IOBJECT = ++ theCurrentPraatObjects -> n;
384 	Melder_assert (FULL_NAME == nullptr);
385 	theCurrentPraatObjects -> list [IOBJECT]. name = Melder_dup_f (name.string);   // all right to crash if out of memory
386 	++ theCurrentPraatObjects -> uniqueId;
387 
388 	if (! theCurrentPraatApplication -> batch)   // put a new object on the screen, at the bottom of the list
389 		GuiList_insertItem (
390 			praatList_objects,
391 			Melder_cat (theCurrentPraatObjects -> uniqueId, U". ", name.string),
392 			theCurrentPraatObjects -> n
393 		);
394 	CLASS = my classInfo;
395 	OBJECT = me.releaseToAmbiguousOwner();   // FIXME: should be move()
396 	SELECTED = false;
397 	for (int ieditor = 0; ieditor < praat_MAXNUM_EDITORS; ieditor ++)
398 		EDITOR [ieditor] = nullptr;
399 	if (file)
400 		MelderFile_copy (file, & theCurrentPraatObjects -> list [IOBJECT]. file);
401 	else
402 		MelderFile_setToNull (& theCurrentPraatObjects -> list [IOBJECT]. file);
403 	ID = theCurrentPraatObjects -> uniqueId;
404 	theCurrentPraatObjects -> list [IOBJECT]. isBeingCreated = true;
405 	Thing_setName (OBJECT, givenName.string);
406 	theCurrentPraatObjects -> totalBeingCreated ++;
407 }
408 
409 static MelderString thePraatNewName;
praat_new(autoDaata me)410 void praat_new (autoDaata me) {
411 	praat_newWithFile (me.move(), nullptr, U"");
412 }
praat_new(autoDaata me,const MelderArg & arg)413 void praat_new (autoDaata me, const MelderArg& arg) {
414 	praat_newWithFile (me.move(), nullptr, arg._arg);
415 }
praat_new(autoDaata me,const MelderArg & arg1,const MelderArg & arg2,const MelderArg & arg3,const MelderArg & arg4,const MelderArg & arg5)416 void praat_new (autoDaata me,
417 	const MelderArg& arg1, const MelderArg& arg2, const MelderArg& arg3,
418 	const MelderArg& arg4, const MelderArg& arg5)
419 {
420 	MelderString_copy (& thePraatNewName, arg1._arg, arg2._arg, arg3._arg, arg4._arg, arg5._arg);
421 	praat_new (me.move(), thePraatNewName.string);
422 }
423 
praat_updateSelection()424 void praat_updateSelection () {
425 	if (theCurrentPraatObjects -> totalBeingCreated > 0) {
426 		integer IOBJECT;
427 		praat_deselectAll ();
428 		WHERE (theCurrentPraatObjects -> list [IOBJECT]. isBeingCreated) {
429 			praat_select (IOBJECT);
430 			theCurrentPraatObjects -> list [IOBJECT]. isBeingCreated = false;
431 		}
432 		theCurrentPraatObjects -> totalBeingCreated = 0;
433 		praat_show ();
434 	}
435 }
436 
gui_cb_list_selectionChanged(Thing,GuiList_SelectionChangedEvent event)437 static void gui_cb_list_selectionChanged (Thing /* boss */, GuiList_SelectionChangedEvent event) {
438 	Melder_assert (event -> list == praatList_objects);
439 	integer IOBJECT;
440 	bool first = true;
441 	WHERE (SELECTED) {
442 		SELECTED = false;
443 		integer readableClassId = theCurrentPraatObjects -> list [IOBJECT]. object -> classInfo -> sequentialUniqueIdOfReadableClass;
444 		theCurrentPraatObjects -> numberOfSelected [readableClassId] --;
445 		Melder_assert (theCurrentPraatObjects -> numberOfSelected [readableClassId] >= 0);
446 	}
447 	theCurrentPraatObjects -> totalSelection = 0;
448 	autoINTVEC selected = GuiList_getSelectedPositions (praatList_objects);
449 	for (integer iselected = 1; iselected <= selected.size; iselected ++) {
450 		IOBJECT = selected [iselected];
451 		SELECTED = true;
452 		integer readableClassId = theCurrentPraatObjects -> list [IOBJECT]. object -> classInfo -> sequentialUniqueIdOfReadableClass;
453 		theCurrentPraatObjects -> numberOfSelected [readableClassId] ++;
454 		Melder_assert (theCurrentPraatObjects -> numberOfSelected [readableClassId] > 0);
455 		UiHistory_write (first ? U"\nselectObject: \"" : U"\nplusObject: \"");
456 		UiHistory_write_expandQuotes (FULL_NAME);
457 		UiHistory_write (U"\"");
458 		first = false;
459 		theCurrentPraatObjects -> totalSelection += 1;
460 	}
461 	praat_show ();
462 }
463 
praat_list_renameAndSelect(integer position,conststring32 name)464 void praat_list_renameAndSelect (integer position, conststring32 name) {
465 	if (! theCurrentPraatApplication -> batch) {
466 		GuiList_replaceItem (praatList_objects, name, position);   // void if name equal
467 		if (! Melder_backgrounding)
468 			GuiList_selectItem (praatList_objects, position);
469 	}
470 }
471 
472 /***** objects *****/
473 
praat_name2(char32 * name,ClassInfo klas1,ClassInfo klas2)474 void praat_name2 (char32 *name, ClassInfo klas1, ClassInfo klas2) {
475 	int i1 = 1;
476 	while (theCurrentPraatObjects -> list [i1]. isSelected == 0 || theCurrentPraatObjects -> list [i1]. klas != klas1)
477 		i1 ++;
478 	int i2 = 1;
479 	while (theCurrentPraatObjects -> list [i2]. isSelected == 0 || theCurrentPraatObjects -> list [i2]. klas != klas2)
480 		i2 ++;
481 	char32 *name1 = str32chr (theCurrentPraatObjects -> list [i1]. name.get(), U' ') + 1;
482 	char32 *name2 = str32chr (theCurrentPraatObjects -> list [i2]. name.get(), U' ') + 1;
483 	if (str32equ (name1, name2))
484 		Melder_sprint (name,200, name1);
485 	else
486 		Melder_sprint (name,200, name1, U"_", name2);
487 }
488 
praat_removeObject(integer i)489 void praat_removeObject (integer i) {
490 	praat_remove (i, true);   // dangle
491 	for (integer j = i; j < theCurrentPraatObjects -> n; j ++)
492 		theCurrentPraatObjects -> list [j] = std::move (theCurrentPraatObjects -> list [j + 1]);   // undangle but create second references
493 	theCurrentPraatObjects -> list [theCurrentPraatObjects -> n]. name. reset ();
494 	theCurrentPraatObjects -> list [theCurrentPraatObjects -> n]. object = nullptr;   // undangle or remove second reference
495 	theCurrentPraatObjects -> list [theCurrentPraatObjects -> n]. isSelected = 0;
496 	for (int ieditor = 0; ieditor < praat_MAXNUM_EDITORS; ieditor ++)
497 		theCurrentPraatObjects -> list [theCurrentPraatObjects -> n]. editors [ieditor] = nullptr;   // undangle or remove second reference
498 	MelderFile_setToNull (& theCurrentPraatObjects -> list [theCurrentPraatObjects -> n]. file);   // undangle or remove second reference
499 	-- theCurrentPraatObjects -> n;
500 	if (! theCurrentPraatApplication -> batch)
501 		GuiList_deleteItem (praatList_objects, i);
502 }
503 
praat_exit(int exit_code)504 static void praat_exit (int exit_code) {
505 //Melder_setTracing (true);
506 	integer IOBJECT;
507 	trace (U"destroy the picture window");
508 	praat_picture_exit ();
509 	praat_statistics_exit ();   // record total memory use across sessions
510 
511 	if (! praatP.ignorePreferenceFiles) {
512 		trace (U"stop receiving messages");
513 		#if defined (UNIX)
514 			/*
515 			 * We are going to delete the process id ("pid") file, if it's ours.
516 			 */
517 			if (pidFile. path [0]) {
518 				try {
519 					/*
520 					 * To see whether we own the pid file,
521 					 * we look into it to see whether its pid equals our pid.
522 					 * If not, then we are probably living in an old invocation of the program,
523 					 * and the pid file was written by the latest invocation of the program,
524 					 * which owns the pid (this means sendpraat can only send to the latest Praat if more than one are open).
525 					 */
526 					autofile f = Melder_fopen (& pidFile, "r");
527 					long_not_integer pid;
528 					if (fscanf (f, "%ld", & pid) < 1)
529 						throw MelderError ();
530 					f.close (& pidFile);
531 					if (pid == getpid ())   // is the pid in the pid file equal to our pid?
532 						MelderFile_delete (& pidFile);   // ...then we own the pid file and can delete it
533 				} catch (MelderError) {
534 					Melder_clearError ();   // if the pid file is somehow missing or corrupted, we just ignore that
535 				}
536 			}
537 		#endif
538 
539 		trace (U"save the preferences");
540 		Melder_assert (str32equ (Melder_double (1.5), U"1.5"));   // refuse to write the preferences if the locale is wrong (even if tracing is on)
541 		Preferences_write (& prefsFile);
542 
543 		trace (U"save the script buttons");
544 		if (! theCurrentPraatApplication -> batch) {
545 			try {
546 				autoMelderString buffer;
547 				MelderString_append (& buffer, U"# Buttons (1).\n");
548 				MelderString_append (& buffer, U"# This file is generated automatically when you quit the ", praatP.title.get(), U" program.\n");
549 				MelderString_append (& buffer, U"# It contains the buttons that you added interactively to the fixed or dynamic menus,\n");
550 				MelderString_append (& buffer, U"# and the buttons that you hid or showed.\n\n");
551 				praat_saveAddedMenuCommands (& buffer);
552 				praat_saveToggledMenuCommands (& buffer);
553 				praat_saveAddedActions (& buffer);
554 				praat_saveToggledActions (& buffer);
555 				MelderFile_writeText (& buttonsFile, buffer.string, kMelder_textOutputEncoding::ASCII_THEN_UTF16);
556 			} catch (MelderError) {
557 				Melder_clearError ();
558 			}
559 		}
560 	}
561 
562 	trace (U"flush the file-based objects");
563 	WHERE_DOWN (! MelderFile_isNull (& theCurrentPraatObjects -> list [IOBJECT]. file)) {
564 		trace (U"removing object based on file ", & theCurrentPraatObjects -> list [IOBJECT]. file);
565 		praat_remove (IOBJECT, false);
566 	}
567 	Melder_files_cleanUp ();   // in case a URL is open
568 
569 	trace (U"leave the program");
570 	praat_menuCommands_exit_optimizeByLeaking ();   // these calls are superflous if subsequently _Exit() is called instead of exit()
571 	praat_actions_exit_optimizeByLeaking ();
572 	Preferences_exit_optimizeByLeaking ();
573 	/*
574 		OPTIMIZE with an exercise in self-documenting code
575 	*/
576 	constexpr bool weWouldLikeToOptimizeExitingSpeed = ((true));
577 	constexpr bool callingExitTimeDestructorsIsSlow = (true);
578 	constexpr bool notCallingExitTimeDestructorsCausesCorrectBehaviour = (true);
579 	constexpr bool weAreReallySureAboutThat = (true);
580 	constexpr bool weWillUseUnderscoreExitInsteadOfExit =
581 			weWouldLikeToOptimizeExitingSpeed &&
582 			callingExitTimeDestructorsIsSlow &&
583 			notCallingExitTimeDestructorsCausesCorrectBehaviour &&
584 			weAreReallySureAboutThat;
585 	if ((weWillUseUnderscoreExitInsteadOfExit)) {
586 		constexpr bool underscoreExitHasMoreSideEffectsThanJustNotCallingExitTimeDestructors = (true);
587 		constexpr bool avoidOtherSideEffectsOfUnderscoreExit =
588 				underscoreExitHasMoreSideEffectsThanJustNotCallingExitTimeDestructors;
589 		if ((avoidOtherSideEffectsOfUnderscoreExit)) {
590 			constexpr bool oneSideEffectIsThatOpenOutputFilesAreNotFlushed = true;
591 			constexpr bool weShouldFlushAllOpenOutputFilesWhoseNonflushingWouldCauseIncorrectBehaviour =
592 					oneSideEffectIsThatOpenOutputFilesAreNotFlushed;
593 			if ((weShouldFlushAllOpenOutputFilesWhoseNonflushingWouldCauseIncorrectBehaviour)) {
594 				constexpr bool stdoutIsOpen = (true);
595 				constexpr bool stderrIsOpen = (true);
596 				constexpr bool stdoutIsBufferedByDefault = true;
597 				constexpr bool stderrIsBufferedByDefault = false;
598 				constexpr bool weKnowThatSetbufHasNotBeenCalledOnStdout = (false);
599 				constexpr bool weKnowThatSetbufHasNotBeenCalledOnStderr = (false);
600 				constexpr bool stdoutHasCertainlyBeenFlushed =
601 						! stdoutIsBufferedByDefault && weKnowThatSetbufHasNotBeenCalledOnStdout;
602 				constexpr bool stderrHasCertainlyBeenFlushed =
603 						! stderrIsBufferedByDefault && weKnowThatSetbufHasNotBeenCalledOnStderr;
604 				constexpr bool notFlushingStdoutCouldCauseIncorrectBehaviour =
605 						stdoutIsOpen && ! stdoutHasCertainlyBeenFlushed;
606 				constexpr bool notFlushingStderrCouldCauseIncorrectBehaviour =
607 						stderrIsOpen && ! stderrHasCertainlyBeenFlushed;
608 				constexpr bool shouldFlushStdout = notFlushingStdoutCouldCauseIncorrectBehaviour;
609 				constexpr bool shouldFlushStderr = notFlushingStderrCouldCauseIncorrectBehaviour;
610 				if ((shouldFlushStdout))
611 					fflush (stdout);
612 				if ((shouldFlushStderr))
613 					fflush (stderr);
614 				constexpr bool thereAreOtherOpenFiles = (false);
615 				constexpr bool thereAreOtherOpenFilesWhoseNonflushingCouldCauseIncorrectBehaviour =
616 						thereAreOtherOpenFiles;
617 				if ((! thereAreOtherOpenFilesWhoseNonflushingCouldCauseIncorrectBehaviour)) {}
618 			}
619 			constexpr bool thereAreNoOtherSideEffectsBesideNotCallingExitDestructorsAndNotFlushingOpenFiles = (true);
620 			if ((thereAreNoOtherSideEffectsBesideNotCallingExitDestructorsAndNotFlushingOpenFiles)) {}
621 		}
622 		_Exit (exit_code);
623 	} else {
624 		exit (exit_code);
625 	}
626 }
627 
cb_Editor_destruction(Editor me)628 static void cb_Editor_destruction (Editor me) {
629 	removeAllReferencesToMoribundEditor (me);
630 }
631 
cb_Editor_dataChanged(Editor me)632 static void cb_Editor_dataChanged (Editor me) {
633 	for (integer iobject = 1; iobject <= theCurrentPraatObjects -> n; iobject ++) {
634 		/*
635 			Am I editing this object?
636 		*/
637 		bool editingThisObject = false;
638 		for (int ieditor = 0; ieditor < praat_MAXNUM_EDITORS; ieditor ++)
639 			editingThisObject |= ( theCurrentPraatObjects -> list [iobject]. editors [ieditor] == me );
640 		if (editingThisObject) {
641 			/*
642 				Notify all other editors associated with this object.
643 			*/
644 			for (int ieditor = 0; ieditor < praat_MAXNUM_EDITORS; ieditor ++) {
645 				Editor otherEditor = theCurrentPraatObjects -> list [iobject]. editors [ieditor];
646 				if (otherEditor && otherEditor != me)
647 					Editor_dataChanged (otherEditor);
648 			}
649 		}
650 	}
651 }
652 
cb_Editor_publication(Editor,autoDaata publication)653 static void cb_Editor_publication (Editor /* me */, autoDaata publication) {
654 /*
655    The default publish callback.
656    Works nicely if the publisher invents a name.
657 */
658 	try {
659 		praat_new (publication.move(), U"");
660 	} catch (MelderError) {
661 		Melder_flushError ();
662 	}
663 	praat_updateSelection ();
664 }
665 
praat_installEditor(Editor editor,integer IOBJECT)666 void praat_installEditor (Editor editor, integer IOBJECT) {
667 	if (! editor)
668 		return;
669 	for (integer ieditor = 0; ieditor < praat_MAXNUM_EDITORS; ieditor ++) {
670 		if (! EDITOR [ieditor]) {
671 			EDITOR [ieditor] = editor;
672 			Editor_setDestructionCallback (editor, cb_Editor_destruction);
673 			Editor_setDataChangedCallback (editor, cb_Editor_dataChanged);
674 			if (! editor -> d_publicationCallback)
675 				Editor_setPublicationCallback (editor, cb_Editor_publication);
676 			//Thing_setName (editor, Melder_cat (editor -> name.get(), U" [", ieditor + 1, U"]"));   // would break existing scripts
677 			return;
678 		}
679 	}
680 	Melder_throw (U"(praat_installEditor:) Cannot have more than ", praat_MAXNUM_EDITORS, U" editors with one object.");
681 }
682 
praat_installEditor2(Editor editor,integer i1,integer i2)683 void praat_installEditor2 (Editor editor, integer i1, integer i2) {
684 	if (! editor)
685 		return;
686 	integer ieditor1 = 0;
687 	for (; ieditor1 < praat_MAXNUM_EDITORS; ieditor1 ++)
688 		if (! theCurrentPraatObjects -> list [i1]. editors [ieditor1])
689 			break;
690 	integer ieditor2 = 0;
691 	for (; ieditor2 < praat_MAXNUM_EDITORS; ieditor2 ++)
692 		if (! theCurrentPraatObjects -> list [i2]. editors [ieditor2])
693 			break;
694 	if (ieditor1 < praat_MAXNUM_EDITORS && ieditor2 < praat_MAXNUM_EDITORS) {
695 		theCurrentPraatObjects -> list [i1]. editors [ieditor1] = theCurrentPraatObjects -> list [i2]. editors [ieditor2] = editor;
696 		Editor_setDestructionCallback (editor, cb_Editor_destruction);
697 		Editor_setDataChangedCallback (editor, cb_Editor_dataChanged);
698 		if (! editor -> d_publicationCallback)
699 			Editor_setPublicationCallback (editor, cb_Editor_publication);
700 	} else {
701 		Melder_throw (U"(praat_installEditor2:) Cannot have more than ", praat_MAXNUM_EDITORS, U" editors with one object.");
702 	}
703 }
704 
praat_installEditor3(Editor editor,integer i1,integer i2,integer i3)705 void praat_installEditor3 (Editor editor, integer i1, integer i2, integer i3) {
706 	if (! editor)
707 		return;
708 	integer ieditor1 = 0;
709 	for (; ieditor1 < praat_MAXNUM_EDITORS; ieditor1 ++)
710 		if (! theCurrentPraatObjects -> list [i1]. editors [ieditor1])
711 			break;
712 	integer ieditor2 = 0;
713 	for (; ieditor2 < praat_MAXNUM_EDITORS; ieditor2 ++)
714 		if (! theCurrentPraatObjects -> list [i2]. editors [ieditor2])
715 			break;
716 	integer ieditor3 = 0;
717 	for (; ieditor3 < praat_MAXNUM_EDITORS; ieditor3 ++)
718 		if (! theCurrentPraatObjects -> list [i3]. editors [ieditor3])
719 			break;
720 	if (ieditor1 < praat_MAXNUM_EDITORS && ieditor2 < praat_MAXNUM_EDITORS && ieditor3 < praat_MAXNUM_EDITORS) {
721 		theCurrentPraatObjects -> list [i1]. editors [ieditor1] = theCurrentPraatObjects -> list [i2]. editors [ieditor2] = theCurrentPraatObjects -> list [i3]. editors [ieditor3] = editor;
722 		Editor_setDestructionCallback (editor, cb_Editor_destruction);
723 		Editor_setDataChangedCallback (editor, cb_Editor_dataChanged);
724 		if (! editor -> d_publicationCallback)
725 			Editor_setPublicationCallback (editor, cb_Editor_publication);
726 	} else {
727 		Melder_throw (U"(praat_installEditor3:) Cannot have more than ", praat_MAXNUM_EDITORS, U" editors with one object.");
728 	}
729 }
730 
praat_installEditorN(Editor editor,DaataList objects)731 void praat_installEditorN (Editor editor, DaataList objects) {
732 	if (! editor)
733 		return;
734 	/*
735 		First check whether all objects in the Ordered are also in the List of Objects (Praat crashes if not),
736 		and check whether there is room to add an editor for each.
737 	*/
738 	for (integer iOrderedObject = 1; iOrderedObject <= objects->size; iOrderedObject ++) {
739 		Daata object = objects->at [iOrderedObject];
740 		integer iPraatObject = 1;
741 		for (; iPraatObject <= theCurrentPraatObjects -> n; iPraatObject ++) {
742 			if (object == theCurrentPraatObjects -> list [iPraatObject]. object) {
743 				int ieditor = 0;
744 				for (; ieditor < praat_MAXNUM_EDITORS; ieditor ++)
745 					if (! theCurrentPraatObjects -> list [iPraatObject]. editors [ieditor])
746 						break;
747 				if (ieditor >= praat_MAXNUM_EDITORS)
748 					Melder_throw (U"Cannot view the same object in more than ", praat_MAXNUM_EDITORS, U" windows.");
749 				break;
750 			}
751 		}
752 		Melder_assert (iPraatObject <= theCurrentPraatObjects -> n);   // an element of the Ordered does not occur in the List of Objects
753 	}
754 	/*
755 		There appears to be room for all elements of the Ordered. The editor window can appear. Install the editor in all objects.
756 	*/
757 	for (integer iOrderedObject = 1; iOrderedObject <= objects->size; iOrderedObject ++) {
758 		Daata object = objects->at [iOrderedObject];
759 		integer iPraatObject = 1;
760 		for (; iPraatObject <= theCurrentPraatObjects -> n; iPraatObject ++) {
761 			if (object == theCurrentPraatObjects -> list [iPraatObject]. object) {
762 				int ieditor = 0;
763 				for (; ieditor < praat_MAXNUM_EDITORS; ieditor ++) {
764 					if (! theCurrentPraatObjects -> list [iPraatObject]. editors [ieditor]) {
765 						theCurrentPraatObjects -> list [iPraatObject]. editors [ieditor] = editor;
766 						Editor_setDestructionCallback (editor, cb_Editor_destruction);
767 						Editor_setDataChangedCallback (editor, cb_Editor_dataChanged);
768 						if (! editor -> d_publicationCallback)
769 							Editor_setPublicationCallback (editor, cb_Editor_publication);
770 						break;
771 					}
772 				}
773 				Melder_assert (ieditor < praat_MAXNUM_EDITORS);   // we just checked, but nevertheless
774 				break;
775 			}
776 		}
777 		Melder_assert (iPraatObject <= theCurrentPraatObjects -> n);   // we already checked, but still
778 	}
779 }
780 
praat_dataChanged(Daata object)781 void praat_dataChanged (Daata object) {
782 	/*
783 		This function can be called at error time, which is weird.
784 	*/
785 	autostring32 saveError;
786 	const bool duringError = Melder_hasError ();
787 	if (duringError) {
788 		if (Melder_hasCrash ())
789 			return;   // straight to the exit, without attempting to notify any editors of any data, which might well be corrupted
790 		saveError = Melder_dup_f (Melder_getError ());
791 		Melder_clearError ();
792 	}
793 	integer IOBJECT;
794 	WHERE (OBJECT == object) {
795 		for (int ieditor = 0; ieditor < praat_MAXNUM_EDITORS; ieditor ++) {
796 			Editor editor = EDITOR [ieditor];
797 			if (editor) {
798 				Editor_dataChanged (editor);
799 				if (duringError)
800 					Melder_clearError ();   // accept only the original error, and not the extra ones generated in the editors
801 			}
802 		}
803 	}
804 	if (duringError) {
805 		Melder_appendError_noLine (saveError.get());   // restore the original error message
806 		/*
807 			If we are during error time, then this error should be caught
808 			either by `throw` (see praatM.h, two occurrences) or by Melder_flushError (also see praatM.h, one occurrence).
809 			LAST CHECKED 2021-12-02
810 		*/
811 	}
812 }
813 
helpProc(conststring32 query)814 static void helpProc (conststring32 query) {
815 	if (theCurrentPraatApplication -> batch) {
816 		Melder_flushError (U"Cannot view manual from batch.");
817 		return;
818 	}
819 	try {
820 		autoManual manual = Manual_create (query, theCurrentPraatApplication -> manPages, false);
821 		manual.releaseToUser();
822 	} catch (MelderError) {
823 		Melder_flushError (U"help: no help on \"", query, U"\".");
824 	}
825 }
826 
publishProc(autoDaata me)827 static int publishProc (autoDaata me) {
828 	try {
829 		praat_new (me.move(), U"");
830 		praat_updateSelection ();
831 		return 1;
832 	} catch (MelderError) {
833 		Melder_throw (U"Not published.");
834 	}
835 }
836 
837 /***** QUIT *****/
838 
839 FORM (DO_Quit, U"Confirm Quit", U"Quit") {
840 	MUTABLE_LABEL (label, U"You have objects in your list!")
841 	OK
842 {
843 	char32 prompt [300];
844 	if (ScriptEditors_dirty ()) {
845 		if (theCurrentPraatObjects -> n)
846 			Melder_sprint (prompt,300, U"You have objects and unsaved scripts! Do you still want to quit ", praatP.title.get(), U"?");
847 		else
848 			Melder_sprint (prompt,300, U"You have unsaved scripts! Do you still want to quit ", praatP.title.get(), U"?");
849 		SET_STRING (label, prompt)
850 	} else if (theCurrentPraatObjects -> n) {
851 		Melder_sprint (prompt,300, U"You have objects in your list! Do you still want to quit ", praatP.title.get(), U"?");
852 		SET_STRING (label, prompt)
853 	} else {
854 		praat_exit (0);
855 	}
856 }
857 DO
858 	praat_exit (0);
859 	END_NO_NEW_DATA
860 }
861 
gui_cb_quit(Thing)862 static void gui_cb_quit (Thing /* me */) {
863 	DO_Quit (nullptr, 0, nullptr, nullptr, nullptr, nullptr, false, nullptr);
864 }
865 
praat_dontUsePictureWindow()866 void praat_dontUsePictureWindow () { praatP.dontUsePictureWindow = true; }
867 
868 /********** INITIALIZATION OF THE PRAAT SHELL **********/
869 
870 #if defined (UNIX) && ! defined (NO_GUI)
871 	/*
872 		sendpraat messages can only enter via SIGUSR1 at interrupt time.
873 		We generate an event that should enter the main event loop at loop time.
874 	*/
cb_sigusr1(int signum)875 	static void cb_sigusr1 (int signum) {
876 		Melder_assert (signum == SIGUSR1);
877 		signal (SIGUSR1, cb_sigusr1);   // keep this handler in the air
878 		GdkEventProperty gevent;   // or GdkEventSetting, once we can find its signal name
879 		gevent. type = GDK_PROPERTY_NOTIFY;   // or GDK_SETTING
880 		gevent. window = gtk_widget_get_window (GTK_WIDGET (theCurrentPraatApplication -> topShell -> d_gtkWindow));
881 		gevent. send_event = 1;
882 		gevent. atom = gdk_atom_intern_static_string ("SENDPRAAT");
883 		gevent. time = 0;
884 		gevent. state = GDK_PROPERTY_NEW_VALUE;
885 		// Melder_casual (U"event put");
886 		gdk_event_put ((GdkEvent *) & gevent);   // this is safe only if gdk_event_put is reentrant, which is unlikely because the event queue is global
887 	}
888 #endif
889 
890 #if defined (UNIX)
891 	#if ! defined (NO_GUI)
cb_userMessage(GtkWidget,GdkEventProperty *,gpointer)892 		static gboolean cb_userMessage (GtkWidget /* widget */, GdkEventProperty * /* event */, gpointer /* userData */) {
893 			trace (U"client event called");
894 			autofile f;
895 			try {
896 				f.reset (Melder_fopen (& messageFile, "r"));
897 			} catch (MelderError) {
898 				Melder_clearError ();
899 				return true;   // OK
900 			}
901 			long_not_integer pid = 0;
902 			int narg = fscanf (f, "#%ld", & pid);
903 			f.close (& messageFile);
904 			{// scope
905 				autoPraatBackground background;
906 				try {
907 					praat_executeScriptFromFile (& messageFile, nullptr);
908 				} catch (MelderError) {
909 					Melder_flushError (praatP.title.get(), U": message not completely handled.");
910 				}
911 			}
912 			if (narg != 0 && pid != 0)
913 				kill (pid, SIGUSR2);
914 			return true;
915 		}
916 	#endif
917 #elif defined (_WIN32)
cb_userMessage()918 	static int cb_userMessage () {
919 		autoPraatBackground background;
920 		try {
921 			praat_executeScriptFromFile (& messageFile, nullptr);
922 		} catch (MelderError) {
923 			Melder_flushError (praatP.title.get(), U": message not completely handled.");
924 		}
925 		return 0;
926 	}
927 	extern "C" char *sendpraat (void *display, const char *programName, long timeOut, const char *text);
cb_openDocument(MelderFile file)928 	static void cb_openDocument (MelderFile file) {
929 		char32 text [kMelder_MAXPATH+25];
930 		/*
931 			The user dropped a file on the Praat icon,
932 			or double-clicked a Praat file,
933 			while Praat is already running.
934 		*/
935 		Melder_sprint (text,500, U"Read from file: ~", file -> path);
936 		sendpraat (nullptr, Melder_peek32to8 (praatP.title.get()), 0, Melder_peek32to8 (text));
937 	}
cb_finishedOpeningDocuments()938 	static void cb_finishedOpeningDocuments () {
939 		praat_updateSelection ();
940 	}
941 #elif macintosh
942 	static int (*theUserMessageCallback) (char32 *message);
mac_setUserMessageCallback(int (* userMessageCallback)(char32 * message))943 	static void mac_setUserMessageCallback (int (*userMessageCallback) (char32 *message)) {
944 		theUserMessageCallback = userMessageCallback;
945 	}
mac_processSignal8(const AppleEvent * theAppleEvent,AppleEvent *,long)946 	static pascal OSErr mac_processSignal8 (const AppleEvent *theAppleEvent, AppleEvent * /* reply */, long /* handlerRefCon */) {
947 		static bool duringAppleEvent = false;   // FIXME: may have to be atomic?
948 		if (! duringAppleEvent) {
949 			char *buffer;
950 			Size actualSize;
951 			duringAppleEvent = true;
952 			//AEInteractWithUser (kNoTimeOut, nullptr, nullptr);   // use time out of 0 to execute immediately (without bringing to foreground)
953 			ProcessSerialNumber psn;
954 			GetCurrentProcess (& psn);
955 			SetFrontProcess (& psn);
956 			AEGetParamPtr (theAppleEvent, 1, typeUTF8Text, nullptr, nullptr, 0, & actualSize);
957 			buffer = (char *) malloc ((size_t) actualSize);
958 			AEGetParamPtr (theAppleEvent, 1, typeUTF8Text, nullptr, & buffer [0], actualSize, nullptr);
959 			if (theUserMessageCallback) {
960 				autostring32 buffer32 = Melder_8to32 (buffer);
961 				theUserMessageCallback (buffer32.get());
962 			}
963 			free (buffer);
964 			duringAppleEvent = false;
965 		}
966 		return noErr;
967 	}
cb_userMessage(char32 * message)968 	static int cb_userMessage (char32 *message) {
969 		autoPraatBackground background;
970 		try {
971 			praat_executeScriptFromText (message);
972 		} catch (MelderError) {
973 			Melder_flushError (praatP.title.get(), U": message not completely handled.");
974 		}
975 		return 0;
976 	}
cb_quitApplication()977 	static int cb_quitApplication () {
978 		DO_Quit (nullptr, 0, nullptr, nullptr, nullptr, nullptr, false, nullptr);
979 		return 0;
980 	}
981 #endif
982 
983 static conststring32 thePraatStandAloneScriptText = nullptr;
984 
praat_setStandAloneScriptText(conststring32 text)985 void praat_setStandAloneScriptText (conststring32 text) {
986 	thePraatStandAloneScriptText = text;
987 }
988 
tryToAttachToTheCommandLine()989 static bool tryToAttachToTheCommandLine ()
990 {
991 	bool weHaveSucceeded = false;
992 	#if defined (_WIN32)
993 		/*
994 		 * On Windows, console applications are automatically attached to the command line,
995 		 * but Praat is always a Windows application instead, so command line attachment
996 		 * has to be handled explicitly, as here.
997 		 */
998 		if (AttachConsole (ATTACH_PARENT_PROCESS)) {   // was Praat called from either a console window or a "system" command?
999 			weHaveSucceeded = true;
1000 		}
1001 	#else
1002 		weHaveSucceeded = isatty (fileno (stdin)) || isatty (fileno (stdout)) || isatty (fileno (stderr));
1003 		/*
1004 			The result is `true` if Praat was called from a terminal window or some system() commands or Xcode,
1005 			and `false` if Praat was called from the Finder by double-clicking or dropping a file.
1006 
1007 			FIXME:
1008 			The result is incorrectly `false` if the output is redirected to a file or pipe.
1009 			A proposed improvement is therefore:
1010 				isatty (fileno (stdin)) || isatty (fileno (stdout)) || isatty (fileno (stderr))
1011 			This might be incorrectly false only if all three streams are redirected, but this hasn't been tested yet.
1012 		*/
1013 	#endif
1014 	return weHaveSucceeded;
1015 }
1016 
setThePraatLocale()1017 static void setThePraatLocale () {
1018 	#if defined (UNIX)
1019 		setlocale (LC_ALL, "C");
1020 		//setenv ("PULSE_LATENCY_MSEC", "1", 0);   // Rafael Laboissiere, August 2014
1021 	#elif defined (_WIN32)
1022 		setlocale (LC_ALL, "C");   // said to be superfluous
1023 	#elif defined (macintosh)
1024 		setlocale (LC_ALL, "en_US");   // required to make swprintf work correctly; the default "C" locale does not do that!
1025 	#endif
1026 }
1027 
installPraatShellPreferences()1028 static void installPraatShellPreferences () {
1029 	praat_statistics_prefs ();   // number of sessions, memory used...
1030 	praat_picture_prefs ();   // font...
1031 	Graphics_prefs ();
1032 	Ui_prefs ();
1033 	structEditor     :: f_preferences ();   // erase picture first...
1034 	structHyperPage  :: f_preferences ();   // font...
1035 	Site_prefs ();   // print command...
1036 	Melder_audio_prefs ();   // asynchronicity, silence after...
1037 	Melder_textEncoding_prefs ();
1038 	Printer_prefs ();   // paper size, printer command...
1039 	structTextEditor :: f_preferences ();   // font size...
1040 }
1041 
praatlib_init()1042 extern "C" void praatlib_init () {
1043 	setThePraatLocale ();   // FIXME: don't use the global locale
1044 	Melder_init ();
1045 	Melder_rememberShellDirectory ();
1046 	installPraatShellPreferences ();   // needed in the library, because this sets the defaults
1047 	praatP.argc = 0;
1048 	praatP.argv = nullptr;
1049 	praatP.argumentNumber = 1;
1050 	Melder_batch = true;
1051 	praatP.userWantsToOpen = false;
1052 	praatP.title = Melder_dup (U"Praatlib");
1053 	theCurrentPraatApplication -> batch = true;
1054 	Melder_getHomeDir (& homeDir);
1055 	praat_actions_init ();
1056 	praat_menuCommands_init ();
1057 	Thing_recognizeClassesByName (classCollection, classStrings, classManPages, classStringSet, nullptr);
1058 	Thing_recognizeClassByOtherName (classStringSet, U"SortedSetOfString");
1059 	Melder_backgrounding = true;
1060 	praat_addMenus (nullptr);
1061 	praat_addFixedButtons (nullptr);
1062 	praat_addMenus2 ();
1063 }
1064 
injectMessageAndInformationProcs(GuiWindow parent)1065 static void injectMessageAndInformationProcs (GuiWindow parent) {
1066 	Gui_injectMessageProcs (parent);
1067 	InfoEditor_injectInformationProc ();
1068 }
1069 
praat_init(conststring32 title,int argc,char ** argv)1070 void praat_init (conststring32 title, int argc, char **argv)
1071 {
1072 	bool weWereStartedFromTheCommandLine = tryToAttachToTheCommandLine ();
1073 
1074 	for (int iarg = 0; iarg < argc; iarg ++) {
1075 		//Melder_casual (U"arg ", iarg, U": <<", Melder_peek8to32 (argv [iarg]), U">>");
1076 	}
1077 	setThePraatLocale ();
1078 	Melder_init ();
1079 
1080 	/*
1081 		Remember the current directory. Useful only for scripts run from batch.
1082 	*/
1083 	Melder_rememberShellDirectory ();
1084 
1085 	installPraatShellPreferences ();
1086 
1087 	praatP.argc = argc;
1088 	praatP.argv = argv;
1089 	praatP.argumentNumber = 1;
1090 	autostring32 unknownCommandLineOption;
1091 
1092 	/*
1093 	 * Running Praat from the command line.
1094 	 */
1095 	bool foundTheOpenOption = false, foundTheRunOption = false, foundTheTraceOption = false;
1096 	while (praatP.argumentNumber < argc && argv [praatP.argumentNumber] [0] == '-') {
1097 		if (strequ (argv [praatP.argumentNumber], "-")) {
1098 			praatP.hasCommandLineInput = true;
1099 			praatP.argumentNumber += 1;
1100 		} else if (strequ (argv [praatP.argumentNumber], "--open")) {
1101 			foundTheOpenOption = true;
1102 			praatP.argumentNumber += 1;
1103 		} else if (strequ (argv [praatP.argumentNumber], "--run")) {
1104 			foundTheRunOption = true;
1105 			praatP.argumentNumber += 1;
1106 		} else if (strequ (argv [praatP.argumentNumber], "--no-pref-files")) {
1107 			praatP.ignorePreferenceFiles = true;
1108 			praatP.argumentNumber += 1;
1109 		} else if (strequ (argv [praatP.argumentNumber], "--no-plugins")) {
1110 			praatP.ignorePlugins = true;
1111 			praatP.argumentNumber += 1;
1112 		} else if (strnequ (argv [praatP.argumentNumber], "--pref-dir=", 11)) {
1113 			Melder_pathToDir (Melder_peek8to32 (argv [praatP.argumentNumber] + 11), & Melder_preferencesFolder);
1114 			praatP.argumentNumber += 1;
1115 		} else if (strequ (argv [praatP.argumentNumber], "--version")) {
1116 			#define xstr(s) str(s)
1117 			#define str(s) #s
1118 			Melder_information (title, U" " xstr (PRAAT_VERSION_STR) " (" xstr (PRAAT_MONTH) " ", PRAAT_DAY, U" ", PRAAT_YEAR, U")");
1119 			exit (0);
1120 		} else if (strequ (argv [praatP.argumentNumber], "--trace")) {
1121 			foundTheTraceOption = true;
1122 			praatP.argumentNumber += 1;
1123 		} else if (strequ (argv [praatP.argumentNumber], "--help")) {
1124 			MelderInfo_open ();
1125 			MelderInfo_writeLine (U"Usage: praat [options] script-file-name [script-arguments]");
1126 			MelderInfo_writeLine (U"Options:");
1127 			MelderInfo_writeLine (U"  --open           regard the command line as files to be opened in the GUI");
1128 			MelderInfo_writeLine (U"  --run            regard the command line as a script to run, with its arguments");
1129 			MelderInfo_writeLine (U"                   (--run is superfluous when you use a Console or Terminal)");
1130 			MelderInfo_writeLine (U"  --no-pref-files  don't read or write the preferences file and the buttons file");
1131 			MelderInfo_writeLine (U"  --no-plugins     don't activate the plugins");
1132 			MelderInfo_writeLine (U"  --pref-dir=DIR   set the preferences directory to DIR");
1133 			MelderInfo_writeLine (U"  --version        print the Praat version");
1134 			MelderInfo_writeLine (U"  --help           print this list of command line options");
1135 			MelderInfo_writeLine (U"  -u, --utf16      use UTF-16LE output encoding, no BOM (the default on Windows)");
1136 			MelderInfo_writeLine (U"  -8, --utf8       use UTF-8 output encoding (the default on MacOS and Linux)");
1137 			MelderInfo_writeLine (U"  -a, --ansi       use ISO Latin-1 output encoding (lossy, hence not recommended)");
1138 			MelderInfo_writeLine (U"                   (on Windows, use -8 or -a when you redirect to a pipe or file)");
1139 			MelderInfo_writeLine (U"  --trace          switch tracing on at start-up (see Praat > Technical > Debug)");
1140 			MelderInfo_close ();
1141 			exit (0);
1142 		} else if (strequ (argv [praatP.argumentNumber], "-8") || strequ (argv [praatP.argumentNumber], "--utf8")) {
1143 			MelderConsole::setEncoding (MelderConsole::Encoding::UTF8);
1144 			praatP.argumentNumber += 1;
1145 		} else if (strequ (argv [praatP.argumentNumber], "-u") || strequ (argv [praatP.argumentNumber], "--utf16")) {
1146 			MelderConsole::setEncoding (MelderConsole::Encoding::UTF16);
1147 			praatP.argumentNumber += 1;
1148 		} else if (strequ (argv [praatP.argumentNumber], "-a") || strequ (argv [praatP.argumentNumber], "--ansi")) {
1149 			MelderConsole::setEncoding (MelderConsole::Encoding::ANSI);
1150 			praatP.argumentNumber += 1;
1151 		#if defined (macintosh)
1152 		} else if (strequ (argv [praatP.argumentNumber], "-NSDocumentRevisionsDebugMode")) {
1153 			(void) 0;   // ignore this option, which was added by Xcode
1154 			praatP.argumentNumber += 2;   // jump over the argument, which is usually "YES" (this jump works correctly even if this argument is missing)
1155 		} else if (strnequ (argv [praatP.argumentNumber], "-psn_", 5)) {
1156 			(void) 0;   // ignore this option, which was added by the Finder, perhaps when dragging a file on Praat (Process Serial Number)
1157 			praatP.argumentNumber += 1;
1158 		#endif
1159 		} else if (strequ (argv [praatP.argumentNumber], "-sgi") ||
1160 			strequ (argv [praatP.argumentNumber], "-motif") ||
1161 			strequ (argv [praatP.argumentNumber], "-solaris") ||
1162 			strequ (argv [praatP.argumentNumber], "-hp") ||
1163 			strequ (argv [praatP.argumentNumber], "-sum4") ||
1164 			strequ (argv [praatP.argumentNumber], "-mac") ||
1165 			strequ (argv [praatP.argumentNumber], "-win32") ||
1166 			strequ (argv [praatP.argumentNumber], "-linux") ||
1167 			strequ (argv [praatP.argumentNumber], "-cocoa") ||
1168 			strequ (argv [praatP.argumentNumber], "-chrome")
1169 		) {
1170 			praatP.argumentNumber += 1;
1171 		} else {
1172 			unknownCommandLineOption = Melder_8to32 (argv [praatP.argumentNumber]);
1173 			praatP.argumentNumber = INT32_MAX;   // ignore all other command line options
1174 			break;
1175 		}
1176 	}
1177 	weWereStartedFromTheCommandLine |= foundTheRunOption;   // some external system()-like commands don't make isatty return true, so we have to help
1178 
1179 	const bool thereIsAFileNameInTheArgumentList = ( praatP.argumentNumber < argc );
1180 	Melder_batch = weWereStartedFromTheCommandLine && thereIsAFileNameInTheArgumentList && ! foundTheOpenOption;
1181 	const bool fileNamesCameInByDropping = ( thereIsAFileNameInTheArgumentList && ! weWereStartedFromTheCommandLine );   // doesn't happen on the Mac
1182 	praatP.userWantsToOpen = foundTheOpenOption || fileNamesCameInByDropping;
1183 
1184 	if (Melder_batch) {
1185 		Melder_assert (praatP.argumentNumber < argc);
1186 		/*
1187 		 * We now get the script file name. It is next on the command line
1188 		 * (not necessarily *last* on the line, because there may be script arguments after it).
1189 		 */
1190 		MelderString_copy (& theCurrentPraatApplication -> batchName, Melder_peek8to32 (argv [praatP.argumentNumber ++]));
1191 		if (praatP.hasCommandLineInput)
1192 			Melder_throw (U"Cannot have both command line input and a script file.");
1193 	} else {
1194 		MelderString_copy (& theCurrentPraatApplication -> batchName, U"");
1195 	}
1196 	//Melder_casual (U"Script file name <<", theCurrentPraatApplication -> batchName.string, U">>");
1197 
1198 	Melder_batch |= !! thePraatStandAloneScriptText;
1199 
1200 	/*
1201 	 * Running the Praat shell from the command line:
1202 	 *    praat -
1203 	 */
1204 	Melder_batch |= praatP.hasCommandLineInput;
1205 
1206 	praatP.title = Melder_dup (title && title [0] != U'\0' ? title : U"Praat");
1207 
1208 	theCurrentPraatApplication -> batch = Melder_batch;
1209 
1210 	/*
1211 	 * Construct a program name like "Praat" for file and directory names.
1212 	 */
1213 	str32cpy (programName, praatP.title.get());
1214 
1215 	/*
1216 	 * Construct a main-window title like "Praat 6.1".
1217 	 */
1218 	programName [0] = Melder_toLowerCase (programName [0]);
1219 
1220 	/*
1221 	 * Get home directory, e.g. "/home/miep/", or "/Users/miep/", or just "/".
1222 	 */
1223 	Melder_getHomeDir (& homeDir);
1224 
1225 	/*
1226 	 * Get the program's private directory (if not yet set by the --pref-dir option):
1227 	 *    "/home/miep/.praat-dir" (Unix)
1228 	 *    "/Users/miep/Library/Preferences/Praat Prefs" (MacOS)
1229 	 *    "C:\Users\Miep\Praat" (Windows)
1230 	 * and construct a preferences-file name and a script-buttons-file name like
1231 	 *    /home/miep/.praat-dir/prefs5
1232 	 *    /home/miep/.praat-dir/buttons5
1233 	 * or
1234 	 *    /Users/miep/Library/Preferences/Praat Prefs/Prefs5
1235 	 *    /Users/miep/Library/Preferences/Praat Prefs/Buttons5
1236 	 * or
1237 	 *    C:\Users\Miep\Praat\Preferences5.ini
1238 	 *    C:\Users\Miep\Praat\Buttons5.ini
1239 	 * Also create names for message and tracing files.
1240 	 */
1241 	if (MelderDir_isNull (& Melder_preferencesFolder)) {   // not yet set by the --pref-dir option?
1242 		structMelderDir prefParentDir { };   // directory under which to store our preferences directory
1243 		Melder_getPrefDir (& prefParentDir);
1244 
1245 		/*
1246 		 * Make sure that the program's private directory exists.
1247 		 */
1248 		char32 name [256];
1249 		#if defined (UNIX)
1250 			Melder_sprint (name,256, U".", programName, U"-dir");   // for example .praat-dir
1251 		#elif defined (macintosh)
1252 			Melder_sprint (name,256, praatP.title.get(), U" Prefs");   // for example Praat Prefs
1253 		#elif defined (_WIN32)
1254 			Melder_sprint (name,256, praatP.title.get());   // for example Praat
1255 		#endif
1256 		try {
1257 			#if defined (UNIX) || defined (macintosh)
1258 				Melder_createDirectory (& prefParentDir, name, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
1259 			#else
1260 				Melder_createDirectory (& prefParentDir, name, 0);
1261 			#endif
1262 			MelderDir_getSubdir (& prefParentDir, name, & Melder_preferencesFolder);
1263 		} catch (MelderError) {
1264 			/*
1265 			 * If we arrive here, the directory could not be created,
1266 			 * and all the files are null. Praat should nevertheless start up.
1267 			 */
1268 			Melder_clearError ();
1269 		}
1270 	}
1271 	if (! MelderDir_isNull (& Melder_preferencesFolder)) {
1272 		#if defined (UNIX)
1273 			MelderDir_getFile (& Melder_preferencesFolder, U"prefs5", & prefsFile);
1274 			MelderDir_getFile (& Melder_preferencesFolder, U"buttons5", & buttonsFile);
1275 			MelderDir_getFile (& Melder_preferencesFolder, U"pid", & pidFile);
1276 			MelderDir_getFile (& Melder_preferencesFolder, U"message", & messageFile);
1277 			MelderDir_getFile (& Melder_preferencesFolder, U"tracing", & tracingFile);
1278 		#elif defined (_WIN32)
1279 			MelderDir_getFile (& Melder_preferencesFolder, U"Preferences5.ini", & prefsFile);
1280 			MelderDir_getFile (& Melder_preferencesFolder, U"Buttons5.ini", & buttonsFile);
1281 			MelderDir_getFile (& Melder_preferencesFolder, U"Message.txt", & messageFile);
1282 			MelderDir_getFile (& Melder_preferencesFolder, U"Tracing.txt", & tracingFile);
1283 		#elif defined (macintosh)
1284 			MelderDir_getFile (& Melder_preferencesFolder, U"Prefs5", & prefsFile);
1285 			MelderDir_getFile (& Melder_preferencesFolder, U"Buttons5", & buttonsFile);
1286 			MelderDir_getFile (& Melder_preferencesFolder, U"Tracing.txt", & tracingFile);
1287 		#endif
1288 		Melder_tracingToFile (& tracingFile);
1289 		if (foundTheTraceOption)
1290 			Melder_setTracing (true);
1291 	}
1292 
1293 	#if defined (NO_GUI)
1294 		if (! Melder_batch) {
1295 			fprintf (stderr, "A no-GUI edition of Praat cannot be used interactively. "
1296 				"Supply \"--run\" and a script file name on the command line.\n");
1297 			exit (1);
1298 		}
1299 	#endif
1300 	#if defined (UNIX)
1301 		if (! Melder_batch) {
1302 			/*
1303 			 * Make sure that the directory /home/miep/.praat-dir exists,
1304 			 * and write our process id into the pid file.
1305 			 * Messages from "sendpraat" are caught very early this way,
1306 			 * though they will be responded to much later.
1307 			 */
1308 			try {
1309 				autofile f = Melder_fopen (& pidFile, "w");
1310 				fprintf (f, "%s", Melder8_integer (getpid ()));
1311 				f.close (& pidFile);
1312 			} catch (MelderError) {
1313 				Melder_clearError ();
1314 			}
1315 		}
1316 	#elif defined (_WIN32)
1317 		if (! Melder_batch)
1318 			motif_win_setUserMessageCallback (cb_userMessage);
1319 	#elif defined (macintosh)
1320 		if (! Melder_batch) {
1321 			mac_setUserMessageCallback (cb_userMessage);
1322 			Gui_setQuitApplicationCallback (cb_quitApplication);   // BUG: the Quit Application message (from the system) is never actually handled
1323 		}
1324 	#endif
1325 
1326 	/*
1327 	 * Make room for commands.
1328 	 */
1329 	trace (U"initing actions");
1330 	praat_actions_init ();
1331 	trace (U"initing menu commands");
1332 	praat_menuCommands_init ();
1333 
1334 	GuiWindow raam = nullptr;
1335 	if (Melder_batch) {
1336 		MelderString_empty (& theCurrentPraatApplication -> batchName);
1337 		for (int i = praatP.argumentNumber - 1; i < argc; i ++) {
1338 			if (i >= praatP.argumentNumber)
1339 				MelderString_append (& theCurrentPraatApplication -> batchName, U" ");
1340 			bool needsQuoting = !! strchr (argv [i], ' ') && (i == praatP.argumentNumber - 1 || i < argc - 1);
1341 			if (needsQuoting)
1342 				MelderString_append (& theCurrentPraatApplication -> batchName, U"\"");
1343 			MelderString_append (& theCurrentPraatApplication -> batchName, Melder_peek8to32 (argv [i]));
1344 			if (needsQuoting)
1345 				MelderString_append (& theCurrentPraatApplication -> batchName, U"\"");
1346 		}
1347 	} else {
1348 		trace (U"starting the application");
1349 		Machine_initLookAndFeel (argc, argv);
1350 		/*
1351 		 * Start the application.
1352 		 */
1353 		#if gtk
1354 			trace (U"locale ", Melder_peek8to32 (setlocale (LC_ALL, nullptr)));
1355 			g_set_application_name (Melder_peek32to8 (title));
1356 			trace (U"locale ", Melder_peek8to32 (setlocale (LC_ALL, nullptr)));
1357 		#elif motif
1358 			argv [0] = Melder_32to8 (praatP. title.get()).transfer();   // argc == 4
1359 			Gui_setOpenDocumentCallback (cb_openDocument, cb_finishedOpeningDocuments);
1360 			GuiAppInitialize ("Praatwulg", argc, argv);
1361 		#elif cocoa
1362 			//[NSApplication sharedApplication];
1363 			NSApplication *theApp = [GuiCocoaApplication sharedApplication];
1364 			/*
1365 				We want to get rid of the Search field in the help menu.
1366 				By default, such a Search field will come up automatically when we create a menu with the title "Help".
1367 				By changing this title to "SomeFakeTitleOfAMenuThatWillNeverBeInstantiated",
1368 				we trick macOS into thinking that our help menu is called "SomeFakeTitleOfAMenuThatWillNeverBeInstantiated".
1369 				As a result, the Search field will come up only when we create a menu
1370 				titled "SomeFakeTitleOfAMenuThatWillNeverBeInstantiated", which is never.
1371 			*/
1372 			theApp.helpMenu = [[NSMenu alloc] initWithTitle:@"SomeFakeTitleOfAMenuThatWillNeverBeInstantiated"];
1373 		#endif
1374 
1375 		trace (U"creating and installing the Objects window");
1376 		char32 objectWindowTitle [100];
1377 		Melder_sprint (objectWindowTitle,100, praatP.title.get(), U" Objects");
1378 		double x, y;
1379 		trace (U"locale ", Melder_peek8to32 (setlocale (LC_ALL, nullptr)));
1380 		Gui_getWindowPositioningBounds (& x, & y, nullptr, nullptr);
1381 		trace (U"locale ", Melder_peek8to32 (setlocale (LC_ALL, nullptr)));
1382 		theCurrentPraatApplication -> topShell = raam =
1383 			GuiWindow_create (x + 10, y, WINDOW_WIDTH, WINDOW_HEIGHT, 450, 250,
1384 				objectWindowTitle, gui_cb_quit, nullptr, 0);
1385 		trace (U"locale ", Melder_peek8to32 (setlocale (LC_ALL, nullptr)));
1386 		#if motif
1387 			GuiApp_setApplicationShell (theCurrentPraatApplication -> topShell -> d_xmShell);
1388 		#endif
1389 		trace (U"before objects window shows locale ", Melder_peek8to32 (setlocale (LC_ALL, nullptr)));
1390 		GuiThing_show (raam);
1391 		trace (U"after objects window shows locale ", Melder_peek8to32 (setlocale (LC_ALL, nullptr)));
1392 	}
1393 	Thing_recognizeClassesByName (classCollection, classStrings, classManPages, classStringSet, nullptr);
1394 	Thing_recognizeClassByOtherName (classStringSet, U"SortedSetOfString");
1395 	if (Melder_batch) {
1396 		Melder_backgrounding = true;
1397 		trace (U"adding menus without GUI");
1398 		praat_addMenus (nullptr);
1399 		trace (U"adding fixed buttons without GUI");
1400 		praat_addFixedButtons (nullptr);
1401 	} else {
1402 
1403 		#ifdef macintosh
1404 			AEInstallEventHandler (758934755, 0, (AEEventHandlerProcPtr) (mac_processSignal8), 0, false);   // for receiving sendpraat
1405 			injectMessageAndInformationProcs (raam);   // BUG: default Melder_assert would call printf recursively!!!
1406 		#endif
1407 		trace (U"creating the menu bar in the Objects window");
1408 		GuiWindow_addMenuBar (raam);
1409 		praatP.menuBar = raam;
1410 		praat_addMenus (praatP.menuBar);
1411 
1412 		trace (U"creating the object list in the Objects window");
1413 		GuiLabel_createShown (raam, 3, -250, Machine_getMenuBarBottom () + 5, Machine_getMenuBarBottom () + 5 + Gui_LABEL_HEIGHT, U"Objects:", 0);
1414 		praatList_objects = GuiList_create (raam, 0, -250, Machine_getMenuBarBottom () + 26, -100, true, U" Objects ");
1415 		GuiList_setSelectionChangedCallback (praatList_objects, gui_cb_list_selectionChanged, nullptr);
1416 		GuiThing_show (praatList_objects);
1417 		praat_addFixedButtons (raam);
1418 
1419 		trace (U"creating the dynamic menu in the Objects window");
1420 		praat_actions_createDynamicMenu (raam);
1421 		trace (U"showing the Objects window");
1422 		GuiThing_show (raam);
1423 		#ifdef UNIX
1424 			if (! praatP.ignorePreferenceFiles)
1425 				Preferences_read (& prefsFile);
1426 		#endif
1427 		#if ! defined (macintosh)
1428 			trace (U"initializing the Gui late");
1429 			injectMessageAndInformationProcs (theCurrentPraatApplication -> topShell);   // Mac: done this earlier
1430 		#endif
1431 		Melder_setHelpProc (helpProc);
1432 	}
1433 	Data_setPublishProc (publishProc);
1434 	theCurrentPraatApplication -> manPages = ManPages_create ().releaseToAmbiguousOwner();
1435 
1436 	trace (U"creating the Picture window");
1437 	trace (U"before picture window shows: locale is ", Melder_peek8to32 (setlocale (LC_ALL, nullptr)));
1438 	if (! praatP.dontUsePictureWindow)
1439 		praat_picture_init ();
1440 	trace (U"after picture window shows: locale is ", Melder_peek8to32 (setlocale (LC_ALL, nullptr)));
1441 
1442 	if (unknownCommandLineOption)
1443 		Melder_fatal (U"Unrecognized command line option ", unknownCommandLineOption.get());
1444 }
1445 
executeStartUpFile(MelderDir startUpDirectory,conststring32 fileNameHead,conststring32 fileNameTail)1446 static void executeStartUpFile (MelderDir startUpDirectory, conststring32 fileNameHead, conststring32 fileNameTail) {
1447 	char32 name [256];
1448 	Melder_sprint (name,256, fileNameHead, programName, fileNameTail);
1449 	if (! MelderDir_isNull (startUpDirectory)) {   // should not occur on modern systems
1450 		structMelderFile startUp { };
1451 		MelderDir_getFile (startUpDirectory, name, & startUp);
1452 		if (! MelderFile_readable (& startUp))
1453 			return;   // it's OK if the file doesn't exist
1454 		try {
1455 			praat_executeScriptFromFile (& startUp, nullptr);
1456 		} catch (MelderError) {
1457 			Melder_flushError (praatP.title.get(), U": start-up file ", & startUp, U" not completed.");
1458 		}
1459 	}
1460 }
1461 
1462 #if gtk
1463 	#include <gdk/gdkkeysyms.h>
theKeySnooper(GtkWidget * widget,GdkEventKey * event,gpointer data)1464 	static gint theKeySnooper (GtkWidget *widget, GdkEventKey *event, gpointer data) {
1465 		trace (U"keyval ", event -> keyval, U", type ", event -> type);
1466 		if ((event -> keyval == GDK_KEY_Tab || event -> keyval == GDK_KEY_ISO_Left_Tab) && event -> type == GDK_KEY_PRESS) {
1467 			using TabCallback = void (*) (GuiObject, gpointer);
1468 			trace (U"tab key pressed in window ", Melder_pointer (widget));
1469 			constexpr bool theTabKeyShouldWorkEvenIfNumLockIsOn = true;
1470 			constexpr uint32 theProbableNumLockModifierMask = GDK_MOD2_MASK;
1471 			constexpr uint32 modifiersToIgnore = ( theTabKeyShouldWorkEvenIfNumLockIsOn ? theProbableNumLockModifierMask : 0 );
1472 			constexpr uint32 modifiersNotToIgnore = GDK_MODIFIER_MASK & ~ modifiersToIgnore;
1473 			if ((event -> state & modifiersNotToIgnore) == 0) {
1474 				if (GTK_IS_WINDOW (widget)) {
1475 					GtkWidget *shell = gtk_widget_get_toplevel (GTK_WIDGET (widget));
1476 					trace (U"tab pressed in GTK window ", Melder_pointer (shell));
1477 					TabCallback tabCallback = (TabCallback) g_object_get_data (G_OBJECT (widget), "tabCallback");
1478 					if (tabCallback) {
1479 						trace (U"a tab callback exists");
1480 						void *tabClosure = g_object_get_data (G_OBJECT (widget), "tabClosure");
1481 						tabCallback (widget, tabClosure);
1482 						return true;
1483 					}
1484 				}
1485 			} else if ((event -> state & modifiersNotToIgnore) == GDK_SHIFT_MASK) {
1486 				if (GTK_IS_WINDOW (widget)) {
1487 					GtkWidget *shell = gtk_widget_get_toplevel (GTK_WIDGET (widget));
1488 					trace (U"shift-tab pressed in GTK window ", Melder_pointer (shell));
1489 					TabCallback tabCallback = (TabCallback) g_object_get_data (G_OBJECT (widget), "shiftTabCallback");
1490 					if (tabCallback) {
1491 						trace (U"a shift tab callback exists");
1492 						void *tabClosure = g_object_get_data (G_OBJECT (widget), "shiftTabClosure");
1493 						tabCallback (widget, tabClosure);
1494 						return true;
1495 					}
1496 				}
1497 			}
1498 		}
1499 		trace (U"end");
1500 		return false;   // pass event on
1501 	}
1502 #endif
1503 
praat_run()1504 void praat_run () {
1505 	trace (U"adding menus, second round");
1506 	praat_addMenus2 ();
1507 	trace (U"locale is ", Melder_peek8to32 (setlocale (LC_ALL, nullptr)));
1508 
1509 	trace (U"adding the Quit command");
1510 	praat_addMenuCommand (U"Objects", U"Praat", U"-- quit --", nullptr, 0, nullptr);
1511 	praat_addMenuCommand (U"Objects", U"Praat", U"Quit", nullptr, praat_UNHIDABLE | 'Q' | praat_NO_API, DO_Quit);
1512 
1513 	trace (U"read the preferences file, and notify those who want to be notified of this");
1514 	/* ...namely, those who already have a window (namely, the Picture window),
1515 	 * and those that regard the start of a new session as a meaningful event
1516 	 * (namely, the session counter and the cross-session memory counter).
1517 	 */
1518 	if (! praatP.ignorePreferenceFiles) {
1519 		Preferences_read (& prefsFile);
1520 		if (! praatP.dontUsePictureWindow)
1521 			praat_picture_prefsChanged ();
1522 		praat_statistics_prefsChanged ();
1523 	}
1524 
1525 	praatP.phase = praat_STARTING_UP;
1526 
1527 	trace (U"execute start-up file(s)");
1528 	/*
1529 	 * On Unix and the Mac, we try no less than three start-up file names.
1530 	 */
1531 	#if defined (UNIX) || defined (macintosh)
1532 		structMelderDir usrLocal { };
1533 		Melder_pathToDir (U"/usr/local", & usrLocal);
1534 		executeStartUpFile (& usrLocal, U"", U"-startUp");
1535 	#endif
1536 	#if defined (UNIX) || defined (macintosh)
1537 		executeStartUpFile (& homeDir, U".", U"-user-startUp");   // not on Windows (empty file name error)
1538 	#endif
1539 	#if defined (UNIX) || defined (macintosh) || defined (_WIN32)
1540 		executeStartUpFile (& homeDir, U"", U"-user-startUp");
1541 	#endif
1542 
1543 	if (! MelderDir_isNull (& Melder_preferencesFolder) && ! praatP.ignorePlugins) {
1544 		trace (U"install plug-ins");
1545 		trace (U"locale is ", Melder_peek8to32 (setlocale (LC_ALL, nullptr)));
1546 		/* The Praat phase should remain praat_STARTING_UP,
1547 		 * because any added commands must not be included in the buttons file.
1548 		 */
1549 		structMelderFile searchPattern { };
1550 		MelderDir_getFile (& Melder_preferencesFolder, U"plugin_*", & searchPattern);
1551 		try {
1552 			autoSTRVEC folderNames = folderNames_STRVEC (Melder_fileToPath (& searchPattern));
1553 			for (integer i = 1; i <= folderNames.size; i ++) {
1554 				structMelderDir pluginDir { };
1555 				structMelderFile plugin { };
1556 				MelderDir_getSubdir (& Melder_preferencesFolder, folderNames [i].get(), & pluginDir);
1557 				MelderDir_getFile (& pluginDir, U"setup.praat", & plugin);
1558 				if (MelderFile_readable (& plugin)) {
1559 					Melder_backgrounding = true;
1560 					try {
1561 						praat_executeScriptFromFile (& plugin, nullptr);
1562 					} catch (MelderError) {
1563 						Melder_flushError (praatP.title.get(), U": plugin ", & plugin, U" contains an error.");
1564 					}
1565 					Melder_backgrounding = false;
1566 				}
1567 			}
1568 		} catch (MelderError) {
1569 			Melder_clearError ();   // in case Strings_createAsDirectoryList () threw an error
1570 		}
1571 	}
1572 
1573 	/*
1574 		Check the locale, to ensure identical behaviour on all computers.
1575 	*/
1576 	Melder_assert (str32equ (Melder_double (1.5), U"1.5"));   // check locale settings; because of the required file portability Praat cannot stand "1,5"
1577 	Melder_assert (Melder_isHorizontalOrVerticalSpace (' '));
1578 	Melder_assert (Melder_isHorizontalOrVerticalSpace ('\r'));
1579 	Melder_assert (Melder_isHorizontalOrVerticalSpace ('\n'));
1580 	Melder_assert (Melder_isHorizontalOrVerticalSpace ('\t'));
1581 	Melder_assert (Melder_isHorizontalOrVerticalSpace ('\f'));
1582 	Melder_assert (Melder_isHorizontalOrVerticalSpace ('\v'));
1583 
1584 	{
1585 		double *a = nullptr;
1586 		double *b = & a [0];
1587 		Melder_assert (! b);
1588 	}
1589 
1590 	/*
1591 		According to ISO 30112, a non-breaking space is not a space.
1592 		We do not agree, as long as spaces are assumed to be word breakers:
1593 		non-breaking spaces are used to prevent line breaks, not to prevent word breaks.
1594 		For instance, in English it's possible to insert a non-breaking space
1595 		after "e.g." in "...take drastic measures, e.g. pose a ban on...",
1596 		just because otherwise a line would end in a period that does not signal end of sentence;
1597 		this use does not mean that "e.g." and "pose" aren't separate words.
1598 	*/
1599 	Melder_assert (Melder_isHorizontalOrVerticalSpace (UNICODE_NO_BREAK_SPACE));
1600 
1601 	Melder_assert (Melder_isHorizontalOrVerticalSpace (UNICODE_OGHAM_SPACE_MARK));   // ISO 30112
1602 
1603 	/*
1604 		According to ISO 30112, a Mongolian vowel separator is a space.
1605 		However, this character is used to separate vowels *within* a word,
1606 		so it should not be a word breaker.
1607 		This means that as long as all spaces are assumed to be word breakers,
1608 		this character cannot be a space.
1609 	*/
1610 	Melder_assert (! Melder_isHorizontalOrVerticalSpace (UNICODE_MONGOLIAN_VOWEL_SEPARATOR));
1611 
1612 	Melder_assert (Melder_isHorizontalOrVerticalSpace (UNICODE_EN_QUAD));   // ISO 30112
1613 	Melder_assert (Melder_isHorizontalOrVerticalSpace (UNICODE_EM_QUAD));   // ISO 30112
1614 	Melder_assert (Melder_isHorizontalOrVerticalSpace (UNICODE_EN_SPACE));   // ISO 30112
1615 	Melder_assert (Melder_isHorizontalOrVerticalSpace (UNICODE_EM_SPACE));   // ISO 30112
1616 	Melder_assert (Melder_isHorizontalOrVerticalSpace (UNICODE_THREE_PER_EM_SPACE));   // ISO 30112
1617 	Melder_assert (Melder_isHorizontalOrVerticalSpace (UNICODE_FOUR_PER_EM_SPACE));   // ISO 30112
1618 	Melder_assert (Melder_isHorizontalOrVerticalSpace (UNICODE_SIX_PER_EM_SPACE));   // ISO 30112
1619 	Melder_assert (Melder_isHorizontalOrVerticalSpace (UNICODE_FIGURE_SPACE));   // questionable
1620 	Melder_assert (Melder_isHorizontalOrVerticalSpace (UNICODE_PUNCTUATION_SPACE));   // ISO 30112
1621 	Melder_assert (Melder_isHorizontalOrVerticalSpace (UNICODE_THIN_SPACE));   // ISO 30112
1622 	Melder_assert (Melder_isHorizontalOrVerticalSpace (UNICODE_HAIR_SPACE));   // ISO 30112
1623 	Melder_assert (! Melder_isHorizontalOrVerticalSpace (UNICODE_ZERO_WIDTH_SPACE));   // questionable
1624 	Melder_assert (! Melder_isHorizontalOrVerticalSpace (UNICODE_ZERO_WIDTH_NON_JOINER));
1625 	Melder_assert (! Melder_isHorizontalOrVerticalSpace (UNICODE_ZERO_WIDTH_JOINER));
1626 	Melder_assert (! Melder_isHorizontalOrVerticalSpace (UNICODE_ZERO_WIDTH_NO_BREAK_SPACE));   // this is the byte-order mark!
1627 	//Melder_assert (iswspace (UNICODE_LEFT_TO_RIGHT_MARK));
1628 	//Melder_assert (iswspace (UNICODE_RIGHT_TO_LEFT_MARK));
1629 	Melder_assert (Melder_isHorizontalOrVerticalSpace (UNICODE_LINE_SEPARATOR));   // ISO 30112
1630 	Melder_assert (Melder_isHorizontalOrVerticalSpace (UNICODE_PARAGRAPH_SEPARATOR));   // ISO 30112
1631 	Melder_assert (Melder_isHorizontalOrVerticalSpace (UNICODE_NARROW_NO_BREAK_SPACE));
1632 	Melder_assert (! Melder_isHorizontalOrVerticalSpace (UNICODE_LEFT_TO_RIGHT_EMBEDDING));
1633 	Melder_assert (! Melder_isHorizontalOrVerticalSpace (UNICODE_RIGHT_TO_LEFT_EMBEDDING));
1634 	Melder_assert (! Melder_isHorizontalOrVerticalSpace (UNICODE_POP_DIRECTIONAL_FORMATTING));
1635 	Melder_assert (! Melder_isHorizontalOrVerticalSpace (UNICODE_LEFT_TO_RIGHT_OVERRIDE));
1636 	Melder_assert (! Melder_isHorizontalOrVerticalSpace (UNICODE_RIGHT_TO_LEFT_OVERRIDE));
1637 	Melder_assert (Melder_isHorizontalOrVerticalSpace (UNICODE_MEDIUM_MATHEMATICAL_SPACE));   // ISO 30112
1638 	Melder_assert (! Melder_isHorizontalOrVerticalSpace (UNICODE_WORD_JOINER));
1639 	Melder_assert (! Melder_isHorizontalOrVerticalSpace (UNICODE_FUNCTION_APPLICATION));
1640 	Melder_assert (! Melder_isHorizontalOrVerticalSpace (UNICODE_INVISIBLE_TIMES));
1641 	Melder_assert (! Melder_isHorizontalOrVerticalSpace (UNICODE_INVISIBLE_SEPARATOR));
1642 	Melder_assert (! Melder_isHorizontalOrVerticalSpace (UNICODE_INHIBIT_SYMMETRIC_SWAPPING));
1643 	Melder_assert (! Melder_isHorizontalOrVerticalSpace (UNICODE_ACTIVATE_SYMMETRIC_SWAPPING));
1644 	Melder_assert (! Melder_isHorizontalOrVerticalSpace (UNICODE_INHIBIT_ARABIC_FORM_SHAPING));
1645 	Melder_assert (! Melder_isHorizontalOrVerticalSpace (UNICODE_ACTIVATE_ARABIC_FORM_SHAPING));
1646 	Melder_assert (! Melder_isHorizontalOrVerticalSpace (UNICODE_NATIONAL_DIGIT_SHAPES));
1647 	Melder_assert (! Melder_isHorizontalOrVerticalSpace (UNICODE_NOMINAL_DIGIT_SHAPES));
1648 	Melder_assert (Melder_isHorizontalOrVerticalSpace (UNICODE_IDEOGRAPHIC_SPACE));   // ISO 30112; occurs on Japanese computers
1649 
1650 	{ unsigned char dummy = 200;
1651 		Melder_assert ((int) dummy == 200);
1652 	}
1653 	Melder_assert (integer_abs (-1000) == integer (1000));
1654 	{ int dummy = 200;
1655 		Melder_assert ((int) (signed char) dummy == -56);   // bingeti8 relies on this
1656 		Melder_assert ((int) (unsigned char) dummy == 200);
1657 		Melder_assert ((double) dummy == 200.0);
1658 		Melder_assert ((double) (signed char) dummy == -56.0);
1659 		Melder_assert ((double) (unsigned char) dummy == 200.0);
1660 	}
1661 	{ int64 dummy = 200;
1662 		Melder_assert ((int) (signed char) dummy == -56);
1663 		Melder_assert ((int) (unsigned char) dummy == 200);
1664 		Melder_assert ((double) dummy == 200.0);
1665 		Melder_assert ((double) (signed char) dummy == -56.0);
1666 		Melder_assert ((double) (unsigned char) dummy == 200.0);
1667 	}
1668 	{ uint16 dummy = 40000;
1669 		Melder_assert ((int) (int16) dummy == -25536);   // bingeti16 relies on this
1670 		Melder_assert ((short) (int16) dummy == -25536);   // bingete16 relies on this
1671 		Melder_assert ((integer) dummy == 40000);   // Melder_integer relies on this
1672 		Melder_assert ((double) dummy == 40000.0);
1673 		Melder_assert ((double) (int16) dummy == -25536.0);
1674 	}
1675 	{ unsigned int dummy = 40000;
1676 		Melder_assert ((int) (int16) dummy == -25536);
1677 		Melder_assert ((short) (int16) dummy == -25536);
1678 		Melder_assert ((integer) dummy == 40000);   // Melder_integer relies on this
1679 		Melder_assert ((double) dummy == 40000.0);
1680 		Melder_assert ((double) (int16) dummy == -25536.0);
1681 	}
1682 	{
1683 		int64 dummy = 1000000000000;
1684 		if (! str32equ (Melder_integer (dummy), U"1000000000000"))
1685 			Melder_fatal (U"The number 1000000000000 is mistakenly written on this machine as ", dummy, U".");
1686 	}
1687 	{ uint32 dummy = 0xffffffff;
1688 		Melder_assert ((int64) dummy == 4294967295LL);
1689 		Melder_assert (str32equ (Melder_integer (dummy), U"4294967295"));
1690 		Melder_assert (double (dummy) == 4294967295.0);
1691 	}
1692 	{ double dummy = 3000000000.0;
1693 		Melder_assert ((uint32) dummy == 3000000000);
1694 	}
1695 	{
1696 		Melder_assert (str32len (U"hello") == 5);
1697 		Melder_assert (str32ncmp (U"hellogoodbye", U"hellogee", 6) == 0);
1698 		Melder_assert (str32ncmp (U"hellogoodbye", U"hellogee", 7) > 0);
1699 		Melder_assert (str32str (U"hellogoodbye", U"ogo"));
1700 		Melder_assert (! str32str (U"hellogoodbye", U"oygo"));
1701 	}
1702 	Melder_assert (isundef (undefined));
1703 	Melder_assert (std::isinf (1.0 / 0.0));
1704 	Melder_assert (std::isnan (0.0 / 0.0));
1705 	{
1706 		double x = sqrt (-10.0);
1707 		//if (! std::isnan (x)) printf ("sqrt (-10.0) = %g\n", x);   // -10.0 on Windows
1708 		x = NUMsqrt (-10.0);
1709 		Melder_assert (isundef (x));
1710 	}
1711 	Melder_assert (isdefined (0.0));
1712 	Melder_assert (isdefined (1e300));
1713 	Melder_assert (isundef (double (1e160 * 1e160)));
1714 	Melder_assert (isundef (pow (10.0, 330)));
1715 	Melder_assert (isundef (0.0 / 0.0));
1716 	Melder_assert (isundef (1.0 / 0.0));
1717 	Melder_assert (undefined != undefined);
1718 	Melder_assert (undefined - undefined != 0.0);
1719 	Melder_assert ((1.0/0.0) == (1.0/0.0));
1720 	Melder_assert ((1.0/0.0) - (1.0/0.0) != 0.0);
1721 	{
1722 		/*
1723 			Assumptions made in abcio.cpp:
1724 			`frexp()` returns an infinity if its argument is an infinity,
1725 			and not-a-number if its argument is not-a-number.
1726 		*/
1727 		int exponent;
1728 		Melder_assert (isundef (frexp (HUGE_VAL, & exponent)));
1729 		Melder_assert (isundef (frexp (0.0/0.0, & exponent)));
1730 		Melder_assert (isundef (frexp (undefined, & exponent)));
1731 		/*
1732 			The following relies on the facts that:
1733 			- positive infinity is not less than 1.0 (because it is greater than 1.0)
1734 			- NaN is not less than 1.0 (because it is not ordered)
1735 
1736 			Note: we cannot replace `! (... < 1.0)` with `... >= 1.0`,
1737 			because `! (NaN < 1.0)` is true but `NaN >= 1.0` is false.
1738 		*/
1739 		Melder_assert (! (frexp (HUGE_VAL, & exponent) < 1.0));
1740 		Melder_assert (! (frexp (0.0/0.0, & exponent) < 1.0));
1741 		Melder_assert (! (frexp (undefined, & exponent) < 1.0));
1742 	}
1743 	Melder_assert (str32equ (Melder_integer (1234567), U"1234567"));
1744 	Melder_assert (str32equ (Melder_integer (-1234567), U"-1234567"));
1745 	MelderColour notExplicitlyIniitialized;
1746 	Melder_assert (str32equ (Melder_colour (notExplicitlyIniitialized), U"{0,0,0}"));
1747 	Melder_assert (str32equ (Melder_colour (MelderColour (0.25, 0.50, 0.875)), U"{0.25,0.5,0.875}"));
1748 	{
1749 		VEC xn;   // "uninitialized", but initializes x.cells to nullptr and x.size to 0
1750 		Melder_assert (! xn.cells);
1751 		Melder_assert (xn.size == 0);
1752 		VEC xn2 = xn;   // copy construction
1753 		xn2 = xn;   // copy assignment
1754 		VEC x { };
1755 		Melder_assert (! x.cells);
1756 		Melder_assert (x.size == 0);
1757 		constVEC xnc;   // zero-initialized
1758 		Melder_assert (! xnc.cells);
1759 		Melder_assert (xnc.size == 0);
1760 		constVEC xc { };
1761 		Melder_assert (! xc.cells);
1762 		Melder_assert (xc.size == 0);
1763 		MAT yn;
1764 		Melder_assert (! yn.cells);
1765 		Melder_assert (yn.nrow == 0);
1766 		Melder_assert (yn.ncol == 0);
1767 		MAT y { };
1768 		Melder_assert (! y.cells);
1769 		Melder_assert (y.nrow == 0);
1770 		Melder_assert (y.ncol == 0);
1771 		//autoMAT z {y.cells,y.nrow,y.ncol};   // "No matching constructor for initialization of autoMAT" (2021-04-05)
1772 		autoMAT a = autoMAT { };
1773 		Melder_assert (! a.cells);
1774 		Melder_assert (a.nrow == 0);
1775 		Melder_assert (a.ncol == 0);
1776 		//a = z.move();
1777 		//double q [11];
1778 		//autoVEC s { & q [1], 10 };   // "No matching constructor for initialization of autoVEC" (2021-04-05)
1779 		//autoVEC b { x };   // "No matching constructor for initialization of autoVEC" (2021-04-05)
1780 		//autoVEC c = x;   // "No viable conversion from VEC to autoVEC" (2021-04-05)
1781 		double aa [] = { 3.14, 2.718 };
1782 		VEC x3 (aa, 2);   // initializes x3 to 2 values from a base-0 array
1783 		Melder_assert (x3 [2] == 2.718);
1784 		autoVEC x4 = { 3.14, 2.718 };
1785 		Melder_assert (x4 [2] == 2.718);
1786 	}
1787 	Melder_assert (Melder_iroundUpToPowerOfTwo (-10) == 1);
1788 	Melder_assert (Melder_iroundUpToPowerOfTwo (-1) == 1);
1789 	Melder_assert (Melder_iroundUpToPowerOfTwo (0) == 1);
1790 	Melder_assert (Melder_iroundUpToPowerOfTwo (1) == 1);
1791 	Melder_assert (Melder_iroundUpToPowerOfTwo (2) == 2);
1792 	Melder_assert (Melder_iroundUpToPowerOfTwo (3) == 4);
1793 	Melder_assert (Melder_iroundUpToPowerOfTwo (4) == 4);
1794 	Melder_assert (Melder_iroundUpToPowerOfTwo (5) == 8);
1795 	Melder_assert (Melder_iroundUpToPowerOfTwo (6) == 8);
1796 	Melder_assert (Melder_iroundUpToPowerOfTwo (7) == 8);
1797 	Melder_assert (Melder_iroundUpToPowerOfTwo (8) == 8);
1798 	Melder_assert (Melder_iroundUpToPowerOfTwo (9) == 16);
1799 	Melder_assert (Melder_iroundUpToPowerOfTwo (44100) == 65536);
1800 	Melder_assert (Melder_iroundUpToPowerOfTwo (131071) == 131072);
1801 	Melder_assert (Melder_iroundUpToPowerOfTwo (131072) == 131072);
1802 	Melder_assert (Melder_iroundUpToPowerOfTwo (131073) == 262144);
1803 	if (sizeof (integer) == 4) {
1804 		Melder_assert (Melder_iroundUpToPowerOfTwo (1073741823) == 1073741824);   // 2^30 - 1
1805 		Melder_assert (Melder_iroundUpToPowerOfTwo (1073741824) == 1073741824);   // 2^30
1806 		Melder_assert (Melder_iroundUpToPowerOfTwo (1073741825) == 0);   // 2^30 + 1
1807 		Melder_assert (Melder_iroundUpToPowerOfTwo (2147483647) == 0);   // 2^31 - 1, i.e. INTEGER_MAX
1808 	} else {
1809 		Melder_assert (Melder_iroundUpToPowerOfTwo (4611686018427387903LL) == 4611686018427387904LL);   // 2^62 - 1
1810 		Melder_assert (Melder_iroundUpToPowerOfTwo (4611686018427387904LL) == 4611686018427387904LL);   // 2^62
1811 		Melder_assert (Melder_iroundUpToPowerOfTwo (4611686018427387905LL) == 0);   // 2^62 + 1
1812 		Melder_assert (Melder_iroundUpToPowerOfTwo (9223372036854775807LL) == 0);   // 2^63 - 1, i.e. INTEGER_MAX
1813 	}
1814 	Melder_assert (Melder_iroundUpToPowerOfTwo (4) == 4);
1815 	{
1816 		autoMAT mat = { { 10, 20, 30, 40 }, { 60, 70, 80, 90 }, { 170, 180, 190, -300 } };
1817 		Melder_assert (mat [1] [1] == 10.0);
1818 		Melder_assert (mat [1] [4] == 40.0);
1819 		Melder_assert (mat [2] [3] == 80.0);
1820 		Melder_assert (mat [3] [1] == 170.0);
1821 		Melder_assert (mat [3] [4] == -300.0);
1822 	}
1823 	static_assert (sizeof (float) == 4,
1824 		"sizeof(float) should be 4");
1825 	static_assert (sizeof (double) == 8,
1826 		"sizeof(double) should be 8");
1827 	static_assert (sizeof (longdouble) >= 8,
1828 		"sizeof(longdouble) should be at least 8");   // this can be 8, 12 or 16
1829 	static_assert (sizeof (integer) == sizeof (void *),
1830 		"sizeof(integer) should equal the size of a pointer");
1831 	static_assert (sizeof (off_t) >= 8,
1832 		"sizeof(off_t) is less than 8. Compile Praat with -D_FILE_OFFSET_BITS=64.");
1833 
1834 	if (Melder_batch) {
1835 		if (thePraatStandAloneScriptText) {
1836 			try {
1837 				praat_executeScriptFromText (thePraatStandAloneScriptText);
1838 				praat_exit (0);
1839 			} catch (MelderError) {
1840 				Melder_flushError (praatP.title.get(), U": stand-alone script session interrupted.");
1841 				praat_exit (-1);
1842 			}
1843 		} else if (praatP.hasCommandLineInput) {
1844 			try {
1845 				praat_executeCommandFromStandardInput (praatP.title.get());
1846 				praat_exit (0);
1847 			} catch (MelderError) {
1848 				Melder_flushError (praatP.title.get(), U": command line session interrupted.");
1849 				praat_exit (-1);
1850 			}
1851 		} else {
1852 			try {
1853 				//Melder_casual (U"Script <<", theCurrentPraatApplication -> batchName.string, U">>");
1854 				praat_executeScriptFromFileNameWithArguments (theCurrentPraatApplication -> batchName.string);
1855 				praat_exit (0);
1856 			} catch (MelderError) {
1857 				Melder_flushError (praatP.title.get(), U": script command <<",
1858 					theCurrentPraatApplication -> batchName.string, U">> not completed.");
1859 				praat_exit (-1);
1860 			}
1861 		}
1862 	} else /* GUI */ {
1863 		if (! praatP.ignorePreferenceFiles) {
1864 			trace (U"reading the added script buttons");
1865 			/* Each line separately: every error should be ignored.
1866 			 */
1867 			praatP.phase = praat_READING_BUTTONS;
1868 			{// scope
1869 				autostring32 buttons;
1870 				try {
1871 					buttons = MelderFile_readText (& buttonsFile);
1872 				} catch (MelderError) {
1873 					Melder_clearError ();
1874 				}
1875 				if (buttons) {
1876 					char32 *line = buttons.get();
1877 					for (;;) {
1878 						char32 *newline = str32chr (line, U'\n');
1879 						if (newline) *newline = U'\0';
1880 						try {
1881 							(void) praat_executeCommand (nullptr, line);   // should contain no cases of "nocheck"
1882 						} catch (MelderError) {
1883 							Melder_clearError ();   // ignore this line, but not necessarily the next
1884 						}
1885 						if (! newline)
1886 							break;
1887 						line = newline + 1;
1888 					}
1889 				}
1890 			}
1891 		}
1892 
1893 		trace (U"sorting the commands");
1894 		trace (U"locale is ", Melder_peek8to32 (setlocale (LC_ALL, nullptr)));
1895 		praat_sortMenuCommands ();
1896 		praat_sortActions ();
1897 
1898 		praatP.phase = praat_HANDLING_EVENTS;
1899 
1900 		if (praatP.userWantsToOpen) {
1901 			for (; praatP.argumentNumber < praatP.argc; praatP.argumentNumber ++) {
1902 				//Melder_casual (U"File to open <<", Melder_peek8to32 (theArgv [iarg]), U">>");
1903 				/*
1904 					The use double-clicked a Praat file,
1905 					or dropped a file on the Praat icon,
1906 					while Praat was not yet running.
1907 				*/
1908 				autostring32 text = Melder_dup (Melder_cat (U"Read from file... ",
1909 															Melder_peek8to32 (praatP.argv [praatP.argumentNumber])));
1910 				try {
1911 					praat_executeScriptFromText (text.get());
1912 				} catch (MelderError) {
1913 					Melder_flushError ();
1914 				}
1915 			}
1916 		}
1917 
1918 		#if gtk
1919 			//gtk_widget_add_events (G_OBJECT (theCurrentPraatApplication -> topShell), GDK_ALL_EVENTS_MASK);
1920 			trace (U"install GTK key snooper");
1921 			trace (U"locale is ", Melder_peek8to32 (setlocale (LC_ALL, nullptr)));
1922 			//g_signal_newv ("SENDPRAAT", G_TYPE_FROM_CLASS (gobject_class), G_SIGNAL_RUN_LAST, NULL, NULL, NULL, NULL, G_TYPE_NONE, 0, NULL);
1923 			g_signal_connect (G_OBJECT (theCurrentPraatApplication -> topShell -> d_gtkWindow), "property-notify-event",
1924 					G_CALLBACK (cb_userMessage), nullptr);
1925 			signal (SIGUSR1, cb_sigusr1);
1926 			gtk_key_snooper_install (theKeySnooper, 0);
1927 			trace (U"start the GTK event loop");
1928 			trace (U"locale is ", Melder_peek8to32 (setlocale (LC_ALL, nullptr)));
1929 			gtk_main ();
1930 		#elif motif
1931 			for (;;) {
1932 				XEvent event;
1933 				GuiNextEvent (& event);
1934 				XtDispatchEvent (& event);
1935 			}
1936 		#elif cocoa
1937 			[NSApp run];
1938 		#endif
1939 	}
1940 }
1941 
1942 /* End of file praat.cpp */
1943