1 /* ScriptEditor.cpp
2  *
3  * Copyright (C) 1997-2005,2007-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 "ScriptEditor.h"
20 #include "../kar/longchar.h"
21 #include "praatP.h"
22 #include "EditorM.h"
23 
24 Thing_implement (ScriptEditor, TextEditor, 0);
25 
26 static CollectionOf <structScriptEditor> theReferencesToAllOpenScriptEditors;
27 
ScriptEditors_dirty()28 bool ScriptEditors_dirty () {
29 	for (integer i = 1; i <= theReferencesToAllOpenScriptEditors.size; i ++) {
30 		ScriptEditor me = theReferencesToAllOpenScriptEditors.at [i];
31 		if (my dirty)
32 			return true;
33 	}
34 	return false;
35 }
36 
v_destroy()37 void structScriptEditor :: v_destroy () noexcept {
38 	our argsDialog.reset();   // don't delay till delete
39 	theReferencesToAllOpenScriptEditors. undangleItem (this);
40 	ScriptEditor_Parent :: v_destroy ();
41 }
42 
v_nameChanged()43 void structScriptEditor :: v_nameChanged () {
44 	bool dirtinessAlreadyShown = GuiWindow_setDirty (our windowForm, dirty);
45 	static MelderString buffer;
46 	MelderString_copy (& buffer, name [0] ? U"Script" : U"untitled script");
47 	if (editorClass)
48 		MelderString_append (& buffer, U" [", environmentName.get(), U"]");
49 	if (name [0])
50 		MelderString_append (& buffer, U" ", MelderFile_messageName (& file));
51 	if (dirty && ! dirtinessAlreadyShown)
52 		MelderString_append (& buffer, U" (modified)");
53 	GuiShell_setTitle (windowForm, buffer.string);
54 }
55 
v_goAway()56 void structScriptEditor :: v_goAway () {
57 	if (interpreter -> running) {
58 		Melder_flushError (U"Cannot close the script window while the script is running or paused. Please close or continue the pause or demo window.");
59 	} else {
60 		ScriptEditor_Parent :: v_goAway ();
61 	}
62 }
63 
args_ok(UiForm sendingForm,integer,Stackel,conststring32,Interpreter,conststring32,bool,void * void_me)64 static void args_ok (UiForm sendingForm, integer /* narg */, Stackel /* args */, conststring32 /* sendingString */,
65 	Interpreter /* interpreter */, conststring32 /* invokingButtonTitle */, bool /* modified */, void *void_me)
66 {
67 	iam (ScriptEditor);
68 	autostring32 text = GuiText_getString (my textWidget);
69 	structMelderFile file { };
70 	if (my name [0]) {
71 		Melder_pathToFile (my name.get(), & file);
72 		MelderFile_setDefaultDir (& file);
73 	}
74 	Melder_includeIncludeFiles (& text);
75 
76 	Interpreter_getArgumentsFromDialog (my interpreter.get(), sendingForm);
77 
78 	autoPraatBackground background;
79 	if (my name [0])
80 		MelderFile_setDefaultDir (& file);
81 	Interpreter_run (my interpreter.get(), text.get());
82 }
83 
args_ok_selectionOnly(UiForm sendingForm,integer,Stackel,conststring32,Interpreter,conststring32,bool,void * void_me)84 static void args_ok_selectionOnly (UiForm sendingForm, integer /* narg */, Stackel /* args */, conststring32 /* sendingString */,
85 	Interpreter /* interpreter */, conststring32 /* invokingButtonTitle */, bool /* modified */, void *void_me)
86 {
87 	iam (ScriptEditor);
88 	autostring32 text = GuiText_getSelection (my textWidget);
89 	if (! text)
90 		Melder_throw (U"No text is selected any longer.\nPlease reselect or click Cancel.");
91 	structMelderFile file { };
92 	if (my name [0]) {
93 		Melder_pathToFile (my name.get(), & file);
94 		MelderFile_setDefaultDir (& file);
95 	}
96 	Melder_includeIncludeFiles (& text);
97 
98 	Interpreter_getArgumentsFromDialog (my interpreter.get(), sendingForm);
99 
100 	autoPraatBackground background;
101 	if (my name [0])
102 		MelderFile_setDefaultDir (& file);
103 	Interpreter_run (my interpreter.get(), text.get());
104 }
105 
menu_cb_run(ScriptEditor me,EDITOR_ARGS_DIRECT)106 static void menu_cb_run (ScriptEditor me, EDITOR_ARGS_DIRECT) {
107 	bool isObscured = false;
108 	if (my interpreter -> running)
109 		Melder_throw (U"The script is already running (paused). Please close or continue the pause or demo window.");
110 	autostring32 text = GuiText_getString (my textWidget);
111 	trace (U"Running the following script (1):\n", text.get());
112 	structMelderFile file { };
113 	if (my name [0]) {
114 		Melder_pathToFile (my name.get(), & file);
115 		MelderFile_setDefaultDir (& file);
116 	}
117 	const conststring32 obscuredLabel = U"#!praatObscured";
118 	if (Melder_stringMatchesCriterion (text.get(), kMelder_string::STARTS_WITH, obscuredLabel, true)) {
119 		const integer obscuredLabelLength = str32len (obscuredLabel);
120 		const double fileKey_real = Melder_atof (MelderFile_name (& file));
121 		const uint64 fileKey = ( isdefined (fileKey_real) ? uint64 (fileKey_real) : 0 );
122 		char32 *restOfText = & text [obscuredLabelLength];
123 		uint64 passwordHash = 0;
124 		if (*restOfText == U'\n') {
125 			restOfText += 1;   // skip newline
126 		} else if (*restOfText == U' ') {
127 			restOfText ++;
128 			char32 *endOfFirstLine = str32chr (restOfText, U'\n');
129 			if (! endOfFirstLine)
130 				Melder_throw (U"Incomplete script.");
131 			*endOfFirstLine = U'\0';
132 			passwordHash = NUMhashString (restOfText);
133 			restOfText = endOfFirstLine + 1;
134 		} else {
135 			Melder_throw (U"Unexpected nonspace after #!praatObscured.");
136 		}
137 		static uint64 nonsecret = UINT64_C (529857089);
138 		text = unhex_STR (restOfText, fileKey + nonsecret + passwordHash);
139 		isObscured = true;
140 	}
141 	Melder_includeIncludeFiles (& text);
142 	const integer npar = Interpreter_readParameters (my interpreter.get(), text.get());
143 	if (npar) {
144 		/*
145 			Pop up a dialog box for querying the arguments.
146 		*/
147 		my argsDialog = Interpreter_createForm (my interpreter.get(), my windowForm, nullptr, args_ok, me, false);
148 		UiForm_do (my argsDialog.get(), false);
149 	} else {
150 		autoPraatBackground background;
151 		if (my name [0])
152 			MelderFile_setDefaultDir (& file);
153 		trace (U"Running the following script (2):\n", text.get());
154 		Interpreter_run (my interpreter.get(), text.get());
155 	}
156 }
157 
menu_cb_runSelection(ScriptEditor me,EDITOR_ARGS_DIRECT)158 static void menu_cb_runSelection (ScriptEditor me, EDITOR_ARGS_DIRECT) {
159 	if (my interpreter -> running)
160 		Melder_throw (U"The script is already running (paused). Please close or continue the pause or demo window.");
161 	autostring32 text = GuiText_getSelection (my textWidget);
162 	if (! text)
163 		Melder_throw (U"No text selected.");
164 	structMelderFile file { };
165 	if (my name [0]) {
166 		Melder_pathToFile (my name.get(), & file);
167 		MelderFile_setDefaultDir (& file);
168 	}
169 	Melder_includeIncludeFiles (& text);
170 	integer npar = Interpreter_readParameters (my interpreter.get(), text.get());
171 	if (npar) {
172 		/*
173 			Pop up a dialog box for querying the arguments.
174 		*/
175 		my argsDialog = Interpreter_createForm (my interpreter.get(), my windowForm, nullptr, args_ok_selectionOnly, me, true);
176 		UiForm_do (my argsDialog.get(), false);
177 	} else {
178 		autoPraatBackground background;
179 		if (my name [0])
180 			MelderFile_setDefaultDir (& file);
181 		Interpreter_run (my interpreter.get(), text.get());
182 	}
183 }
184 
menu_cb_addToMenu(ScriptEditor me,EDITOR_ARGS_FORM)185 static void menu_cb_addToMenu (ScriptEditor me, EDITOR_ARGS_FORM) {
186 	EDITOR_FORM (U"Add to menu", U"Add to fixed menu...")
187 		WORD (window, U"Window", U"?")
188 		SENTENCE (menu, U"Menu", U"File")
189 		SENTENCE (command, U"Command", U"Do it...")
190 		SENTENCE (afterCommand, U"After command", U"")
191 		INTEGER (depth, U"Depth", U"0")
192 		INFILE (scriptFile, U"Script file", U"")
193 	EDITOR_OK
194 		if (my editorClass)
195 			SET_STRING (window, my editorClass -> className)
196 		if (my name [0])
197 			SET_STRING (scriptFile, my name.get())
198 		else
199 			SET_STRING (scriptFile, U"(please save your script first)")
200 	EDITOR_DO
201 		praat_addMenuCommandScript (window, menu, command, afterCommand, depth, scriptFile);
202 		praat_show ();
203 	EDITOR_END
204 }
205 
menu_cb_addToFixedMenu(ScriptEditor me,EDITOR_ARGS_FORM)206 static void menu_cb_addToFixedMenu (ScriptEditor me, EDITOR_ARGS_FORM) {
207 	EDITOR_FORM (U"Add to fixed menu", U"Add to fixed menu...");
208 		RADIOSTR (window, U"Window", 1)
209 			RADIOBUTTON (U"Objects")
210 			RADIOBUTTON (U"Picture")
211 		SENTENCE (menu, U"Menu", U"New")
212 		SENTENCE (command, U"Command", U"Do it...")
213 		SENTENCE (afterCommand, U"After command", U"")
214 		INTEGER (depth, U"Depth", U"0")
215 		INFILE (scriptFile, U"Script file", U"")
216 	EDITOR_OK
217 		if (my name [0])
218 			SET_STRING (scriptFile, my name.get())
219 		else
220 			SET_STRING (scriptFile, U"(please save your script first)")
221 	EDITOR_DO
222 		praat_addMenuCommandScript (window, menu, command, afterCommand, depth, scriptFile);
223 		praat_show ();
224 	EDITOR_END
225 }
226 
menu_cb_addToDynamicMenu(ScriptEditor me,EDITOR_ARGS_FORM)227 static void menu_cb_addToDynamicMenu (ScriptEditor me, EDITOR_ARGS_FORM) {
228 	EDITOR_FORM (U"Add to dynamic menu", U"Add to dynamic menu...")
229 		WORD (class1, U"Class 1", U"Sound")
230 		INTEGER (number1, U"Number 1", U"0")
231 		WORD (class2, U"Class 2", U"")
232 		INTEGER (number2, U"Number 2", U"0")
233 		WORD (class3, U"Class 3", U"")
234 		INTEGER (number3, U"Number 3", U"0")
235 		SENTENCE (command, U"Command", U"Do it...")
236 		SENTENCE (afterCommand, U"After command", U"")
237 		INTEGER (depth, U"Depth", U"0")
238 		INFILE (scriptFile, U"Script file", U"")
239 	EDITOR_OK
240 		if (my name [0])
241 			SET_STRING (scriptFile, my name.get())
242 		else
243 			SET_STRING (scriptFile, U"(please save your script first)")
244 	EDITOR_DO
245 		praat_addActionScript (class1, number1, class2, number2, class3, number3, command, afterCommand, depth, scriptFile);
246 		praat_show ();
247 	EDITOR_END
248 }
249 
menu_cb_clearHistory(ScriptEditor,EDITOR_ARGS_DIRECT)250 static void menu_cb_clearHistory (ScriptEditor /* me */, EDITOR_ARGS_DIRECT) {
251 	UiHistory_clear ();
252 }
253 
menu_cb_pasteHistory(ScriptEditor me,EDITOR_ARGS_DIRECT)254 static void menu_cb_pasteHistory (ScriptEditor me, EDITOR_ARGS_DIRECT) {
255 	char32 *history = UiHistory_get ();
256 	if (! history || history [0] == U'\0')
257 		Melder_throw (U"No history.");
258 	integer length = str32len (history);
259 	if (history [length - 1] != U'\n') {
260 		UiHistory_write (U"\n");
261 		history = UiHistory_get ();
262 		length = str32len (history);
263 	}
264 	if (history [0] == U'\n') {
265 		history ++;
266 		length --;
267 	}
268 	integer first = 0, last = 0;
269 	autostring32 text = GuiText_getStringAndSelectionPosition (my textWidget, & first, & last);
270 	GuiText_replace (my textWidget, first, last, history);
271 	GuiText_setSelection (my textWidget, first, first + length);
272 	GuiText_scrollToSelection (my textWidget);
273 }
274 
menu_cb_expandIncludeFiles(ScriptEditor me,EDITOR_ARGS_DIRECT)275 static void menu_cb_expandIncludeFiles (ScriptEditor me, EDITOR_ARGS_DIRECT) {
276 	structMelderFile file { };
277 	autostring32 text = GuiText_getString (my textWidget);
278 	if (my name [0]) {
279 		Melder_pathToFile (my name.get(), & file);
280 		MelderFile_setDefaultDir (& file);
281 	}
282 	Melder_includeIncludeFiles (& text);
283 	GuiText_setString (my textWidget, text.get());
284 }
285 
menu_cb_AboutScriptEditor(ScriptEditor,EDITOR_ARGS_DIRECT)286 static void menu_cb_AboutScriptEditor (ScriptEditor, EDITOR_ARGS_DIRECT) { Melder_help (U"ScriptEditor"); }
menu_cb_ScriptingTutorial(ScriptEditor,EDITOR_ARGS_DIRECT)287 static void menu_cb_ScriptingTutorial (ScriptEditor, EDITOR_ARGS_DIRECT) { Melder_help (U"Scripting"); }
menu_cb_ScriptingExamples(ScriptEditor,EDITOR_ARGS_DIRECT)288 static void menu_cb_ScriptingExamples (ScriptEditor, EDITOR_ARGS_DIRECT) { Melder_help (U"Scripting examples"); }
menu_cb_PraatScript(ScriptEditor,EDITOR_ARGS_DIRECT)289 static void menu_cb_PraatScript (ScriptEditor, EDITOR_ARGS_DIRECT) { Melder_help (U"Praat script"); }
menu_cb_FormulasTutorial(ScriptEditor,EDITOR_ARGS_DIRECT)290 static void menu_cb_FormulasTutorial (ScriptEditor, EDITOR_ARGS_DIRECT) { Melder_help (U"Formulas"); }
menu_cb_Functions(ScriptEditor,EDITOR_ARGS_DIRECT)291 static void menu_cb_Functions (ScriptEditor, EDITOR_ARGS_DIRECT) { Melder_help (U"Functions"); }
menu_cb_DemoWindow(ScriptEditor,EDITOR_ARGS_DIRECT)292 static void menu_cb_DemoWindow (ScriptEditor, EDITOR_ARGS_DIRECT) { Melder_help (U"Demo window"); }
menu_cb_TheHistoryMechanism(ScriptEditor,EDITOR_ARGS_DIRECT)293 static void menu_cb_TheHistoryMechanism (ScriptEditor, EDITOR_ARGS_DIRECT) { Melder_help (U"History mechanism"); }
menu_cb_InitializationScripts(ScriptEditor,EDITOR_ARGS_DIRECT)294 static void menu_cb_InitializationScripts (ScriptEditor, EDITOR_ARGS_DIRECT) { Melder_help (U"Initialization script"); }
menu_cb_AddingToAFixedMenu(ScriptEditor,EDITOR_ARGS_DIRECT)295 static void menu_cb_AddingToAFixedMenu (ScriptEditor, EDITOR_ARGS_DIRECT) { Melder_help (U"Add to fixed menu..."); }
menu_cb_AddingToADynamicMenu(ScriptEditor,EDITOR_ARGS_DIRECT)296 static void menu_cb_AddingToADynamicMenu (ScriptEditor, EDITOR_ARGS_DIRECT) { Melder_help (U"Add to dynamic menu..."); }
297 
v_createMenus()298 void structScriptEditor :: v_createMenus () {
299 	ScriptEditor_Parent :: v_createMenus ();
300 	if (editorClass) {
301 		Editor_addCommand (this, U"File", U"Add to menu...", 0, menu_cb_addToMenu);
302 	} else {
303 		Editor_addCommand (this, U"File", U"Add to fixed menu...", 0, menu_cb_addToFixedMenu);
304 		Editor_addCommand (this, U"File", U"Add to dynamic menu...", 0, menu_cb_addToDynamicMenu);
305 	}
306 	Editor_addCommand (this, U"File", U"-- close --", 0, nullptr);
307 	Editor_addCommand (this, U"Edit", U"-- history --", 0, nullptr);
308 	Editor_addCommand (this, U"Edit", U"Clear history", 0, menu_cb_clearHistory);
309 	Editor_addCommand (this, U"Edit", U"Paste history", 'H', menu_cb_pasteHistory);
310 	Editor_addCommand (this, U"Convert", U"-- expand --", 0, nullptr);
311 	Editor_addCommand (this, U"Convert", U"Expand include files", 0, menu_cb_expandIncludeFiles);
312 	Editor_addMenu (this, U"Run", 0);
313 	Editor_addCommand (this, U"Run", U"Run", 'R', menu_cb_run);
314 	Editor_addCommand (this, U"Run", U"Run selection", 'T', menu_cb_runSelection);
315 }
316 
v_createHelpMenuItems(EditorMenu menu)317 void structScriptEditor :: v_createHelpMenuItems (EditorMenu menu) {
318 	ScriptEditor_Parent :: v_createHelpMenuItems (menu);
319 	EditorMenu_addCommand (menu, U"About ScriptEditor", '?', menu_cb_AboutScriptEditor);
320 	EditorMenu_addCommand (menu, U"Scripting tutorial", 0, menu_cb_ScriptingTutorial);
321 	EditorMenu_addCommand (menu, U"Scripting examples", 0, menu_cb_ScriptingExamples);
322 	EditorMenu_addCommand (menu, U"Praat script", 0, menu_cb_PraatScript);
323 	EditorMenu_addCommand (menu, U"Formulas tutorial", 0, menu_cb_FormulasTutorial);
324 	EditorMenu_addCommand (menu, U"Functions", 0, menu_cb_Functions);
325 	EditorMenu_addCommand (menu, U"Demo window", 0, menu_cb_DemoWindow);
326 	EditorMenu_addCommand (menu, U"-- help history --", 0, nullptr);
327 	EditorMenu_addCommand (menu, U"The History mechanism", 0, menu_cb_TheHistoryMechanism);
328 	EditorMenu_addCommand (menu, U"Initialization scripts", 0, menu_cb_InitializationScripts);
329 	EditorMenu_addCommand (menu, U"-- help add --", 0, nullptr);
330 	EditorMenu_addCommand (menu, U"Adding to a fixed menu", 0, menu_cb_AddingToAFixedMenu);
331 	EditorMenu_addCommand (menu, U"Adding to a dynamic menu", 0, menu_cb_AddingToADynamicMenu);
332 }
333 
ScriptEditor_init(ScriptEditor me,Editor environment,conststring32 initialText)334 void ScriptEditor_init (ScriptEditor me, Editor environment, conststring32 initialText) {
335 	if (environment) {
336 		my environmentName = Melder_dup (environment -> name.get());
337 		my editorClass = environment -> classInfo;
338 	}
339 	TextEditor_init (me, initialText);
340 	my interpreter = Interpreter_createFromEnvironment (environment);
341 	theReferencesToAllOpenScriptEditors. addItem_ref (me);
342 }
343 
ScriptEditor_createFromText(Editor environment,conststring32 initialText)344 autoScriptEditor ScriptEditor_createFromText (Editor environment, conststring32 initialText) {
345 	try {
346 		autoScriptEditor me = Thing_new (ScriptEditor);
347 		ScriptEditor_init (me.get(), environment, initialText);
348 		return me;
349 	} catch (MelderError) {
350 		Melder_throw (U"Script window not created.");
351 	}
352 }
353 
ScriptEditor_createFromScript_canBeNull(Editor environment,Script script)354 autoScriptEditor ScriptEditor_createFromScript_canBeNull (Editor environment, Script script) {
355 	try {
356 		for (integer ieditor = 1; ieditor <= theReferencesToAllOpenScriptEditors.size; ieditor ++) {
357 			ScriptEditor editor = theReferencesToAllOpenScriptEditors.at [ieditor];
358 			if (MelderFile_equal (& script -> file, & editor -> file)) {
359 				Editor_raise (editor);
360 				Melder_appendError (U"The script ", & script -> file, U" is already open and has been moved to the front.");
361 				if (editor -> dirty)
362 					Melder_appendError (U"Choose \"Reopen from disk\" if you want to revert to the old version.");
363 				Melder_flushError ();
364 				return autoScriptEditor();   // safe null
365 			}
366 		}
367 		autostring32 text = MelderFile_readText (& script -> file);
368 		autoScriptEditor me = ScriptEditor_createFromText (environment, text.get());
369 		MelderFile_copy (& script -> file, & my file);
370 		Thing_setName (me.get(), Melder_fileToPath (& script -> file));
371 		return me;
372 	} catch (MelderError) {
373 		Melder_throw (U"Script window not created.");
374 	}
375 }
376 
ScriptEditor_debug_printAllOpenScriptEditors()377 void ScriptEditor_debug_printAllOpenScriptEditors () {
378 	for (integer ieditor = 1; ieditor <= theReferencesToAllOpenScriptEditors.size; ieditor ++) {
379 		ScriptEditor editor = theReferencesToAllOpenScriptEditors.at [ieditor];
380 		Melder_casual (U"Open script editor #", ieditor, U": <<", & editor -> file, U">>");
381 	}
382 }
383 
384 /* End of file ScriptEditor.cpp */
385