1 /* praat_menuCommands.cpp
2  *
3  * Copyright (C) 1992-2018,2020,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 "praatP.h"
20 #include "praat_script.h"
21 #include "praat_version.h"
22 #include "GuiP.h"
23 
24 static OrderedOf <structPraat_Command> theCommands;
praat_menuCommands_exit_optimizeByLeaking()25 void praat_menuCommands_exit_optimizeByLeaking () { theCommands. _ownItems = false; }
26 
praat_menuCommands_init()27 void praat_menuCommands_init () {
28 }
29 
praat_sortMenuCommands()30 void praat_sortMenuCommands () {
31 	for (integer i = 1; i <= theCommands.size; i ++) {
32 		Praat_Command command = theCommands.at [i];
33 		command -> sortingTail = i;
34 	}
35 	std::sort (theCommands.begin(), theCommands.end(),
36 		[] (Praat_Command me, Praat_Command thee) {
37 			if (my window) {
38 				if (! thy window)
39 					return false;
40 				int compare = str32cmp (my window.get(), thy window.get());
41 				if (compare != 0)
42 					return compare < 0;
43 			} else if (thy window)
44 				return true;
45 			if (my menu) {
46 				if (! thy menu)
47 					return false;
48 				int compare = str32cmp (my menu.get(), thy menu.get());
49 				if (compare != 0)
50 					return compare < 0;
51 			} else if (thy menu)
52 				return true;
53 			return my sortingTail < thy sortingTail;
54 		}
55 	);
56 }
57 
lookUpMatchingMenuCommand(conststring32 window,conststring32 menu,conststring32 title)58 static integer lookUpMatchingMenuCommand (conststring32 window, conststring32 menu, conststring32 title) {
59 /*
60  * A menu command is fully specified by its environment (window + menu) and its title.
61  */
62 	for (integer i = 1; i <= theCommands.size; i ++) {
63 		Praat_Command command = theCommands.at [i];
64 		conststring32 tryWindow = command -> window.get();
65 		conststring32 tryMenu = command -> menu.get();
66 		conststring32 tryTitle = command -> title.get();
67 		if ((window == tryWindow || (window && tryWindow && str32equ (window, tryWindow))) &&
68 		    (menu == tryMenu || (menu && tryMenu && str32equ (menu, tryMenu))) &&
69 		    (title == tryTitle || (title && tryTitle && str32equ (title, tryTitle)))) return i;
70 	}
71 	return 0;   // not found
72 }
73 
do_menu(Praat_Command me,bool isModified)74 static void do_menu (Praat_Command me, bool isModified) {
75 	if (my callback == DO_RunTheScriptFromAnyAddedMenuCommand) {
76 		UiHistory_write (U"\nrunScript: ");
77 		try {
78 			DO_RunTheScriptFromAnyAddedMenuCommand (nullptr, 0, nullptr, my script.get(), nullptr, nullptr, false, nullptr);
79 		} catch (MelderError) {
80 			Melder_flushError (U"Command \"", my title.get(), U"\" not executed.");
81 		}
82 		praat_updateSelection ();
83 	} else {
84 		if (my title && ! str32str (my title.get(), U"...")) {
85 			UiHistory_write (U"\n");
86 			UiHistory_write (my title.get());
87 		}
88 		try {
89 			my callback (nullptr, 0, nullptr, nullptr, nullptr, my title.get(), isModified, nullptr);
90 		} catch (MelderError) {
91 			Melder_flushError (U"Command \"", my title.get(), U"\" not executed.");
92 		}
93 		praat_updateSelection ();
94 	}
95 }
96 
gui_button_cb_menu(Praat_Command me,GuiButtonEvent event)97 static void gui_button_cb_menu (Praat_Command me, GuiButtonEvent event) {
98 	const bool isModified = event -> shiftKeyPressed || event -> commandKeyPressed || event -> optionKeyPressed;
99 	do_menu (me, isModified);
100 }
101 
gui_cb_menu(Praat_Command me,GuiMenuItemEvent event)102 static void gui_cb_menu (Praat_Command me, GuiMenuItemEvent event) {
103 	const bool isModified = event -> shiftKeyPressed || event -> commandKeyPressed || event -> optionKeyPressed;
104 	do_menu (me, isModified);
105 }
106 
windowMenuToWidget(conststring32 window,conststring32 menu)107 static GuiMenu windowMenuToWidget (conststring32 window, conststring32 menu) {
108 	return
109 		str32equ (window, U"Picture") ? praat_picture_resolveMenu (menu) :
110 		str32equ (window, U"Objects") ? praat_objects_resolveMenu (menu) : nullptr;
111 }
112 
praat_addMenuCommand_(conststring32 window,conststring32 menu,conststring32 title,conststring32 after,uint32 flags,UiCallback callback,conststring32 nameOfCallback)113 GuiMenuItem praat_addMenuCommand_ (conststring32 window, conststring32 menu, conststring32 title /* cattable */,
114 	conststring32 after, uint32 flags, UiCallback callback, conststring32 nameOfCallback)
115 {
116 	integer position;
117 	int depth = flags, key = 0;
118 	bool unhidable = false, hidden = false, noApi = false, forceApi = false;
119 	int deprecationYear = 0;
120 	uint32 guiFlags = 0;
121 	if (flags > 7) {
122 		depth = (flags & praat_DEPTH_7) >> 16;
123 		unhidable = (flags & praat_UNHIDABLE) != 0;
124 		hidden = (flags & praat_HIDDEN) != 0 && ! unhidable;
125 		key = flags & 0x000000FF;
126 		noApi = (flags & praat_NO_API) != 0;
127 		forceApi = (flags & praat_FORCE_API) != 0;
128 		deprecationYear = (flags & praat_DEPRECATED) == praat_DEPRECATED ? 2000 + (flags >> 24) : 0;
129 		guiFlags = key ? flags & (0x000000FF | GuiMenu_SHIFT | GuiMenu_OPTION | GuiMenu_BUTTON_STATE_MASK) : flags & GuiMenu_BUTTON_STATE_MASK;
130 	}
131 	if (callback && ! title) {
132 		Melder_flushError (U"praat_addMenuCommand: command with callback has no title. Window \"", window, U"\", menu \"", menu, U"\".");
133 		return nullptr;
134 	}
135 
136 	/*
137 	 * Determine the position of the new command.
138 	 */
139 	if (after && after [0] != U'*') {   // search for existing command with same selection
140 		integer found = lookUpMatchingMenuCommand (window, menu, after);
141 		if (found) {
142 			position = found + 1;   // after 'after'
143 		} else {
144 			Melder_flushError (U"praat_addMenuCommand: the command \"", title, U"\" cannot be put after \"", after, U"\",\n"
145 				U"in the menu \"", menu, U"\" in the window \"", window, U"\"\n"
146 				U"because the latter command does not exist.");
147 			return nullptr;
148 		}
149 	} else {
150 		position = theCommands.size + 1;   // at end
151 	}
152 
153 	/*
154 	 * Make new command.
155 	 */
156 	autoPraat_Command command = Thing_new (Praat_Command);
157 
158 	command -> window = Melder_dup_f (window);
159 	command -> menu = Melder_dup_f (menu);
160 	command -> title = Melder_dup_f (title);
161 	trace (U"insert new command \"", title, U"\"");
162 	command -> depth = depth;
163 	command -> callback = callback;   // null for a separator or cascade button
164 	command -> nameOfCallback = nameOfCallback;
165 	command -> executable = !! callback;
166 	command -> script = autostring32();
167 	command -> hidden = hidden;
168 	command -> unhidable = unhidable;
169 	command -> deprecationYear = deprecationYear;
170 	command -> noApi = noApi;
171 	command -> forceApi = forceApi;
172 
173 	if (! theCurrentPraatApplication -> batch) {
174 		GuiMenu parentMenu = nullptr;
175 
176 		/* WHERE TO PUT IT?
177 		 * Determine parent menu widget.
178 		 * This is not going to fail:
179 		 * if 'depth' is inappropriate, the alleged subitem will be put in the top menu.
180 		 */
181 		if (depth == 0) {
182 			/*
183 			 * Put the new command in the window's top menu.
184 			 */
185 			parentMenu = windowMenuToWidget (window, menu);
186 		} else {
187 			/*
188 			 * Put the new command in a submenu.
189 			 * The supermenu to put the new command in is the first menu that we find when going up.
190 			 */
191 			for (integer parentPosition = position - 1; parentPosition > 0; parentPosition --) {
192 				Praat_Command parentCommand = theCommands.at [parentPosition];
193 				if (parentCommand -> depth == depth - 1 && str32equ (parentCommand -> menu.get(), command -> menu.get())) {
194 					/*
195 					 * We found the supermenu.
196 					 */
197 					if (! parentCommand -> callback && parentCommand -> title && parentCommand -> title [0] != U'-') {
198 						if (! parentCommand -> button)
199 							Melder_fatal (U"No button for ", window, U"/", menu, U"/", title, U".");
200 						Thing_cast (GuiMenuItem, parentButton_as_GuiMenuItem, parentCommand -> button);
201 						parentMenu = parentButton_as_GuiMenuItem -> d_menu;
202 					}
203 					break;
204 				}
205 			}
206 			if (! parentMenu)
207 				parentMenu = windowMenuToWidget (window, menu);   // fallback: put the command in the window's top menu
208 		}
209 		if (! parentMenu) {
210 			trace (U"WARNING: no parent menu for ", window, U"/", menu, U"/", title, U".");
211 			return nullptr;
212 		}
213 
214 		/*
215 		 * WHAT TO PUT THERE?
216 		 */
217 
218 		if (! title || title [0] == U'-') {
219 			trace (U"insert the command as a separator");
220 			command -> button = GuiMenu_addSeparator (parentMenu);
221 			Melder_assert (command -> button);
222 		} else if (! callback) {
223 			trace (U"insert the command as a submenu");
224 			command -> button = GuiMenu_createInMenu (parentMenu, title, 0) -> d_menuItem.get();
225 			Melder_assert (command -> button);
226 		} else {
227 			trace (U"insert the command as a normal menu item");
228 			command -> button = GuiMenu_addItem (parentMenu, title, guiFlags, gui_cb_menu, command.get());
229 			Melder_assert (command -> button);
230 		}
231 		if (hidden) GuiThing_hide (command -> button);
232 	}
233 	Thing_cast (GuiMenuItem, button_as_GuiMenuItem, command -> button);
234 	theCommands. addItemAtPosition_move (command.move(), position);
235 	return button_as_GuiMenuItem;
236 }
237 
praat_addMenuCommandScript(conststring32 window,conststring32 menu,conststring32 title,conststring32 after,integer depth,conststring32 script)238 void praat_addMenuCommandScript (conststring32 window, conststring32 menu, conststring32 title,
239 	conststring32 after, integer depth, conststring32 script)
240 {
241 	try {
242 		Melder_assert (window && menu && title && after && script);
243 		if (script [0] != U'\0' && title [0] == U'\0')
244 			Melder_throw (U"Command with script has no title. Window \"", window, U"\", menu \"", menu, U"\".");
245 
246 		/*
247 		 * Determine the position of the new command.
248 		 */
249 		integer position;
250 		if (str32len (after) && after [0] != U'*') {   // search for existing command with same selection
251 			integer found = lookUpMatchingMenuCommand (window, menu, after);
252 			if (found) {
253 				position = found + 1;   // after 'after'
254 			} else {
255 				/*Melder_throw (U"The menu command \"", title, U"\" cannot be put after \"", after, U"\",\n"
256 					U"in the menu \"", menu, "\" in the window \"", window, U"\"\n"
257 					U"because the latter command does not exist.");*/
258 				position = theCommands.size + 1;   // default: at end
259 			}
260 		} else {
261 			position = theCommands.size + 1;   // at end
262 		}
263 
264 		/*
265 		 * Make new command.
266 		 */
267 		autoPraat_Command command = Thing_new (Praat_Command);
268 		command -> window = Melder_dup_f (window);
269 		command -> menu = Melder_dup_f (menu);
270 		command -> title = ( title [0] != U'\0' ? Melder_dup_f (title) : autostring32() );   // allow old-fashioned untitled separators
271 		command -> depth = depth;
272 		command -> callback = ( script [0] != U'\0' ? DO_RunTheScriptFromAnyAddedMenuCommand : nullptr );   // null for a separator or cascade button
273 		command -> executable = ( script [0] != U'\0' );
274 		command -> noApi = true;
275 		if (script [0] == U'\0') {
276 			command -> script = Melder_dup_f (U"");   // empty string, which will be needed to signal origin
277 		} else {
278 			structMelderFile file { };
279 			Melder_relativePathToFile (script, & file);
280 			command -> script = Melder_dup_f (Melder_fileToPath (& file));
281 		}
282 		command -> after = ( after [0] != U'\0' ? Melder_dup_f (after) : autostring32() );
283 		if (praatP.phase >= praat_READING_BUTTONS) {
284 			static integer uniqueID = 0;
285 			command -> uniqueID = ++ uniqueID;
286 		}
287 
288 		if (! theCurrentPraatApplication -> batch) {
289 			GuiMenu parentMenu = nullptr;
290 
291 			/* WHERE TO PUT IT?
292 			 * Determine parent menu widget.
293 			 * This is not going to fail:
294 			 * if 'depth' is inappropriate, the alleged subitem will be put in the top menu.
295 			 */
296 			if (depth == 0) {
297 				parentMenu = windowMenuToWidget (window, menu);   // not a subitem: in the top menu
298 			} else {
299 				for (integer parentPosition = position - 1; parentPosition > 0; parentPosition --) {
300 					Praat_Command parentCommand = theCommands.at [parentPosition];
301 					if (parentCommand -> depth == depth - 1) {
302 						if (! parentCommand -> callback && parentCommand -> title && parentCommand -> title [0] != U'-') {
303 							if (! parentCommand -> button)
304 								Melder_fatal (U"No button for ", window, U"/", menu, U"/", title, U".");
305 							Melder_assert (parentCommand -> button -> classInfo == classGuiMenuItem);
306 							parentMenu = (static_cast <GuiMenuItem> (parentCommand -> button)) -> d_menu;
307 						}
308 						break;
309 					}
310 				}
311 				if (! parentMenu)
312 					parentMenu = windowMenuToWidget (window, menu);   // fallback: a subitem without a menu title
313 			}
314 			if (parentMenu) {
315 				/* WHAT TO PUT THERE?
316 				 */
317 				if (title [0] == U'\0' || title [0] == U'-') {
318 					command -> button = GuiMenu_addSeparator (parentMenu);
319 				} else if (script [0] == '\0') {
320 					command -> button = GuiMenu_createInMenu (parentMenu, title, 0) -> d_menuItem.get();
321 				} else {
322 					command -> button = GuiMenu_addItem (parentMenu, title, 0, gui_cb_menu, command.get());
323 				}
324 			}
325 		}
326 		theCommands. addItemAtPosition_move (command.move(), position);
327 
328 		if (praatP.phase >= praat_HANDLING_EVENTS) praat_sortMenuCommands ();
329 	} catch (MelderError) {
330 		Melder_throw (U"Script menu command not added.");
331 	}
332 }
333 
praat_hideMenuCommand(conststring32 window,conststring32 menu,conststring32 title)334 void praat_hideMenuCommand (conststring32 window, conststring32 menu, conststring32 title) {
335 	if (theCurrentPraatApplication -> batch || ! window || ! menu || ! title) return;
336 	integer found = lookUpMatchingMenuCommand (window, menu, title);
337 	if (! found) return;
338 	Praat_Command command = theCommands.at [found];
339 	if (! command -> hidden && ! command -> unhidable) {
340 		command -> hidden = true;
341 		if (praatP.phase >= praat_READING_BUTTONS) command -> toggled = ! command -> toggled;
342 		if (command -> button) GuiThing_hide (command -> button);
343 	}
344 }
345 
praat_showMenuCommand(conststring32 window,conststring32 menu,conststring32 title)346 void praat_showMenuCommand (conststring32 window, conststring32 menu, conststring32 title) {
347 	if (theCurrentPraatApplication -> batch || ! window || ! menu || ! title) return;
348 	integer found = lookUpMatchingMenuCommand (window, menu, title);
349 	if (! found) return;
350 	Praat_Command command = theCommands.at [found];
351 	if (command -> hidden) {
352 		command -> hidden = false;
353 		if (praatP.phase >= praat_READING_BUTTONS) command -> toggled = ! command -> toggled;
354 		if (command -> button) GuiThing_show (command -> button);
355 	}
356 }
357 
praat_saveAddedMenuCommands(MelderString * buffer)358 void praat_saveAddedMenuCommands (MelderString *buffer) {
359 	/*
360 		The procedure as it is now, runs in M*N time,
361 		where M is the number of added commands and N is the total number of commands.
362 		Sorting first instead runs in N log N time and will therefore be faster if M >> log N ≈ 10.
363 	*/
364 	integer maxID = 0;
365 	for (integer i = 1; i <= theCommands.size; i ++) {
366 		Praat_Command command = theCommands.at [i];
367 		if (command -> uniqueID > maxID)
368 			maxID = command -> uniqueID;
369 	}
370 	for (integer id = 1; id <= maxID; id ++)   // sorted
371 		for (integer i = 1; i <= theCommands.size; i ++) {
372 			Praat_Command me = theCommands.at [i];
373 			if (my uniqueID == id && ! my hidden && my window && my menu && my title) {
374 				MelderString_append (buffer, U"Add menu command... \"", my window.get(), U"\" \"", my menu.get(), U"\" \"", my title.get(), U"\" \"",
375 					( my after ? my after.get() : U"" ), U"\" ", my depth, U" ", ( my script ? my script.get() : U"" ), U"\n");
376 				break;
377 			}
378 		}
379 }
380 
praat_saveToggledMenuCommands(MelderString * buffer)381 void praat_saveToggledMenuCommands (MelderString *buffer) {
382 	for (integer i = 1; i <= theCommands.size; i ++) {
383 		Praat_Command me = theCommands.at [i];
384 		if (my toggled && my window && my menu && my title && ! my uniqueID && ! my script)
385 			MelderString_append (buffer, my hidden ? U"Hide" : U"Show", U" menu command... \"",
386 				my window.get(), U"\" \"", my menu.get(), U"\" ", my title.get(), U"\n");
387 	}
388 }
389 
390 /***** FIXED BUTTONS *****/
391 
praat_addFixedButtonCommand_(GuiForm parent,conststring32 title,UiCallback callback,conststring32 nameOfCallback,int x,int y)392 void praat_addFixedButtonCommand_ (GuiForm parent, conststring32 title, UiCallback callback, conststring32 nameOfCallback, int x, int y) {
393 	autoPraat_Command me = Thing_new (Praat_Command);
394 	my window = Melder_dup_f (U"Objects");
395 	my title = Melder_dup_f (title);
396 	my callback = callback;
397 	my nameOfCallback = nameOfCallback;
398 	my unhidable = true;
399 	my noApi = ( str32equ (title, U"Inspect") );
400 	if (theCurrentPraatApplication -> batch) {
401 		my button = nullptr;
402 	} else {
403 		GuiThing button = my button = GuiButton_create (parent, x, x + 82, -y - Gui_PUSHBUTTON_HEIGHT, -y,
404 			title, gui_button_cb_menu, me.get(), 0);
405 		GuiThing_setSensitive (button, false);
406 		GuiThing_show (button);
407 	}
408 	my executable = false;
409 	theCommands. addItemAtPosition_move (me.move(), 0);
410 }
411 
praat_sensitivizeFixedButtonCommand(conststring32 title,bool sensitive)412 void praat_sensitivizeFixedButtonCommand (conststring32 title, bool sensitive) {
413 	Praat_Command commandFound = nullptr;
414 	for (integer i = 1; i <= theCommands.size; i ++) {
415 		Praat_Command command = theCommands.at [i];
416 		if (str32equ (command -> title.get(), title)) {
417 			commandFound = command;
418 			break;
419 		}
420 	}
421 	if (! commandFound)
422 		Melder_fatal (U"Unkown fixed button <<", title, U">>");
423 	commandFound -> executable = sensitive;
424 	if (! theCurrentPraatApplication -> batch && ! Melder_backgrounding)
425 		GuiThing_setSensitive (commandFound -> button, sensitive);
426 }
427 
praat_doMenuCommand(conststring32 title,conststring32 arguments,Interpreter interpreter)428 int praat_doMenuCommand (conststring32 title, conststring32 arguments, Interpreter interpreter) {
429 	Praat_Command commandFound = nullptr;
430 	for (integer i = 1; i <= theCommands.size; i ++) {
431 		Praat_Command command = theCommands.at [i];
432 		if (command -> executable && str32equ (command -> title.get(), title) &&
433 			(str32equ (command -> window.get(), U"Objects") || str32equ (command -> window.get(), U"Picture")))
434 		{
435 			commandFound = command;
436 			break;
437 		}
438 	}
439 	if (! commandFound)
440 		return 0;
441 	if (commandFound -> callback == DO_RunTheScriptFromAnyAddedMenuCommand) {
442 		const conststring32 scriptPath = commandFound -> script.get();
443 		const conststring32 preferencesFolderPath = Melder_dirToPath (& Melder_preferencesFolder);
444 		const bool scriptIsInPlugin =
445 				Melder_stringMatchesCriterion (scriptPath, kMelder_string::STARTS_WITH, preferencesFolderPath, true);
446 		Melder_throw (
447 			U"From a script you cannot directly call a menu command that calls another script. Use instead: \nrunScript: ",
448 			scriptIsInPlugin ? U"preferencesDirectory$ + " : U"",
449 			U"\"",
450 			scriptIsInPlugin ? scriptPath + str32len (preferencesFolderPath) : scriptPath,
451 			U"\"",
452 			arguments && arguments [0] ? U", " : U"",
453 			arguments && arguments [0] ? arguments : U"",
454 			U"\n"
455 		);
456 	}
457 	commandFound -> callback (nullptr, 0, nullptr, arguments, interpreter, title, false, nullptr);
458 	return 1;
459 }
460 
praat_doMenuCommand(conststring32 title,integer narg,Stackel args,Interpreter interpreter)461 int praat_doMenuCommand (conststring32 title, integer narg, Stackel args, Interpreter interpreter) {
462 	Praat_Command commandFound = nullptr;
463 	for (integer i = 1; i <= theCommands.size; i ++) {
464 		Praat_Command command = theCommands.at [i];
465 		if (command -> executable && str32equ (command -> title.get(), title) &&
466 			(str32equ (command -> window.get(), U"Objects") || str32equ (command -> window.get(), U"Picture")))
467 		{
468 			commandFound = command;
469 			break;
470 		}
471 	}
472 	if (! commandFound)
473 		return 0;
474 	if (commandFound -> callback == DO_RunTheScriptFromAnyAddedMenuCommand) {
475 		const conststring32 scriptPath = commandFound -> script.get();
476 		const conststring32 preferencesFolderPath = Melder_dirToPath (& Melder_preferencesFolder);
477 		const bool scriptIsInPlugin =
478 				Melder_stringMatchesCriterion (scriptPath, kMelder_string::STARTS_WITH, preferencesFolderPath, true);
479 		Melder_throw (
480 			U"From a script you cannot directly call a menu command that calls another script. Use instead: \nrunScript: ",
481 			scriptIsInPlugin ? U"preferencesDirectory$ + " : U"",
482 			U"\"",
483 			scriptIsInPlugin ? scriptPath + str32len (preferencesFolderPath) : scriptPath,
484 			U"\"",
485 			narg > 0 ? U", ..." : U"",
486 			U"\n"
487 		);
488 	}
489 	commandFound -> callback (nullptr, narg, args, nullptr, interpreter, title, false, nullptr);
490 	return 1;
491 }
492 
praat_getNumberOfMenuCommands()493 integer praat_getNumberOfMenuCommands () { return theCommands.size; }
494 
praat_getMenuCommand(integer i)495 Praat_Command praat_getMenuCommand (integer i)
496 	{ return i < 1 || i > theCommands.size ? nullptr : theCommands.at [i]; }
497 
praat_addCommandsToEditor(Editor me)498 void praat_addCommandsToEditor (Editor me) {
499 	conststring32 windowName = my classInfo -> className;
500 	for (integer i = 1; i <= theCommands.size; i ++) {
501 		Praat_Command command = theCommands.at [i];
502 		if (str32equ (command -> window.get(), windowName)) {
503 			Editor_addCommandScript (me, command -> menu.get(), command -> title.get(), 0, command -> script.get());
504 		}
505 	}
506 }
507 
commandIsToBeIncluded(Praat_Command command,bool deprecated,bool includeCreateAPI,bool includeReadAPI,bool includeRecordAPI,bool includePlayAPI,bool includeDrawAPI,bool includeHelpAPI,bool includeWindowAPI)508 static bool commandIsToBeIncluded (Praat_Command command, bool deprecated, bool includeCreateAPI, bool includeReadAPI,
509 	bool includeRecordAPI, bool includePlayAPI, bool includeDrawAPI, bool includeHelpAPI, bool includeWindowAPI)
510 {
511 	const bool obsolete = ( deprecated && (command -> deprecationYear < PRAAT_YEAR - 10 || command -> deprecationYear < 2017) );
512 	const bool hiddenByDefault = ( command -> hidden != command -> toggled );
513 	const bool explicitlyHidden = hiddenByDefault && ! deprecated;
514 	const bool hidden = explicitlyHidden || ! command -> callback || command -> noApi || obsolete ||
515 		(! includeWindowAPI && Melder_nequ (command -> nameOfCallback, U"WINDOW_", 7)) ||
516 		(! includeHelpAPI && Melder_nequ (command -> nameOfCallback, U"HELP_", 5)) ||
517 		(! includeDrawAPI && Melder_nequ (command -> nameOfCallback, U"GRAPHICS_", 9)) ||
518 		(! includePlayAPI && Melder_nequ (command -> nameOfCallback, U"PLAY_", 5)) ||
519 		(! includeRecordAPI && Melder_nequ (command -> nameOfCallback, U"RECORD_", 7)) ||
520 		(! includeReadAPI && Melder_nequ (command -> nameOfCallback, U"READ_", 5)) ||
521 		(! includeReadAPI && Melder_nequ (command -> nameOfCallback, U"READ1_", 6)) ||
522 		(! includeCreateAPI && Melder_nequ (command -> nameOfCallback, U"NEW1_", 5));
523 	return (command -> forceApi || ! hidden) && command -> callback != DO_RunTheScriptFromAnyAddedMenuCommand;
524 }
525 
commandHasFileNameArgument(Praat_Command command)526 static bool commandHasFileNameArgument (Praat_Command command) {
527 	const bool hasFileNameArgument =
528 		Melder_nequ (command -> nameOfCallback, U"READ1_", 6) ||
529 		Melder_nequ (command -> nameOfCallback, U"SAVE_", 5)
530 	;
531 	return hasFileNameArgument;
532 }
533 
getReturnType(Praat_Command command)534 static conststring32 getReturnType (Praat_Command command) {
535 	const conststring32 returnType =
536 		Melder_nequ (command -> nameOfCallback, U"NEW1_", 5) ? U"PraatObject" :
537 		Melder_nequ (command -> nameOfCallback, U"READ1_", 6) ? U"PraatObject" :
538 		Melder_nequ (command -> nameOfCallback, U"REAL_", 5) ? U"double" :
539 		Melder_nequ (command -> nameOfCallback, U"INTEGER_", 8) ? U"int64_t" :
540 		Melder_nequ (command -> nameOfCallback, U"STRING_", 7) ? U"char *" :
541 		Melder_nequ (command -> nameOfCallback, U"REPORT_", 7) ? U"char *" :
542 		Melder_nequ (command -> nameOfCallback, U"LIST_", 5) ? U"char *" :
543 		Melder_nequ (command -> nameOfCallback, U"INFO_", 5) ? U"char *" :
544 		Melder_nequ (command -> nameOfCallback, U"HINT_", 5) ? U"char *" :
545 		U"void";
546 	return returnType;
547 }
548 
praat_menuCommands_writeC(bool isInHeaderFile,bool includeCreateAPI,bool includeReadAPI,bool includeRecordAPI,bool includePlayAPI,bool includeDrawAPI,bool includeHelpAPI,bool includeWindowAPI)549 void praat_menuCommands_writeC (bool isInHeaderFile, bool includeCreateAPI, bool includeReadAPI,
550 	bool includeRecordAPI, bool includePlayAPI, bool includeDrawAPI, bool includeHelpAPI, bool includeWindowAPI)
551 {
552 	try {
553 		integer numberOfApiMenuCommands = 0;
554 		for (integer i = 1; i <= theCommands.size; i ++) {
555 			Praat_Command command = theCommands.at [i];
556 			const bool deprecated = ( command -> deprecationYear > 0 );
557 			if (! commandIsToBeIncluded (command, deprecated, includeCreateAPI, includeReadAPI,
558 				includeRecordAPI, includePlayAPI, includeDrawAPI, includeHelpAPI, includeWindowAPI)) continue;
559 			MelderInfo_writeLine (U"\n/* Menu command \"", command -> title.get(), U"\"",
560 				deprecated ? U", deprecated " : U"", deprecated ? Melder_integer (command -> deprecationYear) : U"",
561 				U" */");
562 			conststring32 returnType = getReturnType (command);
563 			MelderInfo_writeLine (returnType, U" Praat", str32chr (command -> nameOfCallback, U'_'), U" (");
564 			const bool isDirect = ! str32str (command -> title.get(), U"...");
565 			if (isDirect) {
566 			} else {
567 				command -> callback (nullptr, -1, nullptr, nullptr, nullptr, nullptr, false, nullptr);
568 			}
569 			if (commandHasFileNameArgument (command))
570 				MelderInfo_writeLine (U"\tconst char *fileName");
571 			MelderInfo_write (U")");
572 			if (isInHeaderFile) {
573 				MelderInfo_writeLine (U";");
574 			} else {
575 				MelderInfo_writeLine (U" {");
576 				MelderInfo_writeLine (U"}");
577 			}
578 			numberOfApiMenuCommands += 1;
579 		}
580 	} catch (MelderError) {
581 		Melder_throw (U"Menu commands not written to C library.");
582 	}
583 }
584 
585 /* End of file praat_menuCommands.cpp */
586