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